3. Single Page Applications
3. Single Page Applications
Tot nu toe bestonden de React applicaties die we gebouwd hebben uit één pagina, dit is natuurlijk ontoereikend voor een realistische website. Deze les bekijken we hoe we navigatie, en dus meerdere pagina's, kunnen toevoegen aan een React applicatie. Daarnaast integreren we een CSS-framework en componentenbibliotheek in de applicatie.
Startbestanden
React Router
Een React project, aangemaakt via pnpm create vite, bevat de nodige bibliotheken voor routing nog niet. Om routing te integreren in een React app is een nieuwe bibliotheek nodig.
React-router, implementeert alle routing functionaliteiten en voorziet een aantal utility hooks die routing eenvoudiger maken. Daarnaast kan deze library ook gebruikt worden als een volwaardig framework (inclusief server code), dit onderdeel van de library valt echter buiten de scope van deze cursus. We verwijzen de geïnteresseerde lezer door naar de framework mode documentatie.
Tailwind & Shadcn
Tijdens deze cursus en in backend frameworks maken we gebruik van Shadcn, een op Tailwind CSS en Radix UI gebaseerde bibliotheek.
Radix voorziet component primitives zonder opmaak, Shadcn gebruikt deze primitives vervolgens en voegt hier, via Tailwind CSS opmaak aan toe. Verder is Shadcn geen klassieke componenten library, er is geen npm package beschikbaar voor Shadcn. Elke component moet afzonderlijk geïnstalleerd worden en wordt toegevoegd in de src map en niet in de node_modules. Op deze manier is het heel eenvoudig om de componenten die door Shadcn voorzien worden uit te breiden en aan te passen aan je eigen noden.
Configuratie
De installatie van Tailwind en Shadcn is ietwat ingewikkelder dan de meeste npm libraries, hieronder wordt de installatiehandleidingen die op de Shadcn website beschikbaar is herhaald met extra toelichting.
We beginnen met Tailwind en de TypeScript definitions voor Node.js te installeren. Deze laatste zijn nodig omdat we in vite.config.ts configuratie-code moeten toevoegen die gebruik maakt van Node.js modules.
Shadcn gebruikt import aliassen om import statements zo kort mogelijk te houden. In de plaats van iets als import { Button } from '../../../../components/ui/button', kunnen we, dankzij een import alias, de relatieve paden weglaten en het import statement verkorten tot import { Button } from '@/components/ui/button'.
We kunnen zoveel aliassen configureren als we nodig achten, maar voor Shadcn is er slechts één alias nodig, de '@' alias die verwijst naar de ./src/ folder. Deze alias moet op drie plaatsten geconfigureerd worden.
In vite.config.ts wordt naast de alias ook de tailwindcss() plugin toegevoegd. Deze plugin zorgt ervoor dat er tijdens het compilatieproces door alle bestanden gezocht wordt naar Tailwind CSS-klassen. Op basis van de gevonden klassen wordt het uiteindelijke CSS gegenereerd, hierin zit dus enkel code die ook effectief gebruikt wordt op de website.
{
"files": [],
"references": [
{ "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" }
],
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
}
}{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"target": "ES2022",
"useDefineForClassFields": true,
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"moduleDetection": "force",
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"erasableSyntaxOnly": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true,
"paths": {
"@/*": [
"./src/*",
]
}
},
"include": ["src"]
}import path from 'path'
import tailwindcss from '@tailwindcss/vite'
import {defineConfig} from 'vite'
import react from '@vitejs/plugin-react-swc'
// https://vite.dev/config/
export default defineConfig({
plugins: [react(), tailwindcss()],
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
},
},
})Voordat we verder kunnen gaan met het initialiseren van Shadcn moeten we de configuratie van Tailwind afronden door een nieuw CSS-bestand aan te maken dat de Tailwind stijlregels importeert in main.tsx.
@import "tailwindcss";import {createRoot} from 'react-dom/client'
import {StrictMode} from 'react'
import Navbar from '@/navigation/navbar.tsx'
import './main.css'
const root = createRoot(document.getElementById('root') as HTMLElement)
root.render(
<StrictMode>
<Navbar />
<div className="w-auto block m-4"></div>
</StrictMode>,
)Als laatste stap moeten we Shadcn configureren, hiervoor gebruiken we onderstaand commando. Merk op dat we pnpx gebruiken en niet pnpm, pnpx is een package runner, dit betekent dat we een package vanuit het npm registry kunnen uitvoeren zonder dat we dit ook echt moeten installeren als een dependency in ons project.
Tijdens het configuratieproces wordt er gevraagd om een basiskleur te kiezen, in de lesvoorbeelden is "Gray" gebruikt, maar je bent vrij om een andere kleur te kiezen.
Onderstaand commando past de inhoud van main.css aan, alle theme kleuren worden hier genoteerd. Via websites als tweakcn en shadcnstudio kan je een ander kleurenpalet kiezen of zelf een kleurenpalet samenstellen.
✔ Which color would you like to use as base color? › GrayAls bovenstaande stappen correct uitgevoerd zijn, zijn onderstaande bestanden toegevoegd aan het React project.

Componenten installeren
Nu de configuratie volledig in orde is kunnen we de componenten installeren die nodig zijn om het lesvoorbeeld af te werken. De Shadcn documentatie bevat een overzicht van alle beschikbare componenten en informatie over het gebruik ervan. We gebruiken deze de Card, NavigationMenu en Separator componenten.

Info
Via de shadcn directory kan je een overzicht vinden van verschillende collecties componenten. Eens shadcn geïnstalleerd is, kunnen alle componenten uit deze collecties geïnstalleerd worden via de shadcn CLI.
Navbar met Shadcn
We bouwen een applicatie die 4 pagina's bevat, Home, Foo, Bar, en Class. In de startbestanden is hier al een navbar voor voorzien.

Uit bovenstaand screenshot is het echter duidelijk dat deze navbar niet de juiste opmaak heeft, toch zijn Shadcn en Tailwind correct configureert. Het probleem is dat Shadcn enkel opmaak geeft aan dropdownmenu's, om dit probleem op te lossen kunnen we de navigationMenuTriggerStyle functie uit Shadcn gebruiken in de StyledNavLink component die één link in de Navbar component voorstelt.
import {FunctionComponent, PropsWithChildren} from 'react'
import {NavigationMenuItem, NavigationMenuLink, navigationMenuTriggerStyle} from '@/components/ui/navigation-menu.tsx'
interface StyledNavLinkProps extends PropsWithChildren {
to: string
}
const StyledNavLink: FunctionComponent<StyledNavLinkProps> = ({to, children}) => {
return (
<NavigationMenuItem>
<NavigationMenuLink className={navigationMenuTriggerStyle()} href={to}>
{children}
</NavigationMenuLink>
</NavigationMenuItem>
)
}const Navbar: FunctionComponent = () => {
return (
<div className="w-auto block m-2">
<NavigationMenu>
<NavigationMenuList>
<StyledNavLink to="/">Home</StyledNavLink>
<StyledNavLink to="/foo">Foo</StyledNavLink>
<StyledNavLink to="/bar">Bar</StyledNavLink>
<StyledNavLink to="/class">Class</StyledNavLink>
<StyledNavLink to="/thisLinkProducesA404Error">Error Page</StyledNavLink>
</NavigationMenuList>
</NavigationMenu>
</div>
)
}Na deze aanpassing is opmaak wel in orde.

BrowserRouter
Momenteel hebben de links in de navigatiebalk nog geen effect, alles wat deze links doen is de pagina herladen. Via BrowserRouter, Routes en Route, die componenten uit react-router, kunnen we specifiëren welke componenten getoond moeten worden voor elke route.
De BrowserRouter component moet rond de volledige applicatie staan, of toch rond alle delen die invloed hebben op, of beïnvloed worden door, de routing. Een footer die op elke pagina hetzelfde is en geen links bevat naar andere pagina's in de React applicatie, kan eventueel buiten de BrowserRouter component geplaatst worden. De Navbar moet binnen deze component staan omdat deze links bevat en dus invloed heeft op de routing.
import {BrowserRouter} from 'react-router'
const root = createRoot(document.getElementById('root') as HTMLElement)
root.render(
<StrictMode>
<BrowserRouter>
<Navbar />
<div className="w-auto block m-4"></div>
</BrowserRouter>
</StrictMode>,
)Routes & Route
Begrip: Route & Routes
De Routes en Route componenten uit react-router worden gebruikt om, op basis van de huidige URL, de juiste componenten te tonen.
De Routes component bevat één of meerdere Route componenten waarmee aangegeven wordt welke componenten getoond moeten worden voor welke route. Een Route component gebruikt de property path om de route te definiëren en de property element om de bijhorende component vast te leggen.
Als onderstaande code gerenderd wordt, zal slechts één van de drie pagina's (HomeComponent, AboutComponent, PricingComponent) getoond worden, afhankelijk van de huidige URL.
import {Routes, Route} from 'react-router'
const Routing: FunctionComponent = () => {
return (
<Routes>
<Route path={'/'} element={<HomeComponent/>}/>
<Route path={'/about'} element={<AboutComponent/>}/>
<Route path={'/pricing'} element={<PricingComponent/>}/>
</Routes>
)
}We gebruiken de Routes en Route componenten om elk van de verschillende routes toe te voegen aan de applicatie. Hiervoor schrijven we een nieuwe component Routing. Natuurlijk moet de Routing component zelf ook nog opgeroepen worden, dit doen we in main.tsx.
const Routing: FunctionComponent = () => {
return (
<Routes>
<Route path={'/foo'} element={<Foo />} />
<Route path={'/bar'} element={<Bar />} />
<Route path={'/'} element={<Home />} />
<Route path={'/class'} element={<Class />}/>
</Routes>
)
}root.render(
<StrictMode>
<BrowserRouter>
<Navbar />
<div className="w-auto block m-4">
<Routing />
</div>
</BrowserRouter>
</StrictMode>,
)In bovenstaande code gebruiken we enkele Tailwind klassen, de volledige lijst van klassen is veel te uitgebreid om in deze cursus te bespreken, we verwijzen door naar de Tailwind documentatie en vertrouwen erop dat de gemotiveerde student de betekenis van onbekende klassen zelf kan opzoekt.
Catch all route: 404 Pagina
Natuurlijk heeft elke website nood aan 404 pagina die getoond wordt in het geval een gebruiker een niet bestaande URL probeert te bezoeken. Zoals bovenstaande video toont, werkt die hier nog niet.
Via het wildcard (*) karakter kan een route gebouwd worden die (deels) matcht met een willekeurige URL. Onderstaande route komt dus overeen met alle mogelijke paden.
const Routing: FunctionComponent = () => {
return (
<Routes>
<Route path={'/foo'} element={<Foo />} />
<Route path={'/bar'} element={<Bar />} />
<Route path={'/'} element={<Home />} />
<Route path={'/class'} element={<Class />}/>
<Route path={'*'} element={<PageNotFound />} />
</Routes>
)
}De PageNotFound component is al deels gegeven in de startbestanden mark deze werkt nog niet volledig.
Momenteel zie je een lege pagina met een countdown (de code van deze hook mag je voorlopig negeren) van 5 naar 0. Er zijn twee problemen, de countdown doet nog niets en de pagina mag er wat interessanter uitzien, we willen de video die in de startbestanden toegevoegd is tonen.
Om de video te tonen kunnen we de video file importeren in React en deze vervolgens doorgeven aan het src attribuut van het <video> tag.
import video from '@/assets/404.webm'
const PageNotFound: FunctionComponent = () => {
const countdown = useCountdown(5)
return (
<div className="w-screen h-screen bg-black absolute left-0 top-0 -z-10">
<video
className="h-screen w-screen absolute left-0 top-0"
src={video}
autoPlay={true}
loop={true}
/>
<div className="text-blue-100 absolute left-2 top-16">Redirecting to home in {countdown} seconds</div>
</div>
)
}Navigate component
De video wordt nu wel zonder foutmeldingen geladen, maar de countdown werkt nog niet. De Navigate component biedt een oplossing voor dit probleem.
import {Navigate} from 'react-router'
const PageNotFound: FunctionComponent = () => {
const countdown = useCountdown(5)
if (countdown === 0) {
return <Navigate to={'/'} />
}
return (
<div className="w-screen h-screen bg-black absolute left-0 top-0 -z-10">
<video
className="h-screen w-screen absolute left-0 top-0"
src={video as string}
autoPlay={true}
loop={true}
/>
<div className="text-blue-100 absolute left-2 top-16">Redirecting to home in {countdown} seconds</div>
</div>
)
}SPA Links
De routing werkt nu, alle pagina's kunnen bezocht worden. Het duurt nog wel te lang voor we een nieuwe pagina zien. Als je het network tab opent in de developer tools van je favoriete browser zie je dat de volledige website bij elke klik op een navigatie link opnieuw gedownload wordt van de server. Onderstaande video demonstreert dit.
Het probleem is het gebruik van anchor (<a>) tags in de Navbar component. Een anchor tag is niet geschikt voor gebruik in SPA's omdat dit tag de gebruiker naar een nieuwe pagina stuurt. Een single page application bestaat uit één pagina, de content van de pagina wordt door JavaScript opgevuld. Dit betekent dat we geen nieuwe pagina willen openen, maar enkel de chunks die de inhoud van de nieuwe pagina bevatten (en nog niet in memory zitten) willen downloaden. Hiervoor moeten we natuurlijk react-router gebruiken.
Begrip: SPA Links
SPA links zijn links die gebruikt kunnen worden om te navigeren binnen een SPA zonder de pagina te moeten herladen.
React Router bevat een Link en NavLink component, beide componenten passen de URL in de adresbalk aan, maar doen dit zonder de pagina te herladen. Het verschil tussen de twee componenten is de opmaak. Aan een NavLink component kan via de property style of className een functie meegegeven worden die de opmaak aanpast als de link actief is. Verder is er geen verschil tussen de twee componenten. Elk van deze componenten heeft een to property die gebruikt kan worden om het pad door te geven waarnaar genavigeerd moet worden als op de link geklikt wordt.
import {Link, NavLink} from 'react-router';
const textLinkExample = <Link to="The path to navigate to">Link text on the website</Link>
const navLinkExample1 = (
<NavLink to="The path to navigate to"
className={({isActive}) => isActive ? 'activeClass' : 'standardClass'}>
Link text on the website
</NavLink>
)
const navLinkExample2 = (
<NavLink to="The path to navigate to"
activeStyle={'CSSProperties object with styling'}>
Link naam op website
</NavLink>
)We kunnen in StyledNavLink d eNavigationLink component natuurlijk vervangen met een Link. Maar dan verliezen we de opmaak die Shadcn voorziet voor de NavigationLink component. Om dit op te lossen kunnen we de asChild property toevoegen aan de NavigationMenuItem component en de Link vervolgens als kind meegeven.
import {Link} from 'react-router'
const StyledNavLink: FunctionComponent<StyledNavLinkProps> = ({to, children}) => {
return (
<NavigationMenuItem>
<NavigationMenuLink className={navigationMenuTriggerStyle()}
asChild>
<Link to={to}>{children}</Link>
</NavigationMenuLink>
</NavigationMenuItem>
)
}Nu is de navigatie aanzienlijk sneller, de pagina moet niet herladen worden en de gebruikerservaring (UX) is beter. Onderstaande video demonstreert dit, eens de eerste pagina geladen is, blijft het netwerk tab leeg. Enkel voor de via op de 404 pagina wordt er nog een netwerkverzoek gedaan, dit is te verwachten aangezien de video te groot is om te downloaden als deze niet nodig is.
useLocation
Alhoewel de routing nu volledig correct werkt, is het nog niet duidelijk op welke pagina we zitten, dit zou eigenlijk aangegeven moeten worden in de Navbar.
Begrip: useLocation
De useLocation uit React Router geeft informatie terug over de huidige locatie, i.e. de url.
De informatie wordt teruggeven is een object dat onder meer de pathname property bevat. Deze property bevat de het huidige pad in de webapplicatie, een url als http://example.com/foo/bar?baz=qux geeft dus /foo/bar terug.
import {useLocation} from 'react-router'
const ExampleComponent: FunctionComponent = () => {
const location = useLocation()
return (
<div>
{location.pathname === '/home' ? <h1>Home</h1> : <h1>Not Home</h1>}
</div>
)
}Via de useLocation hook kunnen we de huidige locatie van de applicatie opvragen en deze vergelijken met het pad waarnaar de link verwijst, als deze overeenkomen kunnen we de link als actief markeren via de active property.
import {Link, useLocation} from 'react-router'
const StyledNavLink: FunctionComponent<StyledNavLinkProps> = ({to, children}) => {
const location = useLocation()
const active = location.pathname === to
return (
<NavigationMenuItem>
<NavigationMenuLink className={navigationMenuTriggerStyle()} asChild
active={active}>
<Link to={to}>{children}</Link>
</NavigationMenuLink>
</NavigationMenuItem>
)
}Na deze aanpassing is de navbar volledig in orde.

Navigatie met parameters
In de startbestanden is het bestand studentApi.ts te vinden, deze file exporteert twee methodes waarmee één of meerdere (fictieve) studenten en hun score voor een bepaald vak opgehaald kunnen worden.
import students from '@/api/data/students.ts'
import {Student} from '@/models/student.ts'
export const getAllStudents = (): Student[] => {
return students.map(s => ({...s}))
}
export const getStudentById = (id: number): Student | undefined => {
// Onderstaand statement is gelijk aan this.students.filter((s) => s.id === id)[0]
// met het verschil dat je hier geen IndexOutOfBounds exception kan krijgen.
// Als er geen overeenkomst gevonden is, wordt undefined teruggegeven.
return students.find((s) => s.id === id)
}import {Student} from '@/models/student.ts'
const students: Student[] = [
{id: 565, name: 'Annelies Gevers', grade: 'A-'},
{id: 11, name: 'Ben Pauwels', grade: 'A+'},
{id: 91, name: 'Elien Stevens', grade: 'F'},
{id: 23, name: 'David Van Mol', grade: 'D'},
{id: 4002, name: 'Paul Verstraeten', grade: 'F'},
{id: 8, name: 'Sandra Wouters', grade: 'D-'},
]
export default studentsexport interface Student {
id: number
name: string
grade: string
}Elk van de studenten heeft een id, via dit id kunnen we een detail-view bouwen voor één bepaalde student. De Class component bevat al een overzicht van alle studenten, enkel de link naar de detailpagina moet nog toegevoegd worden.
Om de link toe te voegen hebben we twee opties, we kunnen een absoluut of relatief pad gebruiken.
Begrip: Relatieve en absolute paden in React Router
Een absoluut pad begint met een forward-slash (/) en bevat het volledige pad, vanaf de root.
Een relatief pad begint niet met een forward-slash, maar met het volgende deel van het pad. Stel de link is gedefinieerd in een component die zich op het pad /foo/bar bevindt, dan kunnen we in deze component een link definiëren als baz. Omdat dit pad relatief is (het bevat vooraan geen slash), zal het geïnterpreteerd worden ten opzichte van het huidige pad en wordt een gebruiker na het drukken op deze link dus naar /foo/bar/baz gestuurd.
const Class: FunctionComponent = () => {
const students = getAllStudents()
const studentItem = (s: Student, separator: boolean = true): ReactElement => (
<div key={s.id} className="hover:bg-accent rounded">
<div className="p-2">
<Link to={s.id.toString()}>{s.name}</Link>
</div>
{separator && <Separator />}
</div>
)
return (
<>
<Card>
<CardHeader>
<CardTitle>Class</CardTitle>
</CardHeader>
<CardContent>{students.map((s, i) => studentItem(s, i !== students.length - 1))}</CardContent>
<CardFooter>
<Link to={'/'}>Back</Link>
</CardFooter>
</Card>
</>
)
}Momenteel leidt de link nog naar de 404 pagina omdat we nog nergens gedefinieerd hebben dat er detail-route bestaat.
Door in de Route component waarin de /class route gedefinieerd wordt een extra Route component te plaatsen kunnen we een detail-route aanmaken.
const Routing: FunctionComponent = () => {
return (
<Routes>
<Route path={'/foo'} element={<Foo />} />
<Route path={'/bar'} element={<Bar />} />
<Route path={'/'} element={<Home />} />
<Route path={'/class'} element={<Class />}>
<Route path={':id'} element={<Student />} />
</Route>
<Route path={'*'} element={<PageNotFound />} />
</Routes>
)
}Als we nu op een detail-link klikken, zien we de URL wel wijzigen, maar wel de detail-pagina nog niet.
Begrip: Outlet
De Outlet component wordt gebruikt om geneste routes weer te geven. Zonder deze component wordt enkel de parent route getoond en worden kinderen genegeerd.
import {FunctionComponent} from 'react'
import {Routes, Route, Outlet} from 'react-router'
const Routing: FunctionComponent = () => {
return (
<Routes>
<Route path={'/foo'} element={<Foo/>}>
<Route path={'/bar'} element={<Baz/>}/>
<Route path={':param1'} element={<Qux/>}/>
</Route>
</Routes>
)
}
const Foo: FunctionComponent = () => {
return (
<>
<h1>Foo</h1>
{/**
* De code hierboven worden altijd gerenderd,
* omdat deze in de Foo (parent) component zit.
* De Baz en Qux componenten worden nooit getoond,
* tenzij er ergens een <Outlet/> gebruikt wordt.
*/}
<Outlet/>
</>
)
}import {Link, Outlet} from 'react-router'
const Class: FunctionComponent = () => {
const students = getAllStudents()
const studentItem = (s: Student, separator: boolean = true): ReactElement => (
// Niet relevant en leeg gelaten in dit fragment
)
return (
<>
<Card>
{/* Niet relevant en weggelaten in dit fragment */}
</Card>
<Outlet />
</>
)
}Onderstaand screenshot maakt het duidelijk dat dit niet is wat we willen bereiken.

De Outlet component staat, in dit geval, op de foute plaats. Als de Class component een statisch gedeelte had, dat gelijk was voor alle kinderen, zou Outlet hier wel gebruikt kunnen worden, maar omdat we de Class component volledig willen vervangen met de Student component moet de Outlet component verplaatst worden.
Door de Outlet component mee te geven aan de /class route, kunnen we de Student component renderen in plaats van de Class component. Maar dan wordt de Class component natuurlijk niet meer getoond. Om dit op te lossen voegen we een nieuwe Route component toe aan de kinderen van de /class route, deze component krijgt geen path attribuut, maar wel een index attribuut. Aan de element property geven we de Class component mee. De index property duidt de default route aan, als geen van de kinderen matcht met het ingegeven pad (in de URL-balk) wordt deze route gebruikt.
const Routing: FunctionComponent = () => {
return (
<Routes>
<Route path={'/foo'} element={<Foo />} />
<Route path={'/bar'} element={<Bar />} />
<Route path={'/'} element={<Home />} />
<Route path={'/class'} element={<Outlet />}>
<Route index element={<Class />} />
<Route path={':id'} element={<Student />} />
</Route>
<Route path={'*'} element={<PageNotFound />} />
</Routes>
)
}useParams
Momenteel toont de Students component nog steeds een foutmelding, dit is te verwachten aangezien we de navigatieparameter nog niet uitgelezen hebben. Dit kan via de useParams hook.
Begrip: useParams
De useParams hook geeft een object terug waarin elke parameter voor de actieve route beschikbaar is onder dezelfde naam als in de Route component die de parameter definieert. Naast de actieve route zijn ook alle parameters van parent routes beschikbaar.
Navigatieparameters worden via de URL doorgegeven en zijn dus altijd strings, als je een number doorgeeft, moeten de parameter eerste geconverteerd worden.
import {FunctionComponent} from 'react'
import {Routes, Route, useParams} from 'react-router'
const Routing: FunctionComponent = () => {
return (
<Routes>
<Route path={'/users'} element={<Outlet/>}>
<Route index element={<Users/>}/>
<Route path={':userId'} element={<Outlet/>}>
<Route index element={<UserDetails/>}/>
<Route path={':activityId'} element={<UserActivityDetails/>}/>
</Route>
</Route>
</Routes>
)
}
const UserActivityDetails: FunctionComponent = () => {
const {userId, activityId} = useParams()
return (
<>
...
</>
)
}import {useParams} from 'react-router'
const Student: FunctionComponent = () => {
const {id} = useParams()
const student = getStudentById(Number(id))
if (!student) {
return <div>Student could not be found</div>
}
return (
<Card>
<CardHeader>
<CardTitle>{student?.name}</CardTitle>
</CardHeader>
<CardContent>
<p>Id: {student?.id}</p>
<p>Grade: {student?.grade}</p>
</CardContent>
<CardFooter className="cursor-pointer">Back</CardFooter>
</Card>
)
}useNavigate
We maken tenslotte gebruik van de useNavigate hook om terug te gaan naar de vorige pagina.
Merk op dat er in onderstaande code gebruik gemaakt wordt van het void keyword voor de navigate functie, dit komt omdat de linting regels het niet toestaan dat we een promise in een event handler (synchroon) plaatsen, tenzij we expliciet aangeven dat we niet geïnteresseerd zijn in het resultaat.
import {useNavigate, useParams} from 'react-router'
const Student: FunctionComponent = () => {
const {id} = useParams()
const student = getStudentById(Number(id))
const navigate = useNavigate()
if (!student) {
return <div>Student could not be found</div>
}
return (
<Card>
<CardHeader>
<CardTitle>{student?.name}</CardTitle>
</CardHeader>
<CardContent>
<p>Id: {student?.id}</p>
<p>Grade: {student?.grade}</p>
</CardContent>
<CardFooter onClick={() => void navigate(-1)} className="cursor-pointer">
Back
</CardFooter>
</Card>
)
}