Home > Blockchain >  How can I re-render an instance of an element generated while mapping an array in react js?
How can I re-render an instance of an element generated while mapping an array in react js?

Time:02-10

I'm working on this trivia game, fetching the trivia from Trivia Open DB API. The questions are multiple choice, the correct_answer is a key / value pair, while the incorrect_answer is a key with a value of an array with three incorrect answers, therefore I created a new array with the incorrect answer and randomly inserted the correct answer to that array.

I mapped that array to render once instance for each element of the array and I want to only re-render (change the background color) of the clicked answer, but can't figure out how to do this. I console logged my solution to determine if you clicked the correct answer or not, and it works, but can't figure out how to re-render just the instance that was clicked instead of all the jsx elements of the array. Here's my code:

import React, {useState, useEffect} from 'react'
import { nanoid } from 'nanoid'

export default function Question(){
    const [questions, setQuestions] = useState("")
 

    useEffect(function() {
   
    fetch(`https://opentdb.com/api.php?amount=10&category=26&type=multiple`)
        .then(res => res.json())
        .then(data => setQuestions(data.results.map(triviaQuestion =>{
       
        const randomPosition = Math.floor(Math.random()*4)
        const questionDisplayed = triviaQuestion.question;
        const incorrectAnswersArray = triviaQuestion.incorrect_answers;
        incorrectAnswersArray.splice(randomPosition, 0, triviaQuestion.correct_answer)
        const possibleAnsewrs = incorrectAnswersArray.map(answer => answer)
        function selectAnswer(event){
            event.target.id == possibleAnsewrs.indexOf(triviaQuestion.correct_answer) ? console.log("Correct!!") : console.log("Incorrect, the answer is: " triviaQuestion.correct_answer   " "   possibleAnsewrs.indexOf(triviaQuestion.correct_answer))
                }
        const allAnswers = incorrectAnswersArray.map(answer => 
            <p 
                id={possibleAnsewrs.indexOf(answer)} 
                key={nanoid()} 
                onClick={selectAnswer}  
                className="question--answers"
                >
                {answer}
            </p>)

        return (
        <div  
            key={nanoid()} className='question--container'
            >
            <h3 key={nanoid()} className="question" >{questionDisplayed}</h3>
            <div  
                key={nanoid()} className='question--answer-container'>
            {allAnswers}
            </div>
        </div>
        )
    } 
        )))  
},[]) 
    return(    
            <div>
             {questions}   
            </div>
    )
}

CodePudding user response:

You should not store JSX in state, store only the data and render the JSX from state. When an answer is clicked you update the state. When state is updated it will trigger a rerender of your UI. What you update is up to you.

Example:

function Question() {
  const [questions, setQuestions] = useState([]);

  useEffect(function () {
    fetch(`https://opentdb.com/api.php?amount=10&category=26&type=multiple`)
      .then((res) => res.json())
      .then((data) => {
        setQuestions(
          data.results.map((question) => ({
            id: nanoid(),
            ...question,
            answers: [
              ...question.incorrect_answers,
              question.correct_answer
            ].sort(() => Math.random() - 0.5)
          }))
        );
      });
  }, []);

  const selectAnswer = (question) => (event) => {
    event.target.id === question.correct_answer
      ? console.log("Correct!!")
      : console.log("Incorrect, the answer is: "   question.correct_answer);

    setQuestions((questions) =>
      questions.map((el) =>
        el.id === question.id
          ? {
              ...el,
              answered: event.target.id,
              isCorrect: event.target.id === question.correct_answer
            }
          : el
      )
    );
  };

  return (
    <div>
      {questions.map((question) => {
        return (
          <div key={question.id} className="question--container">
            <h3
              className="question"
              dangerouslySetInnerHTML={{ __html: question.question }}
            />
            <div className="question--answer-container">
              {question.answers.map((answer) => (
                <p
                  key={answer}
                  id={answer}
                  onClick={selectAnswer(question)}
                  className={[
                    "question--answers",
                    question.answered === answer &&
                      (question.isCorrect ? "correct" : "incorrect")
                  ]
                    .filter(Boolean)
                    .join(" ")}
                >
                  {answer}
                </p>
              ))}
            </div>
          </div>
        );
      })}
    </div>
  );
}

CSS

correct {
  background-color: lightgreen;
  border: 1px solid green;
  border-radius: 1rem;
}

.incorrect {
  background-color: lightcoral;
  border: 1px solid red;
  border-radius: 1rem;
}

Edit how-can-i-re-render-an-instance-of-an-element-generated-while-mapping-an-array-i

enter image description here

CodePudding user response:

You are tracking your answers by assigning them an ID, but you're not doing the same for your questions, so we have no way of knowing which question's answer needs to be updated.

You could have two seperate state, one for questions and the other for answers, and for the answers state you could assign an ID that is linked to it's question, but I prefer to simply nest the answers inside the question itself.

Here is my implementation:

import React, { useState, useEffect } from "react";
import { nanoid } from "nanoid";
import "./style.css";

function shuffle(array) {
  let currentIndex = array.length,
    randomIndex;

  // While there remain elements to shuffle...
  while (currentIndex != 0) {
    // Pick a remaining element...
    randomIndex = Math.floor(Math.random() * currentIndex);
    currentIndex--;

    // And swap it with the current element.
    [array[currentIndex], array[randomIndex]] = [
      array[randomIndex],
      array[currentIndex],
    ];
  }

  return array;
}

function parseClass(answerData) {
  if (answerData.clicked) {
    if (answerData.correct) {
      console.log("Returning correct answer");
      return "correct_answer";
    } else {
      console.log("Returning incorrect answer");
      return "incorrect_answer";
    }
  }
}

export default function Question() {
  const [questions, setQuestions] = useState([]);

  function handleAnswerClick(questionID, answerID) {
    const questionIndex = questions.findIndex(
      (question) => question.ID === questionID
    );
    const answerIndex = questions[questionIndex].answers.findIndex(
      (answer) => answer.ID === answerID
    );
    const newQuestions = [...questions];
    // Update the clicked property
    newQuestions[questionIndex].answers[answerIndex].clicked = true;
    setQuestions(newQuestions);
  }

  useEffect(function () {
    fetch(`https://opentdb.com/api.php?amount=10&category=26&type=multiple`)
      .then((res) => res.json())
      .then((data) => {
        const questionsData = data.results.map(
          (questionData, questionIndex) => {
            // Map the incorrect answers into an array and give them a property "correct" that is assigned to false, and "clicked" that is assigned to false
            let answers = questionData.incorrect_answers.map((answer) => ({
              answer,
              correct: false,
              clicked: false,
            }));

            // Push the correct answer and give it property "correct" assigned to true, and "clicked" that is assigned to false
            answers.push({
              answer: questionData.correct_answer,
              correct: true,
              clicked: false,
            });
            // Shuffle the answers array using above helper function
            answers = shuffle(answers);

            answers = answers.map((answer, answerIndex) => ({
              // Spread the values of each object in answers array
              ...answer,
              // Add an ID property to each answer
              ID: `${questionIndex}-${answerIndex}`,
            }));

            return {
              // Spread the values of each object in data.results array
              ...questionData,
              // Add the answers array we created earlier
              answers,
              // Add property, call it ID
              ID: questionIndex,
            };
          }
        );
        setQuestions(questionsData);
      });
  }, []);

  return (
    <div>
      {questions.length &&
        questions.map((questionData) => (
          <div key={questionData.ID}>
            <p>{questionData.question}</p>
            <br />
            <ul>
              {questionData.answers.map((answerData) => (
                <li
                  key={answerData.ID}
                  onClick={() =>
                    handleAnswerClick(questionData.ID, answerData.ID)
                  }
                  className={parseClass(answerData)}
                >
                  {answerData.answer}
                </li>
              ))}
            </ul>
          </div>
        ))}
    </div>
  );
}
  • Related