Home > Net >  Trying to mutate the state of React component
Trying to mutate the state of React component

Time:09-30

I'm trying to create a quizz app and got stuck at the moment when I need to change the background of the Answers (button) when the user clicked on it. Well, function holdAnswer does console.log the id of the answer, but doesn't change its background. What's missing here?

I assume I have also stored all answers that the user chose in some array in order to count how many answers the user guessed.

After I check if the answers are correct or not they have to be highlighted accordingly (correct/incorrect), so again it needs to mutate the state.

Is the code missing something from the beginning?

here is CodeSandBox link

App.js

import { useState } from "react";
import QuestionSet from "./QuestionSet";
import Answers from "./Answers";
import { nanoid } from "nanoid";

function App() {
  const [isQuesLoaded, setIsQuesLoaded] = useState(false);
  const [questions, setQuestions] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  async function startQuiz() {
    try {
      setIsQuesLoaded(!isQuesLoaded);
      const response = await fetch(
        "https://opentdb.com/api.php?amount=5&category=12&difficulty=easy&type=multiple"
      );
      const data = await response.json();
      const allQuestions = data.results;
      const listOfQuestions = allQuestions.map((item) => {
        const allAnswers = [
          {
            id: nanoid(),
            isCorrect: false,
            isChosen: false,
            answer: item.incorrect_answers[0],
          },
          {
            id: nanoid(),
            isCorrect: false,
            isChosen: false,
            answer: item.incorrect_answers[1],
          },
          {
            id: nanoid(),
            isCorrect: false,
            isChosen: false,
            answer: item.incorrect_answers[2],
          },
          {
            id: nanoid(),
            isCorrect: true,
            isChosen: false,
            answer: item.correct_answer,
          },
        ];
        return {
          id: nanoid(),
          question: item.question,
          answers: allAnswers,
        };
      });
      setQuestions(listOfQuestions);
    } catch (err) {
      setError(err.message);
    } finally {
      setLoading(false);
    }
  }

  function holdAnswer(id) {
    console.log(id);
    setQuestions((prevQuestion) =>
      prevQuestion.map((question) =>
        question.answers.id === id
          ? {
              ...question,
              answers: question.answers.map((answer) =>
                answer.id === id
                  ? { ...answer, isChosen: !answer.isChosen }
                  : answer
              ),
            }
          : question
      )
    );
  }

  const questionElm = questions.map((question, index) => {
    return (
      <section key={index}>
        <QuestionSet question={question.question} key={question.id} />
        <Answers
          answers={question.answers}
          isChosen={question.answers.isChosen}
          holdAnswer={holdAnswer}
        />
      </section>
    );
  });

  return (
    <div className="App">
      {!isQuesLoaded ? (
        <main>
          <h1 className="title-app">Quizzical</h1>
          <p className="desc-app">Some description if needed</p>
          <button className="btn" onClick={startQuiz}>
            Start Quiz
          </button>
        </main>
      ) : (
        <main className="quest-box">
          {loading && <div>Loading data...</div>}
          {error && <div>{`There is a problem fetchning data = ${error}`}</div>}
          <section className="quest-content">{questionElm}</section>

          <button className="answer-btn">Check Answers</button>
        </main>
      )}
    </div>
  );
}

export default App;

Answers.js

export default function Answers(props) {
  const styles = {
    backgroundColor: props.answers.isChosen ? "#D6DBF5" : "transparent",
  };

  return (
    <section className="answer-container">
      <div
        className="answer-div"
        style={styles}
        id={props.answers[3].id}
        onClick={() => props.holdAnswer(props.answers[3].id)}
      >
        <p>{props.answers[3].answer}</p>
      </div>
      <div
        className="answer-div"
        style={styles}
        id={props.answers[1].id}
        onClick={() => props.holdAnswer(props.answers[1].id)}
      >
        <p>{props.answers[1].answer}</p>
      </div>
      <div
        className="answer-div"
        style={styles}
        id={props.answers[2].id}
        onClick={() => props.holdAnswer(props.answers[2].id)}
      >
        <p>{props.answers[2].answer}</p>
      </div>
      <div
        className="answer-div"
        style={styles}
        id={props.answers[0].id}
        onClick={() => props.holdAnswer(props.answers[0].id)}
      >
        <p>{props.answers[0].answer}</p>
      </div>
    </section>
  );
}

CodePudding user response:

I wont give any direct answer, its up to you to find out.

First, there is an issue with the React.StrictMode as it runs some of the react api twice such as useEffect (and in ur case the setter for setQuestions), you can remove it for now since i dont think you really need it for this.

Lastly, if you look carefully at where you are changing the styles conditionally you'll see that you are referencing some object fields incorrectly.

it looks like you're just starting out react, so good luck and happy coding.

CodePudding user response:

Your making confusions on object fields, using typescript will prevent you from doing this kind of error.

function holdAnswer(id) {
    console.log(id);
    setQuestions((prevQuestion) =>
      prevQuestion.map((question) =>
        question.answers.id === id // question.answers is an array
// you need to pass the questionId to holdAnswer and use it here
          ? {
              ...question,
              answers: question.answers.map((answer) =>
                answer.id === id
                  ? { ...answer, isChosen: !answer.isChosen }
                  : answer
              )
            }
          : question
      )
    );
  }


  // props.answers is also an array, create a component for Answer and  use style here
  const styles = {
    backgroundColor: props.answers.isChosen ? "#D6DBF5" : "transparent"
  };

Here is the codesandbox if you want to see how to fix it.

  • Related