I'm trying to make an accordion, that opens up upon clicking the " " icon. But when I click on a single element, all the other elements expand. Is there a way to just open one of the accordions? I've added the screenshot of the accordion and also added the code.
import { AiFillPlusCircle, AiFillMinusCircle } from "react-icons/ai";
import data from "../data";
export default function Container() {
const [display, setDisplay] = useState(false);
const clickHandle = () => {
setDisplay(!display);
};
return (
<>
<div className="fContainer container">
<div className="headingContainer">
<h2 className="mainHeading">Questions And Answers About Login</h2>
</div>
<div className="fQuesContainer container">
{data.map((question) => {
const { id, title, info } = question;
return (
<div className="qCard" key={id}>
<div className="qCardTitle">
<h4 className="qTitle">{title}</h4>
<button className="btnContainer" onClick={clickHandle}>
{display === false ? (
<AiFillPlusCircle size="2.4em" />
) : (
<AiFillMinusCircle size="2.4em" />
)}
</button>
</div>
{/* This thing would return the info, or the element */}
{display === false ? (
<></>
) : (
<>
<hr className="fHr" />
<p className="fInfo">{info}</p>
</>
)}
</div>
);
})}
</div>
</div>
</>
);
}
CodePudding user response:
Explanation
Basically you can't use a single variable to toggle all your elements. All the elements will act as a single element, so either all will open or all will close.
You need something that can be checked against each element. Now id
is a potential candidate but it has it's drawbacks, since it's a list the best option is using the index of the element itself.
So you first change the display
type from boolean (false
) to integer
type and default it to -1 (anything less than zero)
Then change your .map
function from .map((question) =>...
to .map((question, questionIndex) =>...
, this will get you a variable questionIndex
which holds the current question's index
You can use that (questionIndex
) and the display
variable to check against each other and display the appropriate states.
Benefits when compared to other answers
Since you are dealing with a list of items, it is always best to use the index of an element to toggle the element's display, This ensures you have decoupled your View from your Data. (As much as possible)
If for some reason your
id
is null or duplicate, it will create issues in your display.It is easier to just call
toggleElement(2)
to automatically open an element for a given position via code (on first load). This is useful if you want to maintain the open states between url changes / reloads, you just add the index to the query parameter of the url.
Solution
import { AiFillPlusCircle, AiFillMinusCircle } from "react-icons/ai";
import data from "../data";
export default function Container() {
// Update the display to be of type integer and init it with -1
const [display, setDisplay] = useState(-1);
// Add a parameter to the click function to take the clicked element's index
const toggleElement = (currentIndex) => {
// Check if the element that is clicked is already open
if(currentIndex===display) {
setDisplay(-1); // If it is already open, close it.
}
else {
setDisplay(currentIndex); // else open the clicked element
}
};
return (
<>
<div className="fContainer container">
<div className="headingContainer">
<h2 className="mainHeading">Questions And Answers About Login</h2>
</div>
<div className="fQuesContainer container">
{/* Add a variable questionIndex to the map method to get the index of the current question */}
{data.map((question, questionIndex) => {
const { id, title, info } = question;
return (
<div className="qCard" key={id}>
<div className="qCardTitle">
<h4 className="qTitle">{title}</h4>
{/* Update the button onClick event to pass the current element's index via the questionIndex variable */}
<button className="btnContainer" onClick={()=> toggleElement(questionIndex)}>
{/* Update the UI state based on the comparison of the display and questionIndex variable (Note, if they match, you need to open the element, else close) */}
{display == questionIndex ? (
<AiFillMinusCircle size="2.4em" />
) : (
<AiFillPlusCircle size="2.4em" />
)}
</button>
</div>
{/* This thing would return the info, or the element */}
{/* Update the UI state based on the comparison of the display and questionIndex variable (Note, if they match, you need to open the element, else close) */}
{display == questionIndex ? (
<>
<hr className="fHr" />
<p className="fInfo">{info}</p>
</>
) : (
<></>
)}
</div>
);
})}
</div>
</div>
</>
);
}
CodePudding user response:
Instead of a boolean, use an id in your state
const [display, setDisplay] = useState("");
When you map an item, add this function
const { id, title, info } = question;
const handleSetDisplay = () => {
if(id === display) {
//Close panel
setDisplay("")
} else {
//open specific panel
setDisplay(id)
}
}
Adjust the button's onClick
<button className="btnContainer" onClick={handleSetDisplay}>
Then to compare if your panel should expand, use
{display === id ? ///...: ...}
In short, you need to compare the saved ID with the mapped item's id.
If your id is a number, just change the initial state to 0