Ok, nasza aplikacja trochę się pozmieniała, postaram się przedstawić jak teraz wygląda. Jeszcze daleko od perfekcji, ale idziemy w dobrym kierunku.

Ok, rzecz pierwsza, zmieniły nam się pytania, ich format:

const QUESTIONS = [
    {
      id: 'q1',
      text: 'What is 2 + 2 = ?',
      answers: {
        optionA: '2',
        optionB:'cztery',
        optionC: '6',
        optionD: '8',
      },
      correctIndex: "optionB"
    },
    {
        id: 'q2',
        text: 'What is 2 x 2 = ?',
        answers: {
            optionA: '2',
            optionB:'cztery',
            optionC: '6',
            optionD: '8',
          },
          correctIndex: "optionB"
      },
      {
        id: 'q3',
        text: 'What is 2 + 2 x 2 = ?',
        answers: {
            optionA: '2',
            optionB:'cztery',
            optionC: '6',
            optionD: '8',
          },
          correctIndex: "optionC"
      },
  ];

  export default QUESTIONS;

Robimy przymiarki do tego, aby móc potem mieszać te odpowiedzi. Na razie pokazuję zmiany. Stare answers:

// function Answers({answers, handleAnswer}){
//     return (
//         <ul>
//             {answers.map((ans, idx) => {
//                 return <li key={ans} onClick={() => handleAnswer(idx)} data-idx={idx}>{ans}</li>
//             })}
//         </ul>
//     )
// };

// export {Answers};

Nowe Answers:

import { useRef } from 'react';

function Answers({answers, handleAnswer}){

    const readyAnswers = useRef([]);
    if(readyAnswers.current.length === 0){
        for (const [k, v] of Object.entries(answers)) {
            const item = [k, v]
            readyAnswers.current.push(item);
          }
        console.log(readyAnswers.current)
    }
    return (
        <ul>
            {readyAnswers.current.map((item) => {
                const [key,val] = item;
               return <li key={val} data-idx={key}  onClick={() => handleAnswer(key)}>{val}</li>
            })}
        </ul>
    )
};

Chciałem być przesadnie cwany i trzymać tablicę elementów JSX, ale wtedy wszystko zawiesiło się na pierwszym pytaniu (chodzi o ten onClick). Ok, kolejne zmiany:

function Menu(){

    const [{name }, dispatch] = useQuizWithDispatch();
    
    const btnRef = useRef(null);

    useEffect(() => {
        if(name === ""){
            btnRef.current.setAttribute("disabled", "");
        } else {
            btnRef.current.removeAttribute("disabled");
        }
      }, [name]);

    return (
        <>
        <p>Your name: {name}</p>
        <Form/>
        <input type="text" onChange={(e) => dispatch({type: "changeName", payload:e.target.value})} value={name}/>
        <button ref={btnRef} onClick={() => dispatch({type: "changeMode", payload:"quiz"})}>Start Quiz</button>
        </>
    )
};

export {Menu};

Form jak widać dodany jako osobny komponent, zrobiłem takie mini-komponenty, których nie eksportuję:


import { useQuizWithDispatch} from './QuizContext';
function QuestionTiming(){

    const [{timedQuestions, questionTime}, dispatch] = useQuizWithDispatch();
    

    function onQuestionTimeChange(e){
        dispatch({type: 'questionTimeChange', payload: e.target.value});
    }

    function onTimedQestionsChange(){
        dispatch({type: "toggleTimedQuestions"})
    }

    return (
        <div>
        <input 
        type="checkbox" 
        id="timedQuestions" 
        checked={timedQuestions} 
        onChange={onTimedQestionsChange}  />
        <label htmlFor="timedQuestions">Timed Questions: {timedQuestions ? 'YES' : 'NO'}</label>
        <p></p>
        <input 
        disabled={timedQuestions ? false: true}
        value={questionTime} 
        onChange={onQuestionTimeChange} 
        type="range" 
        id="questionTime"  
        min="5" max="15" /> {questionTime}s
        </div>
    )
}

function QuizTiming(){

    const [{timedQuiz, quizTime}, dispatch] = useQuizWithDispatch();
    

    function onQuizTimeChange(e){
        dispatch({type: 'quizTimeChange', payload: e.target.value});
    }
    function onTimedQuizChange(){
        dispatch({type: "toggleTimedQuiz"})
    }

    return (
        <div>
        <input type="checkbox" id="timedQuiz" checked={timedQuiz} onChange={onTimedQuizChange}  />
        <label htmlFor="timedQuiz">Timed Quiz: {timedQuiz ? 'YES' : 'NO'}</label>
        <p></p>
        <input 
        disabled={timedQuiz ? false: true}
        value={quizTime} 
        onChange={onQuizTimeChange} 
        type="range" 
        id="quizTime"  
        min="1" max="120" /> {quizTime}m
        </div>
    )
}

function Form(){
    return (
        <>
        <QuestionTiming/>
        <QuizTiming/>
        </>
    )
};

export {Form};

Zrobiłem sobie nowy komponent QuizTimer:

import { useState, useEffect } from 'react';
import {useQuizDispatch } from './QuizContext';
export default function QuizTimer({ timeout}) {

  const [remainingTime, setRemainingTime] = useState(() => timeout);
  const dispatch = useQuizDispatch();

  useEffect(() => {
    const toMiliseconds = timeout * 60000;
    const timer = setTimeout(() => {
        dispatch({type: "changeMode", payload:"finish"})
    }, toMiliseconds);

    return () => {
      clearTimeout(timer);
    };
  }, [timeout, dispatch]);

  useEffect(() => {
    const interval = setInterval(() => {
      setRemainingTime((prevRemainingTime) => prevRemainingTime - 1);
    }, 60000);

    return () => {
      clearInterval(interval);
    };
  }, []);

  return (
    <div>
        <progress
      id="QuizTimer"
      max={timeout}
      value={remainingTime}
    />
    <span>{remainingTime}m</span>
    </div>
    
  );
}

export {QuizTimer};

Miejsce, w którym go użyłem, to Quiz RFC:

<>
        {timedQuiz && (quizIsComplete || <QuizTimer timeout={quizTime}/>)}
        <p>Your name: {name}</p>
        <p>Your score: {score}</p>
        {quizIsComplete || <Question timerKey={timerKey} index={activeQuestionIndex} handleAnswer={handleClick} /> }
        {quizIsComplete || <SimpleSummary idx={activeQuestionIndex} len={QUESTIONS.length}/>}
        {quizIsComplete && <button onClick={() => dispatch({type: "changeMode", payload:"finish"})}>Finish</button>}
        </>

Btw kontekst też nieco się zmienił:

export function useQuiz() {
    return useContext(QuizContext);
  }
  
  export function useQuizDispatch() {
    return useContext(QuizDispatchContext);
  }

  export function useQuizWithDispatch(){
    return [useContext(QuizContext), useContext(QuizDispatchContext) ];
  }

To nie jest konieczne, po prostu próbuję różne rozwiązania, teraz np. można i dispatch i dane ze state wyciągnąć jednym hookiem:

const [{name }, dispatch] = useQuizWithDispatch();

Właśnie tego użyłem sobie w Menu. Więcej Reacta niedługo!