skip to Main Content

There is a challenge on the frontend mentor website (FAQ accordion). I have solved the challenge using React. However, my code is not working properly.

Except for the first click on any question when page loads, I have to double-click each question to toggle it. I suspect something wrong in my code with useState.

I created a component that renders a list of questions/answers and displays them using the details HTML tag. I want to solve the problem using the details tag only.

Here is my code for the component:

import { Fragment, useState } from "react";
import minus from "./icon-minus.svg";
import plus from "./icon-plus.svg";

const Faq = ({ questionsObj }) => {
  const [isOpen, setIsOpen] = useState(false);
  const [selectedId, setSelectedId] = useState(null);
  function handleClick(id) {
    if (selectedId !== id) {
      setSelectedId(id);
    }
    if (selectedId === id) {
      setIsOpen(!isOpen);
    }
  }

  return (
    <main>
      {questionsObj.map((question, index) => (
        <Fragment key={question.id}>
          <details
            onClick={(e) => handleClick(question.id)}
            open={selectedId === question.id ? isOpen : null}
          >
            <summary>
              <span>{question.question}</span>

              <img
                src={
                  isOpen === false && selectedId === question.id
                    ? `${minus}`
                    : `${plus}`
                }
                alt=""
              />
            </summary>
            <p className="animate__animated animate__flipInX">
              {question.answer}
            </p>
          </details>
          {index !== questionsObj.length - 1 ? <hr /> : null}
        </Fragment>
      ))}
    </main>
  );
};

export default Faq;

I also think my code is not good to solve simple problem like this accordion. I would appreciate if someone tell me where is the problem in my code.

Thanks

2

Answers


  1. See issues/15486#issuecomment-873516817, you should use e.preventDefault() in the click event handler of <details/>

    Besides, you don’t need the isOpen state. The selectedId state is enough.

    e.g.

    import { Fragment, useState } from 'react';
    
    const Faq = ({ questionsObj }) => {
      const [selectedId, setSelectedId] = useState(null);
      function handleClick(e, question) {
        e.preventDefault();
        if (selectedId !== question.id) {
          setSelectedId(question.id);
        }
      }
    
      return (
        <main>
          {questionsObj.map((question, index) => (
            <Fragment key={question.id}>
              <details
                onClick={(e) => handleClick(e, question)}
                open={selectedId === question.id}
              >
                <summary>
                  <span>{question.question}</span>
                  <span>{selectedId === question.id ? '-' : '+'}</span>
                </summary>
    
                <p>{question.answer}</p>
              </details>
              {index !== questionsObj.length - 1 ? <hr /> : null}
            </Fragment>
          ))}
        </main>
      );
    };
    
    export default Faq;
    

    stackblitz

    Login or Signup to reply.
  2. useState works async, so when you click first time isOpen will be false because condition will match id(latest value) with selectedId(old value). so it will work on second click.

    Solution is As above ans you don’t need 2 diff state. selectedId is enough for you, but clear id on second click so it can close as like as your code.

    import { Fragment, useState } from "react";
    import minus from "./icon-minus.svg";
    import plus from "./icon-plus.svg";
    
    const Faq = ({ questionsObj }) => {
    
        const [selectedId, setSelectedId] = useState();
    
        function handleClick(id) {
            if (selectedId !== id) {
                setSelectedId(id);
            } else {
                setSelectedId('');
            }
        }
    
        return (
            <main>
                {
                    questionsObj.map((question, index) =>
                        <Fragment key={question.id}>
                            <details onClick={() => { handleClick(question.id) }} open={selectedId === question.id}>
                                <summary>
                                    <span>{question.question}</span>
                                    <img src={selectedId === question.id ? `${minus}` : `${plus}`} alt="" />
                                </summary>
                                <p className="animate__animated animate__flipInX">{question.answer}</p>
                            </details>
                            {index !== questionsObj.length - 1 ? <hr /> : null}
                        </Fragment>
                    )
                }
            </main>
        );
    };
    
    export default Faq;
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search