Home > Software engineering >  How can I set state on individual elements in a mapped function using React?
How can I set state on individual elements in a mapped function using React?

Time:02-10

I have an array of questions that I'm mapping through and displaying them in the UI, there is about 70 questions.

Each question has a ' ' button that reveals an answer when clicked, I want to reveal the answer using state but only for the specific question that is clicked, not the entire list. Currently I have it so when a button is clicked, it reveals all 70 answers, what's the best way to do this so it only reveals one?

This is my code:

  const [open, setOpen] = useState(false);


 <div className='md:mt-8 pb-20'>
            {results[0].data.glossary_boxes.map((item, i) => (
              <>
                <div
                  key={i}
                  className='w-full border-3 border-yellow-500 flex-grow p-2 md:p-2 my-8'>
                  <div className='flex h-full items-center'>
                    <div className='bg-yellow-500 px-3 py-1 flex items-center justify-start ml-2'>
                      <a
                        onClick={() => setOpen(!open)}
                        className='text-white text-3xl cursor-pointer'>
                        {open ? '-' : ' '}
                      </a>
                    </div>
                    <div className='flex flex-col items-start'>
                      <p className='text-gray-500 font-sansbold text-md md:text-xl px-12'>
                        {item.question}
                      </p>

                      <p
                        className={`text-xl gray-500 font-quote text-md md:text-lg px-12 ${
                          open ? 'block' : 'hidden'
                        }`}>
                        {item.answer}
                      </p>
                    </div>
                  </div>
                </div>
              </>
            ))}
          </div>

When the state is true, it also displays a '-' in place of where the ' ' was until clicked again to set to false (hide the answer)

Thanks in advance!

CodePudding user response:

Change the state variable to hold a unique value rather than a boolean value. For example

const [openedItem, setOpenedItem] = useState(null);

Change the onClick event handler to something like this. Where i is from your map function.

onClick={() => setOpenedItem(i)}

Change showing ( )(-) logic to this.

{openedItem === i ? '-' : ' '}

Change the dynamic class generation (block/hidden) logic to the same condition above.

CodePudding user response:

In that situation, you need to handle the open/close trigger for every question. So you need an additional variable in the object that corresponds for a single question if it's open or not (by default it will be false), that way you can handle every single question itself instead of one single open state that can tell if to open or close all. Moreover, I would suggest creating a component for a single question, and the component will receive props (isOpen, answer, question, etc..) inside the map function you will render for every item of the component.
for example :
<SingleQuestion question={item.question} answer={item.answer} isOpen={item.isOpen}>
You can let the component itself (SingleComponent) handle the inner state that decides isOpen true or false, that way every question will hold a state that lets show/hide the answer for the relevant question.
Hope you got the point, if not I'll try to explain more if needed.

CodePudding user response:

You could keep an array with all the revealed questions. Once a question gets revealed, you add that question to the array.

const [revealedQuestions, setReveleadQuestions] = useState([]);

// Receives the question as a string and checks if its in the array
const toggleAnswer = (question) => {
    // if its in the array, then remove it
    if(revealedQuestions.includes(question)){
         var index = revealedQuestions.indexOf(value);
         if (index > -1) {
             setReveleadQuestions(revealedQuestions.splice(index, 1));
         }
    }
    // if not in the array, then add it to the array
    else {
         setReveleadQuestions([...revealedQuestions, question]);
    }
}

{results[0].data.glossary_boxes.map((item, i) => (
     <div key={i}>
         // if the question is in the array show '-' else show ' '
         <a onClick={() => toggleAnswer(item.question)} >
             {revealedQuestions.includes(item.question) ? '-' : ' '}
         </a>
         <p>
             {item.question}
         </p>
         // if the question is in the array, show the answer 
         {revealedQuestions.includes(item.question) && (
             <p>{item.answer}</p>
         )}
    </div>
)}
  • Related