Piszemy wstępny szkielet dla naszej aplikacji quizowej, jaką postaramy się zrobić w bibliotece React. Później jeszcze wiele razy zmienimy kod. Do dzieła.
Ok, nasz App RFC, ze stanem globalnym:
import './App.css';
import { useState} from "react";
import { Menu } from './Menu';
import { Quiz } from './Quiz';
import { Finish } from './Finish';
function App() {
let [mode, setMode] = useState('menu');
let [name, setName] = useState('');
if(mode === 'menu'){
return (
<div className="App">
<Menu setMode={setMode} name={name} setName={setName}/>
</div>
);
}
if(mode === 'quiz'){
return (
<div className="App">
<Quiz name={name} setMode={setMode}/>
</div>
)
}
if(mode === 'finish'){
return (
<div className="App">
<Finish setMode={setMode} name={name} />
</div>
)
}
return (
<div className="App">
App
</div>
)
}
export default App;
Później sobie te ify brzydkie zamienimy na coś lepszego. Także useState pozamieniamy na useReducer, ale to, jak już będziemy mieć jakieś pojęcie, jak będzie wyglądał initialState (w praniu nam wyjdzie).
Chciałbym tylko dodać, że takie ify są podstępne. Na przykład możemy wiedzieć, że hooki typu useEffect używamy tylko w top-level RFC. Nie wyżej, nie wewnątrz ifów. Ale czy wiedzieliśmy, że kolejność też ma znaczenie? Nie możemy używać sobie takich hooków po fragmencie kodu, który coś sprawdza i robi return.
Ok, fajnie, fajnie, zacznijmy zatem zabawę z komponentami. Na pierwszy ogień idzie menu:
import { useRef, useEffect } from 'react';
function Menu({setMode, name, setName}){
const btnRef = useRef(null);
useEffect(() => {
if(name === ""){
btnRef.current.setAttribute("disabled", "");
} else {
btnRef.current.removeAttribute("disabled");
}
}, [name]);
return (
<>
<p>Your name: {name}</p>
<input type="text" onChange={(e) => setName(e.target.value)} value={name}/>
<button ref={btnRef} onClick={() => setMode("quiz")}>Start Quiz</button>
</>
)
};
export {Menu};
Chyba nie muszę tłumaczyć jak to działa, już wielokrotnie tego typu podstawy omawialiśmy. Dobra, następny komponent:
function Quiz({setMode, name}){
return (
<>
<p>Your name: {name}</p>
<button onClick={() => setMode('finish')}>Finish Quiz</button>
</>
)
};
export {Quiz};
Na razie skromnie, chcemy sprawdzić, czy przechodzenie z jednego do drugiego działa bez problemów. Ok, następny komponent:
function Finish({setMode, name}){
return (
<>
<p>Quiz finished!</p>
<p>Your name: {name}</p>
<p>Your score: 0</p>
<button onClick={() => setMode('menu')}>Restart Quiz</button>
</>
)
};
export {Finish};
Dobra, powinno działać. Teraz mała przerwa i kilka poprawek. Upraszczamy RFC APP:
function App() {
let [mode, setMode] = useState('menu');
let [name, setName] = useState('');
return (
<div className="App">
{mode === 'menu' ? <Menu setMode={setMode} name={name} setName={setName}/> : null }
{mode === 'quiz' ? <Quiz name={name} setMode={setMode}/> : null }
{mode === 'finish' ? <Finish setMode={setMode} name={name} />: null }
</div>
)
}
export default App;
Jeżeli short circuiting nas nie przeraża, to polecam użyć zamiast tych nullów:
function App() {
let [mode, setMode] = useState('menu');
let [name, setName] = useState('');
return (
<div className="App">
{mode === 'menu' && <Menu setMode={setMode} name={name} setName={setName}/> }
{mode === 'quiz' && <Quiz name={name} setMode={setMode}/> }
{mode === 'finish' && <Finish setMode={setMode} name={name} />}
</div>
)
}
export default App;
Ok, dodajmy sobie score:
let [score, setScore] = useState(0);
Będziemy też musieli przekazać to komponentom niżej:
{mode === 'quiz' && <Quiz name={name} setMode={setMode} score={score} setScore={setScore}/> }
{mode === 'finish' && <Finish setMode={setMode} name={name} score={score} />}
Na razie to taka prowizorka, ale ok, lecimy z quiz:
function Quiz({setMode, name, score, setScore}){
function handleClick(){
if(score < 10){
setScore(score => score + 1);
} else {
setMode("finish");
}
}
return (
<>
<p>Your name: {name}</p>
<p>Your score: {score}</p>
<button onClick={handleClick}>{score < 10 ? "Answer Question" : "Finish"}</button>
</>
)
};
export {Quiz};
Ok, działa, ale jeszcze finish musi dostać update:
function Finish({setMode, name, score}){
return (
<>
<p>Quiz finished!</p>
<p>Your name: {name}</p>
<p>Your score: {score}</p>
<button onClick={() => setMode('menu')}>Restart Quiz</button>
</>
)
};
export {Finish};
Tylko teraz on ten score będzie pamiętać… Tutaj najbardziej rozsądne wydaje się drillowanie setScore do Finish i użycie go:
function Finish({setMode, name, score, setScore}){
function handleRestart(){
setScore(0);
setMode('menu');
}
return (
<>
<p>Quiz finished!</p>
<p>Your name: {name}</p>
<p>Your score: {score}</p>
<button onClick={handleRestart}>Restart Quiz</button>
</>
)
};
export {Finish};
Działa, nasze App teraz tak wygląda:
import './App.css';
import { useState} from "react";
import { Menu } from './Menu';
import { Quiz } from './Quiz';
import { Finish } from './Finish';
function App() {
let [mode, setMode] = useState('menu');
let [name, setName] = useState('');
let [score, setScore] = useState(0);
return (
<div className="App">
{mode === 'menu' && <Menu setMode={setMode} name={name} setName={setName}/> }
{mode === 'quiz' && <Quiz name={name} setMode={setMode} score={score} setScore={setScore}/> }
{mode === 'finish' && <Finish setMode={setMode} name={name} score={score} setScore={setScore} />}
</div>
)
}
export default App;
Ok, na razie starczy. Rozbudujemy w lekcji następnej!