Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Maak een React applicatie die bestaat uit minstens 4 componenten. Geef de applicatie de naam "routes-en-context".
Routing
Voorzie een hoofdmenu met navigatiebalk met als inhoud “Home”, “About”, “Games”, “Contact”. Bouw react routing in zodat bij het navigeren naar één van deze opties de inhoud van de gekozen pagina verschijnt en de juiste url gebruikt wordt.
Op de inhoud van de verschillende pagina’s toon je een tijdelijke inhoud (lorem ipsum). Maak wel duidelijk welke pagina wordt getoond.
Op de games pagina geef je de keuze uit 4 games (kies zelf welke). Kies zelf op welke manier je de keuze laat maken (drop down, button per game, ...).
Bij het kiezen van een game, moet je ervoor zorgen dat de gebruiker surft naar een pagina met de naam van de game in de url.
Context
Voor het gebruik van de app, willen we een onderscheid maken tussen het soort gebruikers: beginners en experts. Heel veel toekomstige instellingen willen we in alle pagina’s van de applicatie laten afhangen van deze instelling. Maak daarom gebruik van de Context api om deze instelling te kunnen instellen. Overal in de applicatie moet de gebruiker kunnen switchen tussen beginner en expert.
Voorzie 2 verschillende backgrounds, die door alle pagina’s gebruikt wordt en die wordt aangepast bij de keuze beginner en expert. Rechts bovenaan moet een aanduiding staan of de applicatie in beginners of in experts mode actief is.
Zoals de cursusnaam zegt gaan we in dit opleidingsonderdeel dieper ingaan op web frameworks. We gaan voornamelijk kijken naar het meest gebruikte Single Page Applications framework namelijk React.js (door facebook). Er zijn nog vele andere SPA-frameworks zoals Angular (door Google) en Vue.js. Deze frameworks laten het toe om eenvoudig uitgebreidere applicaties te maken dan met gewone JavaScript en jQuery.
Voordat we verder gaan met SPAs, gaan we even terug in de tijd voordat SPAs bestonden. In het verleden werden pagina's nog volledig opgebouwd op de server zelf. Zo had je bijvoorbeeld PHP of node.js in combinatie met Express en een view framework (zie webontwikkeling). In deze traditionele aanpak bezocht de gebruiker een URL om een pagina in te laden en werd het opbouwen van de pagina voornamelijk op de server zelf gedaan. Elke nieuwe pagina die de gebruiker wou bezoeken had dus ook een nieuwe GET request nodig. JavaScript werd toen voornamelijk gebruikt voor simpele animaties en interacties of geavanceerde styling die niet met CSS alleen te doen was. Hiervoor was de JavaScript library jQuery uiterst geschikt.
Bij single page applicaties gebeurt dit op een hele andere manier. De focus wordt verlegd van de server naar de client. De gebruiker bezoekt maar 1 URL en krijgt maar 1 HTML-bestand terug met bijbehorende JavaScript files. Nadat deze allemaal ingeladen zijn wordt de pagina getoond aan de gebruiker en worden alle andere pagina's volledig opgebouwd vanuit JavaScript. Alles voor het tonen van pagina's zit dus volledig vervat in die gedownloade JavaScript bestanden. Uiteraard hebben deze pagina's nog externe data nodig. Deze worden volledig via API calls (via AJAX) vanuit de client naar een server uitgevoerd. Als je bijvoorbeeld een lijst van boeken zou willen tonen vanuit je SPA, zal deze een API call doen naar de boeken API en zal het renderen (opbouwen) van de pagina volledig in je browser verlopen via JavaScript.
Tot nu toe hebben we met JSX telkens maar 1 ding per keer getoond. Vaak wordt er gebruik gemaakt van arrays en deze kunnen we niet uitdrukken in 1 HTML-element. We willen dus een HTML-element renderen per element van de array. Dit gaan we doen aan de hand van de map
functie die elk element van de array zal mappen naar een HTML-element.
We plaatsen nu een array bovenaan ons App.tsx
bestand:
Dit is een eenvoudige array van strings met 3 computer spelletjes in.
De map functie zet een lijst van elementen om naar een lijst van nieuwe elementen. Hier zetten we eigenlijk de lijst van strings om naar een lijst van <div>
elementen met daarin de naam van de persoon van de array.
Omdat React gebruik maakt van geoptimaliseerde technieken voor het tonen van lijsten moet je altijd bij de elementen in de map
constructie een property key
plaatsen zodat deze weten of een element moet gerenderd worden als de array wijzigt. Omdat we hier zelf geen ids hebben voor de personen gebruiken we hier even de naam zelf als key.
We kunnen hier ook gebruik maken van arrays van objecten. Zoals hieronder wordt getoond, als het object dan een unieke ID heeft kan je deze gebruiken voor de key.
en dan kan je deze array ook tonen in je JSX code als volgt:
Tot nu toe zijn onze elementen nog geen hoogvliegers op vlak van styling. Hier zullen we in een verder deel zeker verbeteren bij het toevoegen van stylesheets.
Voor deze cursus zullen we Visual Studio Code gebruiken als editor. Dit is een volledig geïntegreerde editor met ingebouwde terminal. Dit is ook de meest populaire keuze bij web ontwikkelaars. Zorg ervoor dat je de laatste nieuwe versie van vscode hebt.
Daarnaast is het ook de bedoeling dat je git
hebt geïnstalleerd en git bash
hebt ingesteld als default terminal in visual studio code. Op een mac of linux machine is dit automatisch bash
dus je hoeft hier niets te veranderen.
Heb je Windows Subsystem Linux (WSL) geïnstalleerd mag je uiteraard deze ook gebruiken als default bash op Windows 10
Voordat we kunnen beginnen moeten we ook Node en NPM hebben geïnstalleerd op onze machine. We gebruiken deze om packages te installeren en onze applicatie te kunnen uitvoeren. Je kan nakijken welke versie van node je hebt geinstalleerd door het commando
en
uit te voeren.
Zorg ervoor dat je alijd de laatste nieuwe LTS versie hebt geinstalleerd van Node.
Componenten zijn de bouwblokken van elke React applicatie. Tot nu toe hebben we altijd gebruik gemaakt van 1 component: de App
component. We zouden kunnen blijven werken met 1 component en daar alles in plaatsen, maar zoals alle software systemen is dit geen goed idee. We willen de applicatie opsplitsen in kleine herbruikbare blokjes. Deze herbruikbare blokjes noemen we in react: Componenten.
We gaan even terug naar onze lijst applicatie kijken. We gaan hier onze eerste opsplitsing maken in een nieuw component: de List
component.
Je ziet hier dat we gewoon terug een nieuwe functie aanmaken die een stuk van de JSX-code teruggeeft als return waarde. We hebben deze gewoon verplaatst van het App
component naar de List
component. In react noemen we een functie die JSX-code teruggeeft een component.
Omdat we deze code hebben verplaatst naar de nieuwe component moeten we ook nog aangeven in onze App
component dat we deze component willen tonen. Dit doen we aan de hand van de <ComponentName/>
syntax. Je merkt hier op dat we hier blijven werken met de HTML-achtige syntax. Het lijkt een beetje op een eigen HTML tag hebben aangemaakt.
Heel gelijkaardig kunnen we nu ook de header als een eigen component gaan maken als we dit willen.
en dan passen we de App
component verder aan zodat deze ook de Header component zal gebruiken.
In javascript zijn er meerdere manieren om een functie aan te maken. Als je door het internet zoekt naar voorbeeld code zal je ongetwijfeld ook de function syntax tegen komen. Deze kan in ons geval meestal exact gebruikt worden als vervanging van de arrow notatie. Zo kan het Header
Component ook geschreven worden als
We gaan nu eens kijken hoe we onze applicatie iets interactiever kunnen maken door de hand van invoer velden en handler functies. We gaan nu een nieuw component introduceren genaamd InputView
Deze gaat de volgende invoer velden bevatten:
Hier hebben we 1 text invoer veld voor de naam van de film en een numeriek invoerveld voor het jaartal. We zetten hier een maximum 2021 en een minimum van 1900. Tot nu toe wordt er nog niets gedaan met deze invoervelden.
Om hier iets mee te doen gaan we een functie aanmaken om iets te doen als er iets ingetypt wordt in het invoerveld.
Je compiler zal hier direct een waarschuwing over geven dat event nog geen type heeft. TypeScript kan hier niet automatisch afleiden welk type dit event heeft. We zullen dit zo snel mogelijk oplossen.
Als we nu deze event listener functie willen gebruiken moeten we deze toevoegen als property van de input tag.
We kunnen de interface van de handleChange functie hierboven te weten komen door onze muis over de onChange property te plaatsen. Je krijgt dan iets gelijkaardig aan:
We kunnen nu het type van de handleChange functie aanpassen om de waarschuwing van hierboven op te lossen.
We kunnen op dezelfde manier een functie maken om een button click af te handelen:
Het type van de handleClick komen we op dezelfde manier te weten.
Op dit moment hebben we nog altijd onze data die we gebruiken voor onze lijst in een globale variabele gestoken in de huidige applicatie. Het is uiteraard niet houdbaar om alles in globale variabelen te steken en deze zo te delen onder verschillende componenten. De juiste manier om dit te doen in React is deze lijst of andere variabelen door te geven als props of properties. We geven dus informatie door van het ene component naar de andere.
Voordat we props voor de eerste keer gaan gebruiken in onze applicatie gaan we eerst onze lijst van games verplaatsen naar het App
component zelf.
Nu geeft uiteraard de List component een foutmelding omdat de games variabele niet meer globaal staat en dit component dus geen toegang heeft tot deze lijst. We gaan dit oplossen door de games
variabele door te geven aan de List
component aan de hand van een react prop (property). Het lijkt alsof we een nieuwe html attribuut aanmaken voor de List
component.
De properties van de List
component moeten dan nog wel beschreven worden aan de hand van een interface. We geven deze interface meestal de naam van het component gevolgd door Props.
Als je deze interface dan wil gebruiken in je component kan je deze als volgt gebruiken.
Als je niet elke keer props.games wil typen om de games property aan te spreken kan je ook gebruikmaken van destructuring om de props al uit te pakken in aparte variabelen. Dit doe je op de volgende manier:
en dan kan je de games variabele gebruiken net zoals ervoor. Als je niet voor elk component een interface wil aanmaken kan je ook de interface in de functie definitie zelf beschrijven
Nu kunnen we onze kennis over props gaan gebruiken om onze List
component nog verder op te splitsen in kleinere componenten als we dit willen. Zo kunnen we een ListItem
introduceren die verantwoordelijk is voor het tonen van 1 item van de lijst.
en kunnen we het List
component nog meer vereenvoudigen:
Het gebeurt vaak dat props doorheen meerdere componenten worden doorgegeven. Indien je componenten structuur dieper en dieper wordt, wordt het ook altijd maar minder praktisch om dingen te gaan blijven doorgeven aan de hand van properties. We zien later nog hoe we dit probleem kunnen oplossen.
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. State wordt gebruikt om informatie bij te houden en deze aan te passen over de looptijd van je applicatie.
We zullen dat laatste 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.
We zouden foutief kunnen veronderstellen dat we dit probleem kunnen oplossen door een variabele te maken waar we de tekst van de input in kunnen opslagen. Dit zou er ongeveer als volgt kunnen uitzien.
Als je dit zou uitproberen dan ga je merken dat er niets gebeurt als je het input veld aanpast. Dit is omdat het rendering mechanisme hier niet wordt getriggerd. Dit wil zeggen dat het component niet opnieuw getekend zal worden, en dus de naam niet aangepast zal worden op je scherm.
Hoe lossen we het dan wel correct op? De oplossing hierboven is in principe niet ver van de juiste oplossing. We gaan hier in plaats van een variabele, een state aanmaken waar we de name in kunnen opslagen. Als deze state aangepast wordt zal het component wel opnieuw getekend worden (gerendered). We gaan hier gebruik maken van de useState
hook om deze state aan te maken.
De useState
functie heeft als argument een initiële state. Dit is de start waarde die de state zal krijgen als het component voor de eerste keer gerendered 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 aanpassen. We geven ook aan welk type onze state zal bevatten door <string>
mee te geven aan de useState functie.
Als we nu ons voorbeeld weer aanpassen komen we op de volgende code
Als de gebruiker nu in het invoerveld iets ingeeft zal de handleEvent functie aangeroepen worden. Daarin wordt de setName
methode opgeroepen die de waarde van de name
state aanpast. Nadat deze is aangepast wordt het component terug gerendered. Dit wil zeggen dat de code van dit component terug uitgevoerd wordt, maar op dat moment zal de state met de name
de nieuwe waarde bevatten en dus zal dit ook getoond worden aan de gebruiker.
Als je graag wil weten wanneer je component terug opnieuw wordt gerendered kan je in de code van de InputView
component een console.log() statement zetten die afprint dat het component is gerenderd. Zo kan je in je chrome developer tools zien op welk moment het component terug opnieuw gerenderd wordt.
Voor de volledigheid passen we nu ook nog heel de input component aan zodat alle inputvelden event handlers hebben
We hebben hier er ook voor gezorgd dat alle invoer velden nu ook de value attribuut gebruiken. Deze wordt gezet op de huidige waarde van de states. 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.
Tot nu toe hebben wel alles in hetzelfde bestand gezet. Het App.tsx
bestand waar momenteel alle componenten in zitten. Dit is natuurlijk geen best practice, zeker bij applicaties die complex zijn. Daarom gaan we nu zoveel mogelijk elk component in een apart bestand zetten, zodat het gemakkelijker wordt het juiste component te vinden in de bestandsstructuur. We gaan hier gebruik maken van javascript modules in aparte bestanden. React heeft geen vaste file structuur, wat je vaak wel hebt in andere frameworks. Dit maakt het soms wat ingewikkeld als je in een nieuw project stapt met bestaande code base.
Het eerste wat we kunnen doen is gewoon al onze componenten in aparte bestanden zetten. Dit zal de projectstructuur al zeker ten goede komen. We denken terug aan de export syntax uit vorige cursussen om onze componenten beschikbaar te maken vanuit andere bestanden.
We bekomen een structuur die er als volgt uitziet:
We bekijken even de List
component om te bezien hoe zo'n component bestand er kan uitzien.
We zorgen met de laatste lijn dat we de List component exporteren zodat andere bestanden die kunnen importeren. Je ziet hierboven ook hoe dat importeren in zijn werk zal gaan. Je ziet dat hier het keyword default bij de export wordt gezet. Dit is nodig zodat je de componenten kan importeren op de volgende manier.
Soms zie je ook de syntax met { }
in de import. Dit wil zeggen dat er meerdere functies, variabelen,... worden geëxporteerd in een bestand. Een goed voorbeeld hiervan is een nieuw bestand dat we hebben aangemaakt voor alle types in te beschrijven. We hebben alle types die met het model te maken hebben in 1 bestand gezet: types.ts
. Op dit moment staat daar 1 interface in, maar het is uiteraard niet ondenkbaar dat daar meerdere interfaces zullen zitten. We exporteren dus elke interface apart.
Daarna moeten we deze interfaces dan apart importeren met de volgende syntax
Je zou er ook voor kunnen kiezen de componenten op dezelfde manier te exporteren. Dit is helemaal afhankelijk van je persoonlijke voorkeur.
Bij het groeien van je project zullen ook je componenten alsmaar complexer worden. Er worden styles toegevoegd en tests. Je zou eventueel de vorige structuur blijven volgen en deze bestanden gewoonweg naast de component bestanden plaatsen. Vaak worden deze bestanden ook nog in een folder components geplaatst.
Je ziet dat dit op lange termijn niet echt houdbaar is. Op deze manier vinden we uiteindelijk nooit meer de componenten die we nodig hebben omdat die in een soep van bestanden terecht is gekomen. Daarom verkiezen we bij grotere projecten ook een directory per component.
Het noemen van deze bestanden is uiteraard totaal afhankelijk van allerlei aspecten. Zo kan je bijvoorbeeld een ander test framework gebruiken. Zo zal je ook later zien dat vaak in React CSS in typescript zelf wordt gedaan aan de hand van styled components. Dan heb je uiteraard geen css bestanden maar gewoon typescript bestanden. Het voordeel van deze manier van werking met folders is dat je tijdens het ontwikkelen van je applicatie de niet relevante componenten kan dichtklappen en dus je niet meer verzeild geraakt in een berg van bestanden.
Naast tests en css bestanden kan je ook bepaalde utility functies die relevant zijn voor dat component in aparte bestanden plaatsen. Een voorbeeld hiervan zijn hooks en utility functies.
We hebben ook een apart bestand gemaakt voor het ListItem
component. Dit is eigenlijk een onderdeel van het List
component want dit kan eigenlijk niet gebruikt worden zonder zijn ouder component. Meestal worden zo'n componenten samen bij zijn ouder component in de folder geplaatst.
Je zou eventueel dit component ook nog in een aparte folder onder List
kunnen plaatsen. Maar pas altijd op dat je niet te veel folders gaat nesten in elkaar. De meeste richtlijnen geven de raad om nooit dieper dan 2 folders diep te nesten.
Uiteraard zijn er nog veel andere manieren om je project te structureren. En je zal nog andere manieren tegenkomen naarmate je werkt op grotere projecten.
Je zal al wel opgemerkt hebben tijdens het verplaatsen van alle components dat je structuur van je imports altijd met relatieve paden werken. Je herkent dit door imports zoals
je ziet dat je doorheen verschillende niveaus moet gaan aan de hand van ../
dit geeft aan dat het bestand een folder niveau hoger staat. Dit is niet zo'n handige manier van werken, zeker als je dingen begint te verplaatsen kan dit nogal onoverzichtelijk worden. Gelukkig is hier een oplossing voor. Open je tsconfig.json
bestand en voeg de volgende lijn toe aan je compilerOptions
dit bestand zal er dan ongeveer als volgt uitzien
Herstart hierna visual studio code en hij zal dan automatisch oppikken dat je met absolute paden wil werken als je iets automatisch importeert.
In deze cursus gebruiken we de create-react-app
om onze eerste applicatie op te zetten. Het is een starter kit aangeboden door Facebook om snel een React applicatie aan te maken. Het vereist geen configuratie en wordt aangeraden voor beginnende React gebruikers.
Voordat we kunnen werken met de create-react-app
kijk je best ook nog na of je npx
hebt geinstalleerd op jouw machine. Normaal wordt deze mee geinstalleerd met node
en npm
Je kan dit nakijken door
te doen.
npx
laat toe om tooling te gebruiken zonder die eerst te installeren op jouw machine. Zo heb je altijd de laatste nieuwe versie van deze tool. Je zou eventueel deze ook kunnen installeren op jouw machine met npm install -g
maar dan zou je zelf verantwoordelijk zijn voor het updaten van deze tool.
Nu we dit klaar hebben staan kunnen we eindelijk onze eerste react applicatie aanmaken door
uit te voeren.
Let op dat je het stuk --template typescript
niet vergeet. We werken in deze cursus met typescript. Anders wordt er een react applicatie gemaakt in javascript.
We kunnen nu deze folder open doen in Visual Studio Code en eens naar de inhoud van het project kijken.
Hier merk je de volgende bestanden en folders in op:
node_modules deze folder bevat alle packages die werden geïnstalleerd bij het runnen van het create-react-app
commando. Alle packages die je zelf installeert met npm install
zullen ook in deze folder terecht komen.
package.json beschrijft welke dependencies (packages) er moeten geïnstalleerd worden voor deze applicatie uit te kunnen voeren. Het npm install
commando zal deze dependencies dan installeren in de node_modules
folder.
.gitignore deze file bevat alle bestanden en folders die niet mogen toegevoegd worden aan je git repository. node_modules is hiervan een goed voorbeeld. Deze folder moet nooit mee in git geplaatst worden want deze folder met alle packages wordt aangemaakt door npm install
uit te voeren.
src bevat de eigenlijke bestanden die rechtstreeks te maken hebben met react. Hier zal jouw broncode in komen. Het belangrijkste bestand op dit moment is het App.tsx
bestand. Daar zullen we eerst onze eerste react code schrijven. Later zullen we dit uiteraard opsplitsen in verschillende bestanden.
public bevat het html bestand waarin de react applicatie zal getoond worden. Hier moet je meestal niets voor aanpassen want alle visuele componenten worden in react zelf gerendered. Deze folder bevat ook andere dingen zoals de favicon voor als je het icoontje wil veranderen van de web applicatie.
Je kan nu de applicatie opstarten door
uit te voeren in je terminal venster. Als alles in orde is zal je browser automatisch openen op de eerste en enige pagina van jouw webapplicatie:
De reden dat je npm start
kan uitvoeren en dat al deze acties dan automatisch gebeuren hebben we te danken aan het volgende stuk in het package.json
bestand:
We gaan voorlopig nog niet verder op deze commando's in.
Alle React applicaties zijn gebouwd uit verschillende React componenten. Componenten worden uitgedrukt in functies. Ze accepteren willekeurige invoerwaarden (deze noemen we “props”) en geven React elementen terug die beschrijven wat er op het scherm moet verschijnen. Met componenten splits je de UI in onafhankelijke, herbruikbare delen.
Het eerste component waar je altijd mee in aanraking zal komen kan je vinden in het src/App.tsx
bestand. Deze zal er een beetje zoals het onderstaand bestand uitzien:
Tijdens het genereren van de code via de create-react-app is er gebruik gemaakt van het function keyword. Een component kan ook evengoed geschreven worden met de arrow notatie voor functies. Je mag zelf kiezen welke notatie je gebruikt. Een component met de arrow notatie zal er als volgt uit zien:
We zullen nu even het component herleiden tot de essentie door wat overbodige boilerplate code weg te halen. Zo bekomen we tot een heel eenvoudige Hello world
applicatie:
We zien hier een aantal dingen:
We zien hier eigenlijk gewoon een eenvoudige functie. We noemen deze in React geen functie maar een function component.
Deze function component heeft tot nu toe geen parameters maar in verdere delen zullen we zien dat we ook parameters kunnen meegeven aan componenten aan de hand van properties (of props).
Deze functie returned een HTML achtige syntax terug. Deze HTML achtige syntax noemen we JSX en zal het mogelijk maken om JavaScript en HTML eenvoudig te combineren.
Uiteraard kunnen we in een functional component ook meer doen dan HTML renderen. Boven de return kunnen we variabelen declareren en complexere logica gaan toevoegen aan onze componenten.
Willen we nu deze title
gaan gebruiken in onze HTML achtige syntax gaan we gebruik maken van JSX. Zo kunnen we de output gaan mengen met javascript code. Als we de title
variabele willen renderen tussen de h1 tags kunnen we deze als volgt toevoegen
Je kan naast variabelen ook eender welke expressie tussen deze {} haakjes plaatsen. Bijvoorbeeld:
of
en je kan zelfs functies uitvoeren en het resultaat hiervan laten zien:
Tot nu hebben we props en state gezien om informatie bij te houden en door te geven tussen verschillende componenten. Maar we missen nog 1 belangrijk concept om communicatie tussen componenten te laten gebeuren. Op dit moment kunnen we onze informatie invoeren in onze invoer velden in de InputView, maar
maar kunnen we nog op geen enkele manier deze informatie doorgeven aan het App
component die onze lijst bevat.
Props kunnen alleen informatie naar beneden doorgeven. Dus we moeten zelf iets maken dat dit wel mogelijk maakt. Dit noemen we een callback handler (A). Deze geven we door aan onze component aan de hand van props (B)
Voordat we deze callback functie kunnen gebruiken in onze InputView
moeten we deze toevoegen aan de properties van dat component, dus we maken weer een interface voor dit component.
Vervolgens voegen we deze properties toe aan ons InputView
component zelf.
Nu kunnen we in onze handler van de button click deze onAdd
callback handler oproepen. We maken hier een Game
object aan gebruik makende van de waarden die in de state zijn opgeslagen. Als de gebruiker nu op de add knop klikt dan zal de onAdd callback handler aangeroepen worden die werd meegegeven uit het App
component.
Om nu onze applicatie volledig functioneel te maken willen we nu er voor zorgen dat het game in kwestie wordt toegevoegd aan de lijst die zich bevindt in het App
component. Momenteel wordt de lijst van games nog bijgehouden in een variabele, omdat deze nu interactief gaat worden en dus gaat veranderen over de tijd is het de bedoeling om ook deze te gaan bijhouden in een state.
Je eerste gevoel is waarschijnlijk om rechtstreeks in de games
array je nieuwe game te gaan pushen.
Maar zoals we al in het onderwerp over State hebben behandeld mag je NOOIT iets rechtstreeks aanpassen aan een state variabele. Je moet hier de setGames
functie gebruiken die je hebt aangemaakt via de useState
hook. We gebruiken hier de spread operator (drie puntjes) om de inhoud van de games array te nemen en daar achter een element te plakken.
Als je nu op de add knop drukt dan wordt de games state aangepast en dan zal ook de List
component opnieuw gerendered worden. Dit komt omdat de games
daar worden meegegeven als props. Als de props updaten van een component dan wordt het component zelf ook opnieuw gerendered.
Momenteel hebben we nog wel geen id aan deze game gegeven want op het moment dat we het formulier invulden wisten we nog niet wat de id ging worden. We kunnen hier het maximum berekenen van alle id
waarden van alle games.
Soms willen we dat onze componenten iets doen dat niet binnen het takenpakket van een component hoort. Componenten moeten in principe alleen maar bezig zijn met het renderen van de UI. Maar Componenten moeten vaak wel bepaalde acties gaan uirvoeren die iets veroorzaken in de buitenwereld. Zo'n actie noemen we in React een side-effect. Een aantal voorbeelden van een side-effect kunnen zijn:
Het zetten van een title aan de hand van document.title
Het gebruiken van timers met setInterval
en setTimeout
Het meten van de hoogte, breedte of positie van bepaalde elementen in de DOM
Het zetten of lezen van waarden uit de local storage
Data inlezen of schrijven naar externe web services.
Dit zijn allemaal dingen die we nooit rechtstreeks gaan doen in onze component code.
Als eerste voorbeeld van wanneer we side-effects moeten gebruiken gaan we eens zien naar het lezen en schrijven uit local storage. Local storage laat het toen bepaalde configuraties of user settings bij te houden in de browser van de gebruiker.
We gaan er nu voor zorgen dat alles wat er in het name veld wordt ingegeven wordt opgeslagen in de localstorage van de browser, zodat deze waarde volgende keer deze pagina wordt aangeroepen terug wordt getoond aan de gebruiker. We willen dus een bepaald stuk code laten lopen elke keer de name
veranderd. Op het eerste gezicht zouden we het als volgt kunnen doen:
en kunnen we deze waarde uitlezen bij het zetten van de default waarde van onze state:
Merk op dat we hier de ?? operator gebruiken van typescript. Dit maakt het mogelijk om een default waarde mee te geven als het deel voor de ?? undefined of null is.
Deze code zal op zich wel werken, maar er is 1 groot nadeel. We kunnen de functie handleNameChange
nergens anders meer hergebruiken. Dus elke keer deze functie wordt aangeroepen zal de localstorage aangepast worden. We willen dit dus loskoppelen van elkaar. Hiervoor gebruiken we de useEffect
hook om de side-effect te triggeren telkens de name
state wordt aangepast:
De useEffect
hook neemt twee argumenten aan: het eerste is een functie die de side-effect uitvoert. In dit geval is de side effect het opslaan van de name
in de localStorage. Het tweede argument is een array van dependencies. Dit is een lijst van variabelen (of states of props) die wanneer ze veranderen de side-effect zullen triggeren. In ons geval zal dus elke keer name
aangepast wordt, de localstorage waarde geüpdatet worden.
Twee belangrijke dingen die je zeker moet weten:
Als je de array argument weglaat dan zal de side-effect elke keer de component gerendered wordt opnieuw uitgevoerd worden.
Als je een lege array meegeeft dan zal de side-effect enkel getriggered worden als het component voor de eerste keer gerendered wordt. Dit kan handig zijn voor het inlezen van data van een API
Wil je bijvoorbeeld de title van de pagina updaten op het moment dat de component voor de eerste keer gerendered wordt kan je dit doen aan de hand van:
Hoewel het lukt willen we nooit document.title
aanpassen vanuit de code van het component zelf.
Tot nu toe hebben we de data rechtstreeks van de applicatie code zelf gebruikt. In de meeste dynamische webapplicaties gebruik je een externe web service met een of andere dataopslag (SQL-database, files, ...). In dit deel gaan we onze webapplicatie aanpassen zodat die gebruik maakt van de Movie Web Service.
Bij het oproepen van een web service krijg je nooit direct het antwoord terug. Er zal altijd een kleine vertraging opzitten omdat deze via het internet zal verlopen, daarom moet deze asynchroon gebeuren. Soms wordt zo'n web service call ook een AJAX call genoemd.
Er zijn veel verschillende libraries om AXJAX calls te maken in React applicaties. Hieronder vindt je een lijst van veel gebruikte libraries:
Axios
Fetch (ingebouwd in elke browser)
Superagent
React-axios
Use-http
React-request
We kunnen hier in principe eender welke library gebruiken maar we gaan Fetch gebruiken omdat deze geen externe library nodig heeft en ingebouwd is in elke moderne browser.
Omdat onze games niet meer in het begin beschikbaar zijn in onze applicatie zorgen we er nu voor dat de state op een lege array wordt gezet bij het initializeren van het App
component.
We gaan nu even terug naar wat we gezien hebben in het onderdeel side-effects
. We hebben daar de useEffect
hook gezien en we hebben daar afgesproken dat alle dingen die bestaat uit communicatie met een externe service via effects moet gebeuren. We hebben daar ook gezien dat we altijd een lege array mee moeten geven als we deze effect maar 1 keer willen laten uitvoeren bij de eerste render van het component.
Je ziet hier dat we voorlopig toch gewoon onze initiële array hier opvullen met onze voorbeeld data. We vullen deze wel pas op na 1 seconde. Dit doen we aan de hand van de javascript setTimeout
functie. We doen dit omdat we soms een API call willen nabootsen om dingen eerst te testen of omdat de API op dat moment nog niet klaar is. De reden dat we pas de setGame
gebruiken na 1 seconde is omdat we een tragere netwerk request willen naboosten die ongeveer 1 seconde duurt.
Als je nu naar onze webapplicatie naar de /list
path zou gaan met je browser zal je zien dat de lijst eerst leeg zal zijn en dan na een seconde gevuld zal worden. Dit kan voor de gebruiker een beetje raar zijn, daarom gaan we nu een extra updating
state invoeren zodat we een loading indicator kunnen laten zien aan de gebruiker terwijl de data laadt.
Bij aanvang van onze nagemaakte API call zetten we updating
op true en nadat de data is aangepast dan zetten we deze terug op false.
We kunnen nu nakijken of updating
op true staat voordat we de rest van onze layout gaan renderen. Als updating
true is dan tonen we een div
met de text 'Loading data' en anders tonen we gewoon onze Switch
van onze routing.
We kunnen deze loading text ook nog vervangen met een loading spinner aan de hand van wat css magie:
en in onze App.module.css
maken we dan deze classes aan zodat we deze loader kunnen laten draaien aan de hand van keyframes
We gaan nu de eigenlijke web service aanspreken om de games op te halen. We maken een aparte functie voor het ophalen van games van de API. De getGames
functie duiden we aan als async
functie omdat we er gebruik gaan maken van async/await
omdat er asynchrone code in deze functie staat (het ophalen van de games via de API).
Net als bij de nagebootste call van hiervoor zetten we eerst de updating
state eerst op true. Vervolgens doen we de fetch
call naar http://localhost:3001/games
met de GET methode. Deze fetch methode geeft de response terug als stream, dus deze moet nog helemaal omgezet worden naar json aan de hand van de response.json()
methode. Beide fetch
en json
zijn functies die een promise terug geven dus hiervoor kan het await
keyword geplaatst worden zodat er gewacht wordt tot de promise afgehandeld is. Als we het resultaat volledig hebben gebruiken we de setGames
methode om de state van de games aan te passen.
Nu kunnen we deze methode aanroepen in onze effect
Met Fetch kunnen we uiteraard ook POST requests doen naast GET requests. Zo kunnen we heel eenvoudig onze handleAdd
functie aanpassen zodat die nu een Game toevoegt aan de hand van de POST op /games
. Net als bij de getGames
zetten we eerst setUpdating
op true zodat de loader getoond wordt. We geven nu een method: POST
mee aan de fetch functie en plaatsen we het game object in de body. We moeten het wel eerst naar string omzetten. De POST op /games
geeft een json terug met alle games in zodat we deze response kunnen gebruiken om onze games
state te updaten.
In de opdrachtbeschrijving “Opdracht Componenten” werd gevraagd een aantal componenten in je web applicatie op te nemen, waaronder een array van dieren.
In deze opdracht wordt er gevraagd een uitbreidere web applicatie te maken waarbij je gebruik maakt van een array van dieren.
Opdracht 1:
Maak een nieuwe react web applicatie aan genaamd “animal-farm”
Opdracht 2:
Maak een uitbreiding van de array van dieren met het bijpassende geluid dat een dier maakt.
Zoek zelf naar geluidscomponenten van dierengeluiden.
Voeg een link naar die geluiden toe in de array van de dieren.
Opdracht 3:
Zorg ervoor dat de gebruiker een dier kan selecteren en dat vervolgens bij het aanklikken van “maak geluid” het juiste geluid wordt afgespeeld.
Maak een React web applicatie aan. Geef jouw web applicatie de naam "basic-componenten". Voeg vervolgens enkele “basic” componenten toe, die beschreven staan in volgende opdrachten:
Opdracht 1:
Voeg een component toe die een title op het scherm brengt “Welkom in het dierenparadijs”
Opdracht 2:
Voeg componenten toe die het 3de element toont uit de array “hond”, “kat”, “olifant”, “slang”
Opdracht 3:
Voeg een component toe die een random getal tussen 1 en 100 op het scherm brengt
Opdracht 4:
Voeg een component toe die een button toont met als label “maak geluid”. Voorlopig doet deze button nog niets. Hij wordt enkel op het scherm getoond.
Tot nu toe hebben we altijd gewerkt met 1 pagina, maar uiteraard bestaan de meeste web applicaties uit verschillende pagina's. Je hebt bijvoorbeeld een lijst pagina waar de data in een lijst wordt getoond, als je dan doorklikt wil je naar een detail pagina gaan. Je wil ook verschillende pagina's gaan aanbieden op andere url's. Zo zal een lijst bijvoorbeeld op het pad /list
staan en de detail op /detail/{id}
. Om dit te doen gaan we gebruik maken van een externe library uit de npm registry: react-router-dom
Het eerste wat we gaan doen is onze games list pagina in een apart component plaatsen, zodat deze code niet meer in de App
component staat. Deze component krijgt later de verantwoordelijkheid voor het tonen van de verschillende pagina's, niet alleen de List
pagina. We maken een nieuwe directory pages
en maken daar een nieuwe directory ListPage
aan. We kopieren de inhoud van het App.tsx
bestand naar een nieuw bestand genaamd ListPage.tsx
en doen hetzelfde met de CSS-module van dit component. Vergeet niet de naam van het component aan te passen de default export.
Momenteel zit de lijst van games nog in het ListPage
component. We willen dit component beschikbaar maken voor alle pagina's dus we gaan de state een niveau hoger plaatsen. Wat wil zeggen dat we deze gaan terug plaatsen in het App
component. We moeten er voor zorgen dat de ListPage
de games list via props laten binnen krijgen. We definiëren de volgende props voor de ListPage
Je merkt op dat we hier ook de handleAdd
callback handler hebben opgegeven als property van onze ListPage
. De handleAdd
callback handler die hier wordt meegegeven wordt gewoon doorgeven aan het InputView
component.
En het App
component zal er als volgt uit zien.
Merk op dat we de Header
component in de App
component hebben gelaten. We willen de header op elke pagina plaatsen dus we zetten deze dan op dit niveau.
Nu hebben we er voor gezorgd dat we een aparte pagina hebben voor onze lijst van games en dat het App
component verantwoordelijk is voor de lijst van games te beheren (de state bevind zich dus op het hoogste component)
Nu we een aparte pagina hebben voor de lijst gaan we nu de library installeren die het mogelijk maakt om eenvoudig nieuwe pagina's op verschillende routes te introduceren. We installeren react-router met het volgende commando:
Ondertussen is react-router v6 uitgebracht. Dit betekent dat deze instructies niet allemaal nog gelden voor de laatste nieuwe versie. Daarom kan je best v5 installeren.
Omdat we momenteel maar pagina hebben maken we een heel eenvoudige HomePage.
De CSS style van de welcomeText mag je zelf kiezen. Zorg ervoor dat beide bestanden in een src/Pages/HomePage
directory zitten.
Nu gaan we onze App
component aanpassen zodat het de React router library gebruikt om de routes te vast te leggen en te kiezen welke page moet getoond worden bij welke route.
Als je ergens React Router wil gebruiken dan moet het parent component altijd een Router
component zijn.
Er zijn een aantal varianten op deze Router
component zoals BrowserRouter
en HashRouter
. Deze hebben invloed over hoe de urls die worden gegenereerd voor de paden zullen getoond worden. We bekijken deze niet in detail, maar deze zijn belangrijk als je uiteindelijk je applicatie op een externe webserver wil laten lopen.
Route
en Switch
noemen we route matching componenten. Als een Switch
wordt gerendered, wordt er in zijn kind elementen gezocht achter Route
elementen en wordt er gezocht achter het pad dat overeenkomt met de huidige URL. Als het er een vindt, dan rendered hij die Route
en negeert hij alle anderen. Dit betekent dat je altijd de meest specifieke (vaak de langste) eerst moet plaatsen voor de minder specifieke routes.
In ons geval hebben we nu twee routes. De route voor de lijst op /list
en de route voor /
die de HomePage zal laten zien aan de gebruiker. In de Route
componenten plaatsen we hier de componenten die hij zal moeten tonen als naar die route gegaan wordt in je browser.
React Router biedt een <Link>
component aan om links te maken in je applicatie. Overal waar je een Link rendered wordt een anchor (<a>
) tag gerendered in je HTML document.
Je hebt ook nog een speciale versie van de <Link>
component die zichzelf als 'actief' kan renderen als de to
prop overeenkomt met de huidige locatie. Als je de exacte locatie wilt matchen moet je hierbij ook nog exact als prop meegeven.
Zo kunnen we nu een navigatie balk aanmaken in onze header component:
Tot nu toe hebben we altijd routes gebruikt die exacte paden voorstellen. Soms wil je ook aan de hand van de url bepaalde parameters gaan meegeven. Zo willen wij in ons voorbeeld een pad aanmaken waarmee je het detail van een game kan bekijken. We zouden hier graag een url zoals /detail/:id
toelaten waarbij de :id
dus een parameter is. Deze kan bijvoorbeeld /detail/1
of /detail/2
zijn en hiervoor moet altijd hetzelfde component gerendered worden.
Laten we eerst een eenvoudige DetailPage
maken voor deze route:
We moeten dan deze DetailPage
toevoegen aan onze Router
Een parameter in een route wordt aangeven met de naam van de parameter en een :
ervoor. In dit geval is dit /detail/:id
waarvan id de naam van de parameter is.
Om deze parameters uit te kunnen lezen in de DetailPage
kunnen we de useParams
hook gebruiken om deze parameters te kunnen uitlezen.
We kunnen ons ListItem
component aanpassen aan de hand van het Link
component zodat als we op het de game klikken dat we dan naar de detailpagina verwezen worden.
Er zijn veel manieren om stylesheets toe te passen op React applicaties. We gaan hier twee veel gebruikte manieren bekijken:
CSS-in-CSS (CSS Modules)
CSS-in-JS
Elk heeft zijn eigen voor en nadelen. Het is belangrijk om te weten dat geen van deze manieren beter is dan de andere.
CSS Modules is de aanpak die het hardste aanleunt bij traditionele css bestanden zoals deze gebruikt worden in andere html pagina's. Alle elementen krijgen een class
toegewezen en krijgen een style toegewezen via een CSS bestand. In React gebruiken we niet class
maar className
.
In React willen we alle componenten zo zelfstandig mogelijk kunnen werken zonder te veel afhankelijkheden van elkaar. Daarom proberen we zoveel mogelijk per component een aparte CSS bestand aan te maken. Het is perfect mogelijk om alles in de algemene index.css
te plaatsen, maar dat gaan we zoveel mogelijk vermijden.
We kunnen CSS bestanden toevoegen aan een react component aan de hand van de import mechanisme van javascript.
Dit CSS bestand kan dan de volgende inhoud hebben:
We kunnen dan bijvoorbeeld onze <div>
tags stylen als volgt
Het nadeel van deze manier is dat we moeten opletten dat al deze CSS bestanden niet dezelfde class names gebruiken. Als dit wel gebeurt dan zullen deze CSS classes met elkaar botsen en zal de ene de andere overschrijven.
Om dit probleem op te lossen biedt React een betere manier aan van importeren van CSS bestanden. We gaan gebruik maken van CSS modules. Het gebruik is zeer gelijkaardig aan gewone CSS bestanden maar deze gaan volledig onafhankelijk van elkaar werken. Alle classes krijgen automatisch een willekeurig stuk tekst toegewezen zodat de namen van de classes allemaal uniek zijn. Om gebruik te maken van de CSS Modules moet het CSS bestand de naam krijgen van de Component gevolgd door .module.css
Als we nu gebruik willen maken van CSS modules in ons App
component moeten we dit hernoemen naar App.module.css
en moeten we de manier van importeren iets aanpassen
We gebruiken hier dan ook niet meer een string als we de className meegeven maar geven hier direct een variabele uit het styles object.
Je kan eventueel de vscode plugin installeren die autocomplete geeft voor de CSS modules.
We kunnen nu de rest van onze componenten beginnen stylen met CSS Modules.
Dit gaat onze header een grotere font size geven en in uppercase letters zetten. Er zal ook een lijn onder de titel gezet worden.
Dit maakt van de div een flexbox layout met als direction 'column'. Dit zorgt ervoor dat de elementen die in de div geplaatst worden verticaal zullen worden geplaatst worden in plaats van horizontaal. De breedte van de input-velden worden allemaal vastgezet op 500 pixels en krijgen allemaal een zwarte rand.
We zorgen met de gamesList
class dat de div elementen van de games worden in een rij gepositioneerd. Met de flex-wrap
property zorgen we dat de elementen op een nieuwe lijn worden geplaatst als deze niet op 1 lijn passen. We willen alle games laten zien aan de gebruiker als kaartjes met een schaduw en nog verschillende eigenschappen.
Je layout zal er ongeveer als volgt uitzien:
De manier van styling die we hierboven hebben toegepast is zeer gelijkaardig met hoe je in andere HTML paginas gebruik hebt gemaakt van CSS styles. Je hebt een apart CSS-bestand waarin je alle stijlen beschrijft aan de hand van classes en gebruikt classNames voor deze te gebruiken. In react heb je een alternatieve manier om dit te doen, je kan de styles rechtstreeks ook in javascript definiëren. Het voordeel van deze manier is dat we niet afhankelijk zijn van aparte CSS-bestanden en alles wat nodig is om een component te renderen in javascript beschikbaar is.
De eerste manier om dit te doen is via inline styles. Je geeft rechtstreeks de styles mee aan de hand van de style
property.
Je ziet dat we hier ook niet gebruik maken van de font-size
, border-bottom
en text-transform
die we in het gewone CSS-bestand gebruikte. De regel is dat we hier de camelCase varianten gebruiken van onze CSS properties. Zo wordt bvfont-size
dus fontSize
.
In plaats van het object rechtstreeks mee te geven aan de property kan je deze ook in een aparte constante plaatsen.
Niet alle dingen die je kan gebruiken in CSS kan je in CSS-in-JS gebruiken. Dingen zoals animaties zijn hierdoor niet eenvoudig te implementeren. Daarom wordt er vaak voor CSS-in-JS gebruik gemaakt van de library styled-components
Je kan deze eenvoudigweg installeren door
uit te voeren in je terminal en dan te importeren op de volgende manier
zo kunnen we onze header component ook als volgt stylen
Je merkt op dat we hier wel de standaard CSS properties kunnen gebruiken als in een gewoon CSS-bestand.
We gaven al aan dat we zelfs animaties kunnen definiëren in CSS. Dit kunnen we op de volgende manier doen:
Er zijn een tal van frameworks die het gebruik van zelf CSS schrijven minimaliseren. Je maakt daar gebruik van herbruikbare componenten die allemaal al voor jou geschreven zijn. De meest bekende zijn:
Deze vereenvoudigen het ontwikkelproces drastisch en zorgen ervoor dat er een bepaald design systeem wordt gebruikt. Dit zorgt ervoor dat het ontwikkelen van web applicaties drastisch kan versneld worden.
In een React applicatie wordt data van het bovenste component doorgegeven naar het onderste component aan de hand van props. Dit is mogelijk in een applicatie waar de component structuur niet heel diep is, maar naar mate de applicatie groeit, groeit meestal ook de diepte van je componenten structuur. Je zal dan vaak een prop verschillende niveau's diep moeten doorgeven.
Stel dat we een light theme en een dark theme willen ondersteunen in onze applicatie. Elk component heeft deze prop nodig want elk component moet zijn UI aanpassen als de theme light of dark is.
Schematisch ziet dat er als volgt uit:
Je ziet dat zelfs voor een kleine applicatie, zoals als degene die we in deze cursus hebben gemaakt, dat deze theme
prop al moeten doorgeven doorheen 3 niveaus van componenten. Bij grotere applicaties gaat dit nog veel erger worden. Daarom heeft React voor de Context
API gezorgd. Dit is een manier om data te gaan delen doorheen componenten zonder door heel de structuur te moeten doorgegeven worden.
Het eerste wat we moeten doen is een Context
aanmaken aan de hand van de createContext
Je moet altijd een default waarde meegeven aan de context. Deze default waarde wordt enkel gebruikt als er geen provider aanwezig is bovenaan in de component structuur. We geven hier een object met een mode
property. Dus in ons geval zal onze layout dus standaard in light mode staan.
Nu moeten we een Provider
maken van onze ThemeContext
zodat we de values kunnen meegeven die we in de componenten willen gebruiken. De provider moet rond de bovenste component staan die onze context willen gebruiken.
Nu kan je de values van de ThemeContext
provider opvragen aan de hand van de useContext
hook. Als de mode
dark is dan voegen we een extra className toe zodat we in ons css bestand een gameCardDark
class kunnen maken.
Je ziet nu dat elk component toegang heeft tot de mode
variabele die we in de context hebben aangeboden zonder dat we deze moeten doorgeven doorheen de component hierarchie.
We kunnen ook handler functies toevoegen in onze ThemeContext
zodat we ook van elk component de mode
kunnen aanpassen. We voegen nu aan de createContext functie een setMode
functie toe. We moeten hier een default value voor aanbieden, op dit moment hebben we nog geen handler dus we geven een lege functie mee.
We maken nu in onze App
component een state aan die de mode
bijhoudt zodat we deze kunnen aanpassen.
Nu geven we de setMode
functie mee aan de provider
Vanaf nu is de setMode
functie overal beschikbaar in elk component die deze context gebruikt. We kunnen nu de Header
component uitbreiden met een ModeButton
component die gebruik kan maken van de mode
en setMode
van de context. We maken hier gebruik van een FontAwesome icon. Dit is een grote verzameling van icons die worden aangeboden om in projecten te gebruiken. Deze kan je installeren door
uit te voeren. We maken ook nog een nieuwe component ModeButton
in een nieuwe component directory components/ModeButton
We tonen aan de hand van de huidige mode nu een button die een zon icon toont als het dark mode is en een maan icon als het light mode is. We zetten hier een onClick
listener op en roepen in deze listener de setMode
functie op. Dit is de setMode
functie die we hebben meegegeven met de ThemeContext
provider.
We kunnen de button stylen met een css module te maken voor de ModeButton
component.
Nu kunnen we deze in de Header
component plaatsen en dan hebben we een manier om de dark mode en de light mode te triggeren.
Er zijn nog veel andere voorbeelden waar Context
handig is om je applicatie structuur te verbeteren. Maar zorg er wel voor dat je deze niet te veel begint te gebruiken. Zolang je props niet op veel verschillende plaatsen moeten gebruikt worden of niet door verschillende componenten heen moeten doorgegeven worden is het soms beter om toch props te blijven gebruiken. Context
is geen alternatief voor props, dus gebruik het zeker niet om alles globaal beschikbaar te maken.
We gaan voor onze webapplicatie een web service bouwen aan de hand van node en express. We plaatsen uit gemak gewoon de code van onze api
in de src
folder van onze web applicatie zodat we toegang hebben tot alle bestanden van onze react applicatie. Zo kunnen we de types
hergebruiken en moeten we deze dus niet dubbel definiëren.
In deze api
folder voer je het volgende commando uit om de tsconfig.json
file aan te maken.
door dit commando wordt de volgende tsconfig.json
file aangemaakt
vervolgens doe je npm init
om een package.json
file te genereren.
Voor onze kleine API moeten we de volgende libraries installeren
De Express library laat toe om eenoudig en declaratief een web service te bouwen in node.
De cors library laat toe een bepaalde security check voor je browser te deactiveren.
Omdat we typescript gebruiken moeten we @types/express
en @types/node
installeren als dev dependency zodat we de types kunnen gebruiken voor node
en express
Voor we alle routes beginnen opstellen zetten we de simpele express applicatie op. We maken een index.ts
bestand aan met de volgende inhoud.
De express applicatie wordt aangemaakt aan de hand van de express()
functie (A).
(B) Je browser laat standaard nooit toe om web service calls te doen vanuit een javascript bestand naar een domein of poort dat verschilt van het domein of poort van waar het javascript bestand is gehosted.
Bv. als je javascript bestand gehosted is op http://localhost:3000/index.js
kan je bijvoorbeeld geen API calls doen naar http://localhost:3001/
omdat de poort verschillend is. Je zal een error krijgen die sterk lijkt op:
om deze error te vermijden moeten we de cors
package installeren. Deze laat toe om de Access-Control-Allow-Origin *
header te zetten voor elke request zodat je browser dit wel toelaat.
(C) zorgt ervoor dat de body van http requests worden omgezet naar json objecten zodat die rechtstreeks kunnen aangesproken worden vanuit je code.
(D) Deze lijnen code zorgen ervoor dat er geluisterd wordt naar de poort 3001 en dat requests kunnen afgehandeld worden.
We maken een bestand games.json
aan die de begin dataset zal voorstellen voor onze API.
We kunnen deze dan eenvoudig inlezen door na het starten van onze express server dit bestand in te lezen:
We moeten dan nog wel een globale variabele games
moeten voorzien. We gebruiken hier de types die we voor onze React applicatie hebben gemaakt.
De eerste route die we gaan aanmaken is een GET route voor/games
Deze route gaat de array van games teruggeven als json.
We maken nu ook een route aan voor /games/:id
zodat we ook een game kunnen opvragen met een id. Eerst halen we de id
uit de parameters en zetten we deze om naar een getal aan de hand van de parseInt
functie. We gebruiken de find
methode op de games array om de juiste game te vinden. Als deze gevonden wordt geven we deze terug met de res.json()
methode. Als deze niet gevonden wordt geven we een 404 Not Found
terug.
Om de mogelijkheid toe te staan om ook games toe te voegen maken we een POST methode voor /games
om een game toe te voegen. We zoeken eerst de hoogste id in de games
array, want deze kan de gebruiker van de API niet zelf bepalen. Daarna gebruiken we het Game
object in de body en vervangen we de id van dit object. Vervolgens voegen we het object toe aan de array. Na het toevoegen geven we de volledige array terug mee als json zodat de client geen extra request moet doen om de aangepaste lijst terug te krijgen.
Voor de eenvoudigheid gaan we er vanuit dat er geen foutieve data aanwezig kan zijn dus er is geen server side input validatie aanwezig.
De volgende route die we gaan aanbieden is er een die het mogelijk maakt om een Game
aan te passen. Daarvoor moeten we een PUT op /games/:id
aanmaken. We gebruiken hier weeral de id
van de parameters die worden doorgeven. Vervolgens gebruiken we de map
functie om de games array om te zetten naar een nieuwe array maar waar het element vervangen is.
We konden in plaats van de map
functie ook gewoon een eenvoudige for
loop schrijven maar dit is veel uitgebreider.
Heel gelijkaardig kunnen we een POST methode maken voor /games/:id/sell
om de sales counter te verhogen voor een game.
Nu kunnen we de API opstarten aan de hand van
Je kan via een tool zoals Postman nagaan of de routes werken zoals het hoort.
Opdracht:
Deze opdracht bestaat uit een aantal deelopdrachten waarbij de complexiteit van de applicatie steeds groter wordt.
De uiteindelijke bedoeling is om een dashboard te maken waarop een aantal tellers worden getoond, waarvan voor elke teller de waarde door de gebruiker kan worden gewijzigd door voor elke teller specifiek een verhogingswaarde in te stellen. Door op een knop te duwen, kan de gebruiker bepalen wanneer de tellers moeten worden verhoogd.
Voorbeeld:
Teller1 heeft als initiële waarde 1 en als verhogingswaarde ook 1.
Als de gebruiker op de knop “verhoog” drukt, dan zal de nieuwe waarde van teller1 = 2 te worden.
Veronderstel dat de gebruiker de verhogingswaarde van teller1 daarna op 4 zet, dan zal de volgende keer dat de gebuiker op “verhoog” duwt, de waarde van teller1 = 6 worden (2 oude waarde + 4 verhoging).
Detailbeschrijving:
Elke teller is een object. Elke teller heeft een key, een naam, een calcul (=de verhogingswaarde) en een value (=de huidige waarde van de teller).
Als de gebruiker op een knop “verhoog” drukt, dan moeten alle tellers verhoogd worden met de voor hen specifieke verhogingswaarde.
De initïële waarde van elke teller alsook de verhogingswaarde is steeds = de index = de key van de teller.
Nog niet alle kennis zal onmiddellijk na de eerste lesweek aanwezig zijn om deze volledig applicatie te bouwen zoals hierboven beschreven is.
Deze opdracht bestaat daarom uit onderstaande deelopdrachten. Tijdens de labo’s zal aangegeven worden welke opdracht wanneer kan worden gemaakt.
Opdracht 1:
Voorzie een knop op het scherm. Elke keer als de gebruiker op de knop drukt, moet de waarde van de knop verhoogd worden met 5. De initiële waarde van de knop zet je op 25.
Opdracht 2:
Maak de knop die je in deel1 creëerde generieker waarbij je door een parameter mee te geven bepaalt met welke waarde de knop verhoogd moet worden.
Opdracht 3:
Laat de gebruiker bepalen met welke waarde de knop verhoogd moet worden door hem dit in een invoerveld te laten invoeren.
Opdracht 4:
Zet 5 tellers op het scherm waarbij je voor elke teller afzonderlijk de waarde laat verhogen door een door de gebruiker gekozen waarde. Als de gebruiker op 1 knop “verhoog” drukt, dan moeten de waarde van alle tellers worden verhoogd.
Opdracht 5:
Laat de gebruiker kiezen hoeveel tellers hij op het scherm wil zien. Voorzie daarvoor een invoerveld. Initieel moet dus dat aantal tellers worden getoond met hun initiële waarde. Bij het drukken op een “add” knop moet dit aantal knoppen worden toegevoegd. De werking van elke teller moet hetzelfde blijven als in deel4. De gebruiker kan dus nog steeds per teller de verhogingswaarde instellen.
Voorbeeldscenario met bijhorende schermen:
Opstarten van de applicatie: default staat het aantal tellers bij de opstart van de applicatie op 2. Er worden dus 2 tellers getoond met hun initiële waarde en hun verhoogwaarde.
Nadat de gebruiker op de knop “verhoog” gedrukt heeft, zal het volgende worden getoond:
Veronderstel dat de gebruiker nu zegt dat er 3 tellers moeten worden toegevoegd. De gebruiker zet aantal bijkomende tellers op 3.
Veronderstel dat de gebruiker op de “add” knop drukt: 3 bijkomende tellers worden geïnitialiseerd
Na het drukken op de knop “verhoog”:
Veronderstel dat de gebruiker de verhoogwaarde van teller 3 en teller 6 als volgt heeft aangepast:
Als vervolgens de gebruiker op de knop “verhoog” drukt:
Opmerking: bij het wijzigen van het aantal bijkomende tellers, moet de applicatie dus al onmiddellijk reageren zonder dat nog eens extra op de knop add moet worden gedrukt. Bij het wijzigen van de verhoogwaarde, gebeurt er nog niets. Er moet dan wel op de knop “verhoog” worden geduwd om de verhoging met de nieuw ingestelde verhoogwaardes door te voeren.
Dreamstays is een applicatie die – zoals de naam het zegt – exclusieve verblijven wil gaan aanbieden in v luxueuze verblijven die onze dromen overstijgen. Het gaat om verblijven in villa’s die wereldwijd verspreid zijn.
Maak hiervoor een applicatie "dreamstays" aan die de gebruiker toelaat in een lijst van beschikbare villa’s een villa te zoeken.
Verfijn jouw applicatie op basis van volgende opdrachten:
Opdracht 1:
Zet bovenaan de pagina het logo en de slogan “ Beyond your dreams”
Opdracht 2:
Voorzie een dropdown keuzelijst waarbij de gebruiker kan kiezen in welk land hij een luxeverblijf wil zoeken.
Opdracht 3:
Toon de lijst van beschikbare villa’s voor het land dat de gebruiker selecteert.
Opdracht 4:
Laat de details van het pand op het scherm verschijnen nadat de gebruiker 1 pand uit de lijst heeft aangeklikt. Zorg ervoor dat op deze detailpagina een foto van de gekozen villa wordt getoond, de prijs en de beschrijving.
Opdracht 5:
Het doel van deze opdracht is om jouw DreamStay webapplicatie te stylen en te personaliseren. Je zal hiervoor zelf opzoekingswerk moeten doen.
Je kan gebruik maken van een UI framework zoals Bootstrap om de DreamStay applicatie een professionele look te geven.
Enkele requirements:
Toon op de home page een hero image.
Kies mooie lettertypes en font sizes.
Voeg gepaste grafische elementen toe.
Laat je inspireren door het web... bijvoorbeeld:
of doe zelf opzoeking op internet om een hedendaagse, trendy layout te bouwen.
Doe een voorstel van de layout die je wil bouwen en laat dit goedkeuren door jouw docent voor dit vak alvorens je met de realisatie begint!
Maak voor het uitvoeren van bovenstaande opdrachten gebruik van de bijgevoegde bronbestanden:
Logo Dreamstay Villas
Json bestand met beschikbare data van de villa’s
Images van de aanwezige villa’s