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!