Home > Software engineering >  How to trigger a specific element in a list of elements that are created using .map in React JS?
How to trigger a specific element in a list of elements that are created using .map in React JS?

Time:06-30

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>
    </>
  );
}

This one is for the closed one

This one is after I click on the   icon.

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

  1. 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)

  2. If for some reason your id is null or duplicate, it will create issues in your display.

  3. 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

  • Related