State
State en Props zijn een van de meest essentiële concepten die je moet begrijpen in React. Props dienen om informatie door een componenten structuur te geven, en state wordt gebruikt om applicaties interactief te maken.
Terwijl props worden gebruikt om data van buitenaf te geven aan een component, wordt state gebruikt om data van een component te bewaren. Als deze data verandert, zal de component opnieuw gerenderd worden (en dus ook zijn kinderen).
Alles wat verandert doorheen de levenscyclus van een component, wordt opgeslagen in de state. Dit kan een waarde zijn van een input veld, een checkbox, een lijst van items, een error, een loading state, ...
useState hook
We gaan het gebruik van state eens demonstreren aan de hand van een voorbeeld. We gaan hiervoor terug naar ons InputView
voorbeeld. Stel dat we elke keer de gebruiker iets intypt in de input box, dat we deze text willen laten tonen ergens anders in de applicatie. Dit is dus informatie die aangepast wordt over de looptijd van de applicatie.
const InputView = () => {
const handleChange: React.ChangeEventHandler<HTMLInputElement> = (event) => {
console.log(event.target.value);
}
return (
<input type="text" id="name" onChange={handleChange} />
)
}
We zouden foutief kunnen veronderstellen dat we dit probleem kunnen oplossen door een variabele te maken die de ingetypte tekst opslaat. Een verstaanbare poging is deze:
// DEZE CODE IS FOUT
const InputView = () => {
let name = "";
const handleChange: React.ChangeEventHandler<HTMLInputElement> = (event) => {
event = event.target.value;
}
return (
<>
<input type="text" id="name" onChange={handleChange} />
<p>
The name you typed is {name}
</p>
</>
);
}
Wijzigingen in het input veld hebben geen effect op de rest van de pagina. Elke render (en er vindt er niet eens een plaats wanneer de handler de waarde wijzigt) runt de code voor de component opnieuw. Er wordt dus telkens een nieuwe variabele name met beginwaarde ""
aangemaakt.
In plaats van een gewone variabele is een state variabele nodig. Bij een wijziging hiervan wordt de component hertekend en de waarde wordt bijgehouden over uitvoeringen heen. Deze variabele kan aangemaakt worden door middel van de useState hook.
const [name, setName] = useState<string>('');
De useState
functie heeft als argument een initiële state. Dit is de start waarde die de state zal krijgen als de component voor de eerste keer gerenderd wordt. De functie geeft een array terug met twee elementen in: het eerste element is de huidige state en het tweede element is een functie waarmee je de state kan en moet aanpassen. We geven aan welk type onze state zal bevatten door <string>
mee te geven aan de useState
functie.
import { useState } from "react";
const InputView = () => {
const [name, setName] = useState<string>('');
const handleChange: React.ChangeEventHandler<HTMLInputElement> = (event) => {
setName(event.target.value);
}
return (
<>
<input type="text" id="name" onChange={handleChange} value={name}/>
<p>
The name you typed is {name}
</p>
</>
);
}
Het value attribuut wordt ingesteld op de huidige waarde van de state. Zo zorgen we ervoor dat het inputveld altijd up-to-date is met de huidige waarde van de state. Dit noemen ze in react controlled components.
Je mag meerdere keren useState
gebruiken in 1 component. Bijvoorbeeld als je meerdere input velden hebt in je formulier zal je ook meerdere states hebben.
setState met callback
Soms is de nieuwe waarde van een state afhankelijk van de vorige waarde van de state. In dit geval moeten we de setState
aanroepen met een callback functie als argument.
const App = () => {
const [count, setCount] = useState(0);
return (
<>
Count: {count}
<button onClick={() => setCount(0)}>Reset</button>
<button onClick={() => {
setCount(count + 1);
setCount(count + 1); // count is nog niet geupdated.
}
}>+</button>
</>
);
}
In het bovenstaande voorbeeld zou je denken dat de count altijd met twee omhoog gaat. Maar dat is niet het geval. Als de tweede setCount
wordt aangeroepen is de count
state nog niet aangepast.
Wil je dit doen dan moet je dit op de volgende manier doen:
const App = () => {
const [count, setCount] = useState(0);
return (
<>
Count: {count}
<button onClick={() => setCount(0)}>Reset</button>
<button onClick={() => {
setCount(prevCount => prevCount + 1);
setCount(prevCount => prevCount + 1);
}
}>+</button>
</>
);
}
Regels van useState
Je mag geen useState
hook gebruiken in:
- loops
- condities
- geneste functies
Je moet altijd de useState
hook gebruiken bovenaan je React component (functie).
Als je deze regels volgt dan ben je altijd zeker dat de useState
hooks opgeroepen worden in dezelfde volgorde elke keer het component gerendered wordt.
setState
mag uiteraard wel overal gebruikt worden in het component op eender welke plaats.
Er zullen meestal geen foutmeldingen ontstaan als je dit toch doet. Maar hierdoor kunnen heel moeilijk te debuggen bugs door kunnen onstaan. Dus let er op dat je deze regels zelf goed volgt!
Hier is een voorbeeld waar we de regels van de useState
hook niet respecteren:
import { useState } from "react";
interface UserInfoProps {
askAge: boolean;
}
const UserInfo = ({ askAge }: UserInfoProps) => {
const [name, setName] = useState("");
if (askAge) {
const [age, setAge] = useState(0); // 1. Mag niet in een IF staan
// 2. Moet altijd bovenaan de functie staan
}
return (
<fieldset>
<legend>User Info</legend>
<label htmlFor="name">Name:</label>
<input
name="name"
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
/>
<br />
{askAge && (
<>
<label htmlFor="name">Age:</label>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
/>
</>
)}
</fieldset>
);
};
const App = () => {
const [askAge, setAskAge] = useState(false);
return (<>
<label>Ask age?</label>
<input type="checkbox" checked={askAge} onChange={(e) => setAskAge(e.target.checked)}/>
<UserInfo askAge={askAge} />
</>);
};
export default App;
De oplossing hier is de useState
hook uit de if statement te zetten en bovenaan de functie.
Hoe state herkennen?
Bij elk component moet je de volgende vragen stellen:
- Werd de data van buitenaf gegeven? Dan is het een prop.
- Blijft de date hetzelfde doorheen de levenscyclus van het component? Dan is het waarschijnlijk geen state.
- Is de data afgeleid van andere data in je component? Dan is het waarschijnlijk geen state.