Lifting Up State – ReactJS

Ik heb veel gepuzzeld om het Lifting Up State principe te doorgronden. Ik las het wel, maar het drong niet tot me door.

Om dat toch te bereiken heb ik besloten zelf deze pagina te maken. In het Nederlands. In het Engels is er genoeg over te lezen, maar de taal blijkt soms toch een barrière om het echt te laten landen.

Als ik het moet uitleggen aan anderen leer ik zelf nog meer dan mijn publiek. Omdat ik het dan wel MOET begrijpen. Dus dit is ook vooral een denkoefening voor mezelf, met als silverlining dat jullie er ook wat aan hebben.

Mijn initiele vraag om tot deze uitlegpagina te komen is:

Hoe ‘Lift-up State’ je twee levels omhoog in ReactJS?

Voor een opdracht liep ik tegen het volgende aan:

Ik wil de waarde van een status in een functie aanpassen en beschikbaar maken voor andere functies in je code, die op hetzelfde niveau bestaan, maar ook elders in de hiërarchie.

Analogie/Uitgangspunt

Om het aan mezelf uit te kunnen uitleggen heb ik de volgende analogie als uitgangspunt:

Stel er zijn 3 componenten Kwik, Kwek en Kwak.

Kwik is het aanspreekpunt van de drie is is hiërarchisch de ouder van Kwek en Kwak.

Component DonaldDuck is hiërarchisch de ouder van Component Kwik.

Persoonlijke status ‘slaapt’

In principe heeft elke Component, dus in dit geval, Kwek en Kwak, maar ook Kwik en Donald een persoonlijke status. Als Kwak bijvoorbeeld slaap krijgt en daarmee de status ‘slaapt’ aanneemt, is dat louter zijn probleem. Zolang hij die status niet aan anderen doorgeeft (kenbaar maakt) dat hij slaperig is kan niemand iets daarmee.

Statussen doorgeven

In principe kan Kwak er toe besluiten om tegen iedereen (dus Kwik, Kwek én Donald) te zeggen dat hij slaperig is. Dan weet iedereen dat. Maar stel nu dat hij toch weer wakkerder wordt. Dat verandert zijn eigen status in ‘wakker’. Maar zolang hij dat niet doorgeeft aan de anderen zullen ze in de veronderstelling blijven dat Kwak slaperig is. Kwak zal dat dus aan iedereen individueel moeten doorgeven.

Dat is in het geval van 4 componenten in totaal niet zo moeilijk. Je kunt ook zelf nog wel onthouden wie Kwak de status heeft doorgeven en het overzicht houden, maar dat wordt al snel onoverzichtelijk als er meerdere componenten zijn, en/of als er snel of veel status wisselingen zijn.

Om die moeilijkheid te omzeilen gebruiken we de hiërarchie en spreken af dat we statussen en wijzigingen daarop alleen doorgeven aan de component boven je. Dus in dit geval geeft Kwak zijn status door aan Kwik.

Kwik geeft los daarvan als hij wil zijn eigen status door aan Donald.

Beginnen bij het begin

Hooks zijn een nieuwe toevoeging in React 16.8 en verder. Het laat je state features van React gebruiken zonder gebruik te maken van classes met constructors. In plaats daarvan schrijf je functions of arrow-functions met hooks.

voorbeeld van
classses

voorbeeld van function

voorbeeld van
arrow-function

Ik kies ervoor om gebruik te maken van de laatste stand van zaken, dus in mijn voorbeeld maak ik gebruik van arrow-functions.

De opzet van losse componenten voor dit voorbeeld in ReactJS code:

De hiërarchie start zoals alle default React Apps met de component/function App in App.js. Deze wordt aangeroepen vanuit index.js.

In App roepen we Donald aan vanwaar uit ik het principe van React Hooks Lifting Up State verder ga uitleggen:

Als je meer wilt weten over de opbouw van een React App verwijs ik je naar https://github.com/facebook/create-react-app.

We gaan door in dit voorbeeld met Donald in Donald.js
Donald staat boven (is de Parent van) Kwik. We importeren Kwik in Donald, zodat we deze met <Kwik /> kunnen aanroepen:

Dan maken de aangeroepen Kwik aan in Kwik.js. Kwik, staat onder (is de Child van) Donald, maar staat boven (is de Parent van) Kwek en Kwak. Kwek en Kwak importeren we dan dus ook, zodat we ze kunnen aanroepen:

Dan maken we Kwek aan in Kwek.js. Kwek staat onder (is de Child van) Kwik.

en als laatste maken we Kwak in Kwak.js. Ook Kwak staat onder (is de Child van) Kwik.

Zoals je kunt zien heb ik de div’s een class meegegeven die correspondeert met hun positie in de hiërarchie zodat het een en ander ook makkelijk vorm te geven is in -in dit geval- index.css.

Kwak de status wakker meegeven

Kwak is wakker. Dat als status meegeven doe je door twee dingen toe te voegen:

Allereerst vertellen we React dat we de hook ‘useState’ gaan gebruiken die we ‘mood’ noemen. En in de functie roepen we die hook aan. Dat ziet er als volgt uit:

In regel 1 importeren we { useState } , die we in regel 4 aanroepen.

In de weer te geven tekst zelf hebben we zoals je in regel 7 ziet de status van ‘mood’ als variabele aangeroepen in JSX met {mood}

Status verandering! Kwak gaat slapen

Kwak blijft echter niet wakker. Na verloop van tijd verandert zijn status naar slapen. Laten we een knop maken waar we op kunnen klikken om uiteindelijk de status mee te kunnen veranderen:

Nu is er alleen een knop. Om ook wat te laten gebeuren als we op de knop klikken moeten we aangeven wat we willen laten gebeuren. In dit geval willen we de status veranderen van ‘is wakker’ naar ‘slapen’. In React hooks kunnen we gebruik maken van een ingebouwde functie die we aanroepen door een tweede argument te definiëren bij de useState die we hierboven hebben aangemaakt.
We veranderen de code

in

De conventie daarbij is [ x, setX ], dus [ status, setStatus ] of [ count, setCount ] of [ color, setColor ]. Het maakt feitelijk niet uit hoe je de status noemt, als je je maar houdt aan de conventie.

De eerste parameter bepaalt de naam van je state. En de tweede is de functie die de status kan ‘setten’ ofwel aanpassen.

Nu we de (in dit geval) setStatus hebben aangemaakt kunnen we hem ook aanroepen.
Dat doen we door een property mee te geven bij het aanmaken van de button. We voegen op de JSX manier een eventhandler toe en koppelen die aan de setStatus met een instructie qua wijziging:

Als je nu op de knop klikt wordt eventhandler onClick in regel 8 getriggerd die de (inline) naamloze arrow-functie aanroept die de state van mood verandert met de ingebouwde setMood functie naar “slapen”.

Omdat het straks wat makkelijker test, maken we gelijk nog een extra knop waarmee we de status weer op ‘wakker’ kunnen zetten zonder de browser te moeten refreshen.

Kwak heeft ook 10 munten op zak

Een component, in dit geval nog steeds Kwak kan meerdere states hebben. Bijvoorbeeld ‘wel of geen pet’, de kleur van zijn jasje (in dit geval altijd groen) of bijvoorbeeld de hoeveelheid geld op zak.
Elke losse status declareer je ook apart, dus de voorbeelden hierboven zou je zo declareren:

Bij colorjacket staat geen setColorjacket omdat de kleur van het jasje van de neefjes in Duckstad nooit verandert. En functies die we niet gebruiken moet je ook niet initiëren.

Component Kwak komt er als volgt uit te zien:

Om het af te maken voegen we een button toe waarmee we het zakgeld van Kwak per klik met 1 munt verhogen.

Statussen doorgeven

Kwek wil zeggen dat Kwak slaperig is. Maar hij mag die aanname niet doen. Dus zal hij de status van Kwak moeten opvragen. Strikt genomen kan dit rechtstreeks, maar omdat het zo kan zijn dat er veel meer broertjes en zusjes zijn die dat willen weten en we de vraag niet willen herhalen maken we gebruik van het principe Lifting up state ofwel:

Lifting up state: de status een niveau verhoogd in de hiërarchie brengen

Anders gezegd: Letterlijk de “status in de lift zetten”, op het knopje van een verdieping hoger drukken en zelf uitstappen.

We gaan dus zorgen dat Kwek de status bij Kwik kan opvragen en die daar dus beschikbaar maken, en we moeten zorgen dat Kwak zijn status naar Kwik brengt en de status dáár ook voortaan update. Kan je het nog volgen?

Hier gaan we:

Stap 1a: zorgen dat Kwak zijn status ‘mood’ naar Kwik brengt.

Allereerst halen we de state bij Kwak weg en declareren deze state bij zijn woordvoerder Kwik.

Kwak heeft nu zelf zijn eigen status niet meer in bezit. Als de status verandert, dan kan hij die dus niet bij zichzelf veranderen, maar alleen nog maar bij zijn woordvoerder Kwik.

Stap 1b: props voorbereiden

Kwik moet echter wel de mogelijkheid hebben om de status te veranderen van Kwak. Daarvoor geven we Kwik een functie in de vorm van een prop(erty) daarvoor, die Kwak dan kan aanroepen.

Daarvoor moeten we twee dingen doen. Allereerst moeten we zorgen dat kwik properties kan aannemen. Dit doen we simpel door ‘props’ als argument te declareren bij het aanmaken van het component:

We maken gebruik van de properties (props) van een component. Je kunt echter geen props ‘doorgeven’ van child- naar parent components (dus omhoog), maar je kunt wél functies als props doorgeven vanuit de child die de state aanpassen in de parent.


Lees deze zin gerust nog een paar keer. Het koste mij ook een behoorlijke tijd voor ik het daadwerkelijk snapte.

Stap 1c: state meegeven als prop aan de children

We kunnen nu de waarde van de state ‘mood’ doorgeven aan de Children door ze als prop mee te geven, zodat Kwak, maar ook Kwek, de state weten van de mood van Kwak.

De naam van de prop kies je zelf, maar wees duidelijk wat je er mee doorgeeft; het kan al snel verwarrend worden.

het gaat nu bv specifiek om de mood van Kwak….

Die kunnen we daar dan ook aanroepen door de prop.moodKwak als JSX in een element te zetten:

De buttons in regel 9-11 zijn even uitgeschakeld omdat die later nog herschreven zullen worden.

En het mooie is nu: voor Kwek geldt dat gelijk ook:

We hebben nu feitelijk aan ‘Lifting up State’ gedaan voor ‘mood’ van Kwak, alleen is het nog niet dynamisch.

Stap 2a: functie als property meegeven

We delen een state met andere componenten als we veranderingen daarin gelijk aan elkaar willen houden. We moeten de state dus weer aanpasbaar maken.

Wat we daarvoor moeten doen is -zoals gezegd- de functie om de status aan te passen changeMoodKwak={(newMood) => setMood(newMood)} bij de Parent Kwik als prop(erty) mee geven aan component <Kwak />:

Stap 2b: de functie aanroepen vanuit de child-component.

Nu kan deze functie aangeroepen worden in component Kwak als een prop als onClick-eventhandler van de desbetreffende button: <button onClick={() => props.changeMoodKwak("slaapt")}>Slapen</button>

Uiteraard kan voor de wijziging naar “is wakker” dezelfde functie worden aangeroepen op dezelfde manier. De child component ziet er nu als volgt uit:

Als er nu op de button ‘slapen’ of ‘wakker’ in de childcomponent wordt geklikt dan wordt de state dus via een function die als child property is aangemaakt in de parent uitgevoerd door de child en de state in de parent aangepast, zodat de parent en alle childcomponents waar de state aan mee wordt gegeven ook die status kennen/ kunnen gebruiken.

Lees ook deze paragraaf zeker een keer of 4. Het kan echt even duren voordat het je beklijft. Ga anders even wat anders doen, en lees het morgen nog eens een paar keer.

Stap 3: ook de andere state verhuizen

Kwek weet nu dat Kwak slaapt of wakker is. En hij kan dat ook aan Kwik doorgeven. Ook als Kwak dat verandert is hij daarvan op de hoogte, en Kwik dus ook. Dat geldt nog niet voor de hoeveelheid munten die Kwak heeft.

Laten we daar voor gaan zorgen.

Stap 3a status van child naar parent verhuizen:

Als eerste verhuizen we de status van de child component naar de parent component:

Omdat de states nu bij Kwak weg zijn gehaald is het niet meer gelijk duidelijk om wiens states het nu gaat. Het is dus verstandig om de states specifieker te benoemen:

const [moodKwak, setMoodKwak] = useState("is wakker"); const [coinsKwak, setCoinsKwak] = useState(10);

We kunnen gelijk deze status als prop meegeven in de aangeroepen children. En omdat het nu wel meer props worden zetten we ze even per prop apart op een regel voor het overzicht:

Stap 3b function prop aan child component aanroep toevoegen

Net als bij de mood maken we een prop aan met een zelfverzonnen key (die beschrijft wat het doet) met daarin een arrow functie die met de meegegeven value de setFunctie aanroept van de hook voor de state die aangepast moet worden:

Stap 3c de functie aanroepen

Vervolgens roepen we de zojuist gecreëerde function-property in de child component zelf aan.
In de child component moeten we alle {coins} vervangen door {props.coinsKwak}en de in de button arrow function moet setCoins(coins + 1) vervangen worden door props.changeCoinsKwak(props.coinsKwak + 1)

Dat ziet er als volgt uit:

Het resultaat kunnen we nu laten zien door in de beide child components Kwek en Kwak én in de parent component Kwik de state te tonen:

Kwik:
Kwak:
Kwek
Het resultaat tot nu toe!

We hebben nu daadwerkelijk ‘Lifting up State’ gerealiseerd voor ‘mood’ en ‘coins’ van Kwak!

Maar we zijn er nog niet…….

De state nóg een niveau hoger tillen

Nu komen we tot de kern van de zaak. We gaan de states van Kwak van Kwik naar Donald tillen. Kwik wordt daarmee de child component die door Donald als parent component wordt aangeroepen met de juiste props.

Stap 1 Lets go!

De state van Kwik naar Donald brengen:

Stap 2 properties meegeven aan de child component

Dan de properties vanuit de parent component mee geven aan de child component Kwik:

Stap 3 aanpassingen in de child component zelf

De meeste aanpassingen moeten in Kwik gemaakt worden:

elke {moodKwak} moet vervangen worden door {props.moodKwak}

elke {coinsKwak} moet vervangen worden door {props.coinsKwak}

arrow function {(newMood) => setMoodKwak(newMood)} moet vervangen worden door {props.changeMoodKwak}

arrow function {(newValue) => setCoinsKwak(newValue)}moet vervangen worden door {props.changeCoinsKwak}

Daarnaast heb ik het tekstueel aangepast zodat het ook klopt.

Dat ziet er in totaal als volgt uit:

We’ve made it!
We hebben een dubbele
Lifting Up State
gerealiseerd!

Zowel Parent, Child als Grandchild delen nu de states van Kwak!

En de states van Kwik en Kwek dan?

Precies! Goede vraag. Want je kan je indenken dat als we dat op deze manier moeten doen dat het al heel snel erg onoverzichtelijk wordt.

Voor je het weet ben je child components zoals hieronder aan het aanroepen:

Dat moeten we natuurlijk absoluut niet willen. We kunnen de verschillende states van de verschillende components in een Array opslaan waar binnen we bij change of state de desbetreffende value aanpassen.

Voor het aanpassen van deze data moeten er functies geschreven worden die kunnen worden aangeroepen en ook moet deze data doorgegeven worden aan de child components.

Met standaard JavaScript kennis en de inmiddels vergaarde ReactJS kennis zou je dit nu moeten kunnen realiseren.

Het totaalplaatje heb ik hier in een codesandbox.io gezet. De weg hiernaar toe zal ik de komende tijd stap voor stap beschrijven.

Ik hoop met deze oefening je duidelijk uitgelegd te hebben hoe het Lifitin Up State principe werkt. Ik heb er zelf in ieder geval veel van geleerd, en ik begrijp het nu ook echt, dus die missie is geslaagd.

Ik wens je succes met je eigen project. Als je vragen hebt kun je ze altijd stellen!