2

I have succeeded in making a quiz app that shows red if the wrong option is clicked and green if the right one is clicked. The thing is that since I am getting the options from an api, I put the correct and incorrect options in an array, gave it state and then mapped over it. This means that if I go to each individual question and pick and option that I would see if it turns red or green, but this unfortunately means that the buttons are managing its own state. I want to create a button at the end of my app, so that after I've picked all my answers for all the questions, it marks all the questions together and not individually like I am doing now but I don't know how.

App.js file:

import {React, useState, useEffect} from 'react'
import './App.css';
import Question from './components/Question'

function App(){
  const [getQuestions, setGetQuestions] = useState([])
  const [start, setStart] =useState(false)

  useEffect(function(){
    async function getQuestionsFromApi(){
      const res = await fetch(`https://opentdb.com/api.php?amount=5`)
      const data = await res.json()
      setGetQuestions(data.results)
    }
    getQuestionsFromApi()
  },[])

  const displayQuestions = getQuestions.map(
    element => <Question 
      key={element.question}
      question = {element.question}
      correct_answer = {element.correct_answer}
      incorrect_answers ={element.incorrect_answers}
    />
  )

  function gameStart(){
    setStart(true)
  }

  function checkResult(){
    console.log('check result')
  }

  return(
    <>
      {start ? 
        <div>
          {displayQuestions}
          <button className='result--button' onClick={checkResult}>Check the Answers</button>
        </div> : 
        <div className="home--page">
          <h1>Quizzical</h1>
          <p>some description if needed</p>
          <button className='start' onClick={gameStart} >Start quiz</button>
        </div>
      }
    </>
  )
}
export default App

Question.js file:

import {React, useState, useEffect} from 'react'
import './Question.css'

function Question({question, correct_answer, incorrect_answers}){
  const [options, setOptions] = useState([])
  const [selectedOption, setSelectedOption] = useState()

  useEffect(function(){
    const arr = [...incorrect_answers, correct_answer]
    setOptions(arr)
  }, [incorrect_answers, correct_answer])

  function handleSelect(element){
    if (selectedOption === element && selectedOption === correct_answer) {
      return 'select'
    } else if (selectedOption === element && selectedOption !== correct_answer) {
      return 'wrong'
    } else if (element === correct_answer) {
      return 'select'
    } else {
      return 'grey'
    }
  }

  function handleClick(element){
    setSelectedOption(element)
  }

  const displayOptions = options.map(element => 
    <button 
      className= {`${selectedOption && handleSelect(element)}`}
      key={element}
      onClick={() => handleClick(element)}
      disabled={selectedOption}
    >
      {element}
    </button>
  )

  return(
    <>
      <div className='question--container'>
        <h1>
          {question}
        </h1>
        {displayOptions}
      </div>
    </>
  )
}
export default Question
Drew Reese
  • 165,259
  • 14
  • 153
  • 181
gerard
  • 221
  • 1
  • 2
  • 10

1 Answers1

0

Basically you apply the Lifting State Up pattern. The questions array is already in the parent component, you just need to move the "selected" option up to the parent. Merge the selected option with the existing question data and pass down an onSelect callback.

Example:

import { nanoid } from "nanoid";

export default function App() {
  const [questions, setQuestions] = useState([]);
  const [start, setStart] = useState(false);
  const [showResult, setShowResult] = useState(false);

  const getQuestionsFromApi = async () => {
    const res = await fetch(`https://opentdb.com/api.php?amount=5`);
    return await res.json();
  };

  const gameStart = async () => {
    const data = await getQuestionsFromApi();
    setQuestions(data.results.map((el) => ({ ...el, id: nanoid() })));
    setStart(true);
    setShowResult(false);
  };

  const checkResult = () => setShowResult(true);

  const selectAnswer = (id) => (selected) => {
    setQuestions((questions) =>
      questions.map((question) =>
        question.id === id
          ? { ...question, selected }
          : question
      )
    );
  };

  return (
    <div className="home--page">
      {start ? (
        <>
          {questions.map((question) => (
            <Question
              key={question.id}
              question={question}
              onSelect={selectAnswer(question.id)}
              showAnswer={showResult}
            />
          ))}
        </>
      ) : (
        <>
          <h1>Quizzical</h1>
          <p>some description if needed</p>
        </>
      )}

      <br />

      {start ? (
        showResult ? (
          <button className="result--button" onClick={gameStart}>
            Play again
          </button>
        ) : (
          <button className="result--button" onClick={checkResult}>
            Check the Answers
          </button>
        )
      ) : (
        <button className="start" onClick={gameStart}>
          Start quiz
        </button>
      )}
    </div>
  );
}

Question:

import { clsx } from "clsx";

function Question({
  question: { question, correct_answer, incorrect_answers, selected },
  onSelect,
  showAnswer
}) {
  const options = useMemo(() => {
    return [...incorrect_answers, correct_answer].sort(
      () => Math.random() - 0.5
    );
  }, [correct_answer, incorrect_answers]);

  const getClasses = (answer) => {
    return clsx(
      { selected: selected === answer },
      showAnswer && {
        correct: answer === correct_answer,
        wrong: selected === answer && selected !== correct_answer
      }
    );
  };

  return (
    <div className="question--container">
      <h1>{question}</h1>
      {options.map((option) => (
        <button
          className={getClasses(option)}
          key={option}
          onClick={() => onSelect(option)}
          disabled={selected}
        >
          {option}
        </button>
      ))}
    </div>
  );
}

Edit how-to-make-a-single-submit-button-for-a-quiz-app-instead-of-making-each-individ

enter image description here

Drew Reese
  • 165,259
  • 14
  • 153
  • 181