Les 5: Hooks
Tijdens deze oefeningenreeks begin je een soort social network uit te bouwen, voor deze les kunnen gebruikers zich inschrijven in een groep en de groepen waar ze deel van uitmaken bekijken. Daarnaast kan een gebruiker een groep aanmaken en wordt een side-nav geïmplementeerd. Dit is een uitgebreide oefeningenreeks, volgend les wordt er een uitbreiding voorzien op deze oefeningen, we zullen de posts, comments en chat functie dan toevoegen. Daarnaast zul je enkele zaken verbeteren door de toevoeging van global state.
Oefening 0: Voorbereiding
Maak een nieuw project aan en plaats de startbestanden in dit project. In deze startbestanden kan je een inlogpagina vinden en een pagina om de username van een gebruiker aan te passen. Daarnaast is de login-flow ook al geïmplementeerd en is de flex-box lay-out van de navigatiebalk al deels aanwezig. Verder worden er hier en daar ook componenten aangeboden met lay-out, de stateful logica is niet geïmplementeerd in deze componenten. Voor deze oefeningen heb je React Router, React Bootstrap, en de prop-types nodig. Installeer deze alvast in je project.
Naast deze bekende bibliotheken maak je ook gebruik van enkele minder bekende bibliotheken, dotenv wordt gebruikt om de API keys voor Supabase in te lezen. Tenslotte gebruiken we ook enkele Bootstrap Icons. Je kan deze bibliotheken installeren via onderstaand commando.
npm i dotenv @supabase/supabase-js bootstrap-icons
Om gebruik te kunnen maken van de Bootstrap iconen bibliotheek moet onderstaand import statement toegevoegd worden aan je index.js.
import "bootstrap-icons/font/bootstrap-icons.css"
Voor deze opgave gelden volgende regels:
- Gebruik enkel hooks, class components zijn niet toegestaan voor deze opgave.
- Bouw herbruikbare hooks, gebruik useState en useEffect enkel rechtstreeks in een component als het echt nodig is.
- Voeg voor elke component waar je properties gebruikt, een controle via de prop-types bibliotheek toe.
- De lay-out is NIET belangrijk, als je de screenshots/gif niet exact nabouwt, is dit geen probleem. Leg de focus op hooks.
- Je gebruikt de gegeven .env, je moet zelf geen Supabase aanmaken.
Oefening 1: NavBar
In de startbestanden, in index.js, in de Main component vind je een skelet voor de lay-out van de website. De eerste kolom die teruggegeven wordt door de Main component bevat de NavBar component, voorlopig bevat deze component enkel nog maar wat lay-out code. De basisstructuur van de NavBar is te zien op onderstaand screenshot.

De NavBar component toont een side-nav met 4 links, elk van deze links bestaat uit een icoontje (2 breed) en een titel (10 breed). De gebruiker kan zowel op het icoontje als de titel klikken om naar een andere pagina te navigeren.
Gebruik een component NavItem die één link voorstelt. Als het NavItem de titel van de website is ("Social Network"), dan wordt er een horizontale lijn getoond onder de titel en het icoontje, daarnaast wordt een <h3> gebruikt voor het icoontje en de titel. Alle andere NavItems gebruiken een <h4> gebruikt. Bekijk de Bootstrap Icons documentatie voor meer info over het gebruik van de icoontjes.
- Social Network
- Verwijst naar het pad '/', wat de component HomePage laad.
- Het gebruikte icoon is: bi-house-door-fill.
- {username}
- Verwijst naar het pad '/user', wat de component Profile laad.
- Het gebruikte icoon is: bi-person-fill.
- Groups
- Verwijst naar het pad '/groups', wat de component GroupRouting laad.
- Het gebruikte icoon is: bi-people-fill.
- Chat
- Verwijst naar het pad 'chat', wat de component ChatPage laad.
- Het gebruikte icoon is: bi-chat-fill
1.1 Active link styling
Maak gebruik van de React Router hooks om ervoor te zorgen dat de geselecteerde link de CSS-klasse 'text-muted' krijgt. Onderstaande gif demonstreert de werking.
Hint
De React Router hooks die we in de les besproken hebben zijn niet voldoende, aangezien de NavBar component zelf niet in de Route componenten staat is de match steeds /. Je kan de useLocation hook gebruiken om dit probleem op te lossen.
1.2 Dichtklappen
De NavBar component moet dichtgeklapt kunnen worden, in dit geval worden enkel de icoontjes getoond. Om het dichtklappen te implementeren kan je de icoontjes bi-arrow-left en bi-arrow-right gebruiken.
Oefening 2: Groepen
Een gebruiker kan een groep aanmaken en zich aansluiten bij een bestaande groep. Een groep kan privé gemaakt worden zodat je zaken kan uittesten zonder dat de code van andere studenten hier invloed op kan hebben. We beginnen met een overzicht te bouwen van alle groepen en gaan vervolgens verder met het aanmaken vna een nieuwe groep en tenslotte met het openen van de detailpagina.
2.1: Groep pagina
De groep pagina bestaat uit een Bootstrap Tab component, met 3 tabbladen, de layout is te vinden in /src/groups/groupPage/groupPage.js. Zorg dat deze component geladen wordt als het pad /groups bezocht wordt (via de GroupRouting component.)
Het eerste tabblad zal gebruikt kunnen worden om een overzicht te krijgen van de meest recente posts in alle groepen waar de gebruiker op geabonneerd is. Voorlopig bevat dit tabblad enkel onderstaande tekst.

Het tweede tabblad zal een overzicht geven van de groepen waar de gebruiker zich op geabonneerd heeft, aangezien je momenteel nog op geen enkele groep ingeschreven bent, kan je hier voorlopig onderstaande tekst plaatsen. In oefening 2.7 werk je dit verder uit.

Het derde tabblad toont een overzicht van alle publieke groepen en bied de optie om hierdoor te zoeken. Daarnaast kan deze pagina ook gebruikt worden om een nieuwe groep aan te maken. Onderstaande formulier is te vinden in de component SearchGroups

2.2: GroupItem
Bouw een component GroupItem deze component krijgt één groep binnen en toont hiervoor de basisinformatie (titel, eigenaar, beschrijving en link naar een detailpagina). Vergeet niet om controles op de properties toe te voegen. De nodige data wordt als een JSON object aangeleverd door de API, hieronder zie je een voorbeeld voor de groep 'Lorem 1'. Je kan onderstaande JSON code kopiëren om de component uit te testen. Vergeet de controles via prop-types niet.
{
"id": "0ce34360-0971-4b1d-9eb9-864508540ebb",
"created_at": "2021-11-05T17:08:02+00:00",
"name": "Lorem 1",
"owner": {
"username": "Sebastiaan Henau"
},
"is_private": false,
"description": "Lorem ipsum dolor sit amet, ..."
}
2
3
4
5
6
7
8
9
10
Deze informatie wordt als volgt weergegeven in een Bootstrap Card component.

2.3: useGroups hook
Bouw een custom hook die gebruikt kan worden om een overzicht te generen van alle groepen die voldoen aan een bepaalde zoekopdracht. Deze hook zal telkens dat de zoekterm aangepast wordt, de nieuwe data ophalen uit de database, hiervoor kan je gebruik maken van de fetchMatchingGroups methode uit groupAPI.js. Het is vanzelfsprekend dat alle useEffect calls ook correct afgehandeld worden indien de component zou verdwijnen uit de DOM. Onderstaande gif demonstreert de werking van de useGroups hook. De 4 getoonde groepen zijn publiek, en zouden dus ook in jullie webapplicatie te zien moeten zijn.
Voer de zoekopdracht pas uit als er op de "Search" knop gedrukt wordt. De backend ondersteund wel oneindig veel requests, maar draait op een free tier en heeft dus niet heel veel resources. Als elke student na elk onChange event zoek, kan het zijn dat er vertragingen optreden voor alle studenten.
2.4: Pagination
Er zouden, in theorie, veel verschillende groepen teruggegeven kunnen worden, om het ietwat overzichtelijk te houden, zullen we pagination toevoegen aan de zoekresultaten. Voor de backend is dit vereist vanaf 1000 rijen, dit is het maximum dat in één request teruggegeven kan worden.
Bouw een Pagination component, deze krijgt 4 properties, de huidige pagina, het totaal aantal pagina's en de nodige functie(s) waarmee de huidige pagina aangepast kan worden. Voeg opnieuw controles toe via de prop-types bibliotheek.

2.5: useGroups hook v2
Breid de useGroups hook uit zodat deze gebruikt kan worden om de overeenkomstige groepen terug te geven in pagina's via pagination. Hiervoor voeg je minstens één parameter toe aan de hook die grootte van de pagina's bepaald. Je kan de fetchMatchingGroupsWithPagination functie in de startbestanden gebruiken om slecht een selectie van de overeenkomstige groepen op te halen. In onderstaande gif wordt een pagina grootte van 2 groepen gebruikt. Merk op dat de Paginator niet getoond wordt als er slechts één pagina is.
2.6: Groep aanmaken
Elke gebruiker kan een nieuwe groep aanmaken, de UI die je hiervoor kan gebruiken is terug te vinden in de component CreateGroup in de startbestanden. Gebruik voor dit formulier uit te werken de componenten FormSubmitButtonWithLoading en ResponseMessage (meer uitleg over de properties is te vinden in deze componenten).
Zoals in onderstaande gif te zien is, wordt de gebruiker na 2.5 seconden automatisch herleid naar de detailpagina van de groep als de groep succesvol aangemaakt is. Daarnaast moet een privégroep niet verschijnen in het overzicht, en moet een publieke groep hier wel verschijnen. Dit is in de API verwerkt, je moet er enkel voor zorgen dat de checkbox, in het formulier, correct verwerkt wordt. Om deze opgave uit te werken, kan je gebruik maken van de createGroup functie in de meegeleverde code.
2.7: Lijst van inschrijvingen
Breid de useGroups hook uit met een parameter personal, deze parameter geeft aan of een lijst van alle groepen teruggegeven moet worden of enkel een lijst van de groepen waarop de gebruiker ingeschreven is. Standaard is deze parameter false.
Het grootste deel van de code in de useGroups hook blijft onveranderd, enkel de gebruikte functie uit de API moet conditioneel aangepast worden. Je kan gebruik maken van de functies fetchAllGroupsForUserWithPagination en fetchNumberOfMatchingGroupsForUser, deze werken exact op dezelfde manier als de functies die je eerder gebruikt hebt. Bouw onderstaande view na, merk op dat de privégroep die in de vorige gif aangemaakt is, nu zichtbaar is. In onderstaande gif is een pagina grootte van 4 gebruikt.
2.8: Detailpagina
De detailpagina voor een groep kan gebruikt worden om de posts en de leden van een groep te bekijken, daarnaast kan een gebruiker hier ook inschrijven in een groep en zich opnieuw uitschrijven.
Schrijf voor deze pagina een nieuwe hook useGroup die het id van een groep als argument krijgt. Verder haalt deze hook, de details van een groep (via de functie fetchGroup) en de leden van een groep (via de functie fetchGroupMembers) op. Daarnaast controleert de hook of de huidige gebruiker deel uitmaakt van de groep (via de functie isSubscribedTo). In de component GroupDetail kan je de lay-out voor deze component vinden.