Les 3: Single Page Applications

Sebastiaan HenauOngeveer 9 minuten

Les 3: Single Page Applications

Tot nu toe hebben we enkel React applicaties ontwikkeld met één pagina, dit is natuurlijk niet voldoende voor een realistische website. De meeste websites bestaan uit meerdere pagina's. Deze les bekijken we hoe we navigatie kunnen toevoegen aan een React applicatie. Daarnaast integreren we een CSS-framework in de webapplicaties.

Startbestandenopen in new window

React Router installeren

Een React project, aangemaakt via pnpm create vite, bevat de nodige bibliotheken voor routing nog niet. Om routing te integreren in een React app zijn twee nieuwe bibliotheken nodig.

De eerste bibliotheek is react-routeropen in new window, deze implementeert de core routing functionaliteiten, ongeacht het platform waarop de React app draait. Vervolgens is ook react-router-domopen in new window nodig. Deze bibliotheek implementeert routing specifiek voor een webapplicatie. Er bestaat een alternatief pakket react-router-nativeopen in new window dat gebruikt kan worden om mobile applications te ontwikkelen met React Native.

Het is niet nodig om zowel react-router als react-router-dom te installeren, deze laatste bibliotheek installeert de eerste automatisch als een dependency.

pnpm add react-router-dom

React Bootstrap installeren

Bootstrap is een eenvoudig CSS-framework voor het ontwikkelen van responsieve websites, dat al bekend is uit andere vakken. Het is mogelijk om de standaard Bootstrap versie te gebruiken in een React project, maar dit is niet echt ideaal. De componenten die via pure CSS-code gebouwd worden zijn geen probleem, maar Bootstrap maakt voor verschillende componenten gebruik van JavaScript-code. Deze JavaScript is niet geschreven voor React en kan problemen geven. Daarom voorziet React Bootstrapopen in new window een volledige implementatie van Bootstrap via React componenten.

pnpm add react-bootstrap

React Bootstrap steunt nog steeds op de standaard Bootstrap CSS-regels, de nodige stylesheets kunnen via pnpm geïnstalleerd worden.

pnpm add bootstrap

Vervolgens moet de Bootstrap CSS geïmporteerd worden in main.tsx.

import 'bootstrap/dist/css/bootstrap.min.css'

We bouwen een applicatie die 4 pagina's bevat, Home, Foo, Bar, en Class. Voor we routing kunnen implementeren moet het mogelijk zijn om naar deze pagina's te navigeren. Hiervoor creëren we een eenvoudige navigatiebalk.

import styled from 'styled-components'
import {FunctionComponent} from 'react'

const NavUL = styled.ul`
  list-style: none;
`

const NavLi = styled.li`
  display: inline-block;
  margin: 1em;
`

const NavBarNoBootstrap: FunctionComponent = () => {
    return (
        <NavUL>
            <NavLi><a href={'/'}>Home</a></NavLi>
            <NavLi><a href={'/foo'}>Foo</a></NavLi>
            <NavLi><a href={'/bar'}>Bar</a></NavLi>
            <NavLi><a href={'/class'}>Class</a></NavLi>
        </NavUL>
    )
}
export default NavBarNoBootstrap

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, 3 componenten die aangeboden worden door react-router-dom, kunnen we specifiëren welke componenten getoond moeten worden voor elke route.

De BrowserRouter component moet rond de volledige applicatie staat, 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-dom'

root.render(
    <StrictMode>
        <BrowserRouter>
            <NavBarNoBootstrap/>
        </BrowserRouter>
    </StrictMode>
)
 



 

 


Routes & Route

Begrip: Route & Routes

De Routes component bevat één of meerdere Route componenten, via de Route componenten kan aangegeven worden welke componenten getoond moeten worden voor welke route. Een Route component gebruikt de property path om de route te definiëren.

Deze componenten worden aangeboden door react-routeropen in new window.

import {Routes, Route} from 'react-router-dom'

const Routing: FunctionComponent = () => {
    return (
        <Routes>
            <Route path={'/'} element={<HomeComponent/>}/>
            <Route path={'/about'} element={<AboutComponent/>}/>
            <Route path={'/pricing'} element={<PricingComponent/>}/>
        </Routes>
    )
}
import {Route, Routes} from 'react-router-dom'

const Routing: FunctionComponent = () => {
    return (
        <Routes>
            <Route path={'/foo'}/>
        </Routes>
    )
}
 



 
 
 


Deze route wordt dus aangesproken wanneer de route '/foo' ingegeven wordt in de adresbalk. De tweede belangrijke property is element deze geeft aan welke component geladen moet worden als de opgegeven route in de adresbalk ingevuld wordt. Onderstaande code toont dus de component Foo als de route '/foo' ingegeven wordt in de adresbalk.

We gebruiken dezelfde syntax om ook de andere routes toe te voegen.

Natuurlijk moet de Routing component zelf ook nog opgeroepen worden, dit doen we vlak onder de navbar.

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>
    )
}



 






In bovenstaande code gebruiken we de React Bootstrap Container componentopen in new window. Deze component wordt, net zoals alle andere Bootstrap componenten, individueel geïmporteerd. Dit zorgt voor een kleinere bundle, hoe kleiner de bundle, hoe sneller de website. Daarnaast gebruiken we de Bootstrap klasse mt-4 om een top-marge toe te voegen.

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. Om zo'n pad te selecteren maken we gebruik van het wildcard (*) karakter. Onderstaande route komt overeen met alle mogelijke routes. We voegen ook een link toe aan de NavBarNoBootstrap component om de 404 pagina te kunnen testen.

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 gegeven in de startbestanden (de useCountdown hook mag je voorlopig nog negeren, we bekijken in een volgende les hoe je deze zelf kan schrijven). Als je de 404 pagina opent, zie je voorlopig een countdown van 5 naar 0, maar als deze afgelopen is, gebeurt er nog niets.

Figuur 1: 404 Pagina, zonder redirect

Om de PageNotFound pagina te laten werken, kunnen we gebruik maken van de Navigate component.

Begrip: Navigate

De Navigateopen in new window component wordt aangeboden door React router en kan gebruikt worden om de gebruiker door te sturen naar een nieuwe locatie zonder dat er op een link gedrukt moet worden. Deze component wordt typisch gebruikt om het renderen van een andere component vroegtijdig af te breken, bijvoorbeeld als er niet voldaan is aan een bepaalde voorwaarde (ingelogd, rechten, ...).

De Navigate component heeft één property to waarmee het pad waarnaar de gebruiker doorverwezen moet worden aangeduid wordt.

import {Navigate} from 'react-router-dom'

const ExampleComponent: FunctionComponent = () => {
    
    if (someCondition) {
        return <Navigate to={'/login'}/>
    }
    
    return (
        <>
            ...
        </>
    )
}
import {Navigate} from 'react-router-dom'

const PageNotFound: FunctionComponent = () => {
    const countdown = useCountdown(5)

    if (countdown === 0) {
        return <Navigate to={'/'}/>
    }

    return (
        <div>
            <Video404 src={gif} autoPlay={true} loop={true}/>
            <CounterContainer>
                Redirecting to home in {countdown} seconds
            </CounterContainer>
        </div>

    )
}
 





 












De routing werkt nu, alle pagina's kunnen bezocht worden. Het duurt 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.

Figuur 2: Anchor tags herladen de volledige pagina

Het probleem is het gebruik van anchor (<a>) tags in de NavBarNoBootstrap 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 willen downloaden, dit moet natuurlijk door react/react-router-dom afgehandeld worden.

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-dom';

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>
)






 






 



De NavBarNoBootstrap component wordt dan

import {NavLink} from 'react-router-dom'

const NavBarNoBootstrap: FunctionComponent = () => {
    const activeStyle: CSSProperties = {
        color: '#49DE73',
    }

    const chooseStyle = ({isActive}: { isActive: boolean }): CSSProperties => {
        return isActive ? activeStyle : {}
    }

    return (
        <NavUL>
            <NavLi>
                <NavLink to={'/'} style={chooseStyle}>Home</NavLink>
            </NavLi>
            <NavLi>
                <NavLink to={'/foo'} style={chooseStyle}>Foo</NavLink>
            </NavLi>
            <NavLi>
                <NavLink to={'/bar'} style={chooseStyle}>Bar</NavLink>
            </NavLi>
            <NavLi>
                <NavLink to={'/class'} style={chooseStyle}>Class</NavLink>
            </NavLi>
            <NavLi>
                <NavLink to={'/thisLinkProducesA404Error'} style={chooseStyle}>Error page</NavLink>
            </NavLi>
        </NavUL>
    )
}
 













 


 


 


 


 




Nu is de navigatie aanzienlijk sneller, de pagina moet niet herladen en de gebruikerservaring (UX) is beter. Onderstaande video demonstreert dit, enkel de eerste keer dat de pagina geladen wordt, zie je veel beweging in het network tab.

Figuur 3: SPA Links, geen dubbele netwerk requests

In de startbestanden is studentApi.ts te vinden, deze file exporteert twee methodes waarmee één of meerdere studenten en hun score voor een bepaald vak opgehaald kunnen worden.

import students from '../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)
}

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 opgevuld 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): ReactElement => (
        <ListGroupItem key={s.id}>
            <Link to={`${s.id}`}>
                {s.name}
            </Link>

        </ListGroupItem>
    )

    return (
        <Card>
            <Card.Header>Class</Card.Header>

            <ListGroup>
                {students.map(s => studentItem(s))}
            </ListGroup>
            <Card.Footer className="text-muted">
                <Link to={'/'}>Back</Link>
            </Card.Footer>
        </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. De parameter, het id van de student, wordt via een dubbelpunt aangegeven.

Begrip: Navigatie parameter

Een navigatie parameter wordt aan een route toegevoegd via een dubbelpunt. Stel dat we een parameter willen toevoegen aan het pad /voorbeeld, dan kunnen we dit noteren als /voorbeeld/:param1. Deze notatie kan uitgebreid worden naar meerdere parameters, /voorbeeld/:param1/:param2. Hier zijn param1 en param2 de namen die gebruikt moeten worden om de waarde van de parameter uit te lezen in de component die aan de route gelinkt is.

Een route met een navigatie parameter kan gedefinieerd wordt als één geheel, of als relatief kind van een andere route.

import {FunctionComponent} from 'react'
import {Routes, Route} from 'react-router-dom'

const Routing: FunctionComponent = () => {
    return (
        <Routes>
            <Route path={'/example/:param1'} element={<SomeComponent/>}/>
        </Routes>
    )
}






 



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.

Figuur 4: Detailpagina wordt niet geopend

Begrip: Outlet

De Outletopen in new window 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-dom'

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-dom'

const Class: FunctionComponent = () => {
    const students = getAllStudents()

    const studentItem = (s: Student): ReactElement => (
        // Niet relevant en leeg gelaten in dit fragment
    )

    return (
        <>
            <Card>
               {/* Niet relevant en weggelaten in dit fragment */}
            </Card>

            <Outlet/>
        </>
    )
}
 














 



Figuur 5: Detailpagina getoond, niet correct

Het is 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.

In dit geval, willen we geen statische delen tonen, maar een volledig nieuwe component voor de paden /class en /class:id. Om dit te bereiken passen we de Routing component nogmaals aan. We voegen een kind toe aan de /class route zonder een path, maar met de index property, vervolgens koppelen we hier de Class component aan. De index property duidt de default route aan, als geen van de kinderen matcht met het ingegeven pad (in de URL-balk). Het element dat gerenderd wordt door de Class component wordt tenslotte ingesteld op de Outlet component.

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 useParamsopen in new window 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-dom'

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-dom'

const Student: FunctionComponent = () => {
    const {id} = useParams()
    const student = getStudentById(Number(id))

    if (!student) {
        return <div>Student could not be found</div>
    }

    return (
        <Card>
            <Card.Header>{student?.name}</Card.Header>
            <Card.Body>
                <Card.Text>Id: {student?.id}</Card.Text>
                <Card.Text>Grade: {student?.grade}</Card.Text>
            </Card.Body>
            <Card.Footer>
                <div>Back</div>
            </Card.Footer>
        </Card>
    )
}
 


 
 


















useNavigate

We maken tenslotte gebruik van de useNavigate hook om terug te gaan naar de vorige pagina.

Begrip: Navigate

De useNavigateopen in new window hook geeft een functie terug die exact één argument heeft, de URL waarnaar genavigeerd moet worden. Dit kan een absoluut pad zijn, zoals '/class', maar kan ook een negatief getal zijn, zoals 1-1. Hier betekent de - dat we terug willen gaan in de navigatiegeschiedenis en de 11 dat we één pagina terug willen gaan. Een parameter 5-5 zou dus betekenen dat we 5 pagina's terug willen gaan.

import {useNavigate} from 'react-router-dom'

const ExampleComponent: FunctionComponent = () => {
    const navigate = useNavigate()
    
    return (
        <button onclick={() => navigate(-1)}>Go back</button>
    )
}

Begrip: Navigate vs useNavigate

De useNavigateopen in new window hook en Navigateopen in new window component hebben een gelijkaardige functie.

Als je het renderen wilt onderbreken en redirecten gebruik je de Navigate component. Dit is een component en als je die teruggeeft vanuit een FunctionComponent wordt de functie correct beëindigt en kan je redirecten.

Als je wil redirecten na een user-event zoals click, gebruik je de useNavigate hook.

import {useNavigate, useParams} from 'react-router-dom'

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>
            <Card.Header>{student?.name}</Card.Header>
            <Card.Body>
                <Card.Text>Id: {student?.id}</Card.Text>
                <Card.Text>Grade: {student?.grade}</Card.Text>
            </Card.Body>
            <Card.Footer>
                <div onClick={() => navigate(-1)}>Back</div>
            </Card.Footer>
        </Card>
    )
}
 




 













 




In de vorige voorbeelden hebben we de NavBarNoBootstrap component gebruikt. Natuurlijk willen we de navigatiebalk ook via React Bootstrap opbouwen. In de documentatieopen in new window vinden we een voorbeeld dat eenvoudig aan te passen is voor onze website.

import {Container, Nav, Navbar} from 'react-bootstrap'

const NavBarBootstrap: FunctionComponent = () => {
    return (
        <Navbar bg="dark" expand="lg" variant="dark">
            <Container fluid>
                <Navbar.Brand href="/">Les 3: SPA</Navbar.Brand>
                <Navbar.Toggle aria-controls="basic-navbar-nav"/>
                <Navbar.Collapse id="basic-navbar-nav">
                    <Nav>
                        <Nav.Link href={'/'}>Home</Nav.Link>
                        <Nav.Link href={'/foo'}>Foo</Nav.Link>
                        <Nav.Link href={'/bar'}>Bar</Nav.Link>
                        <Nav.Link href={'/class'}>Class</Nav.Link>
                        <Nav.Link href={'/thisLinkProducesA404Error'}>Error page</Nav.Link>
                    </Nav>
                </Navbar.Collapse>
            </Container>
        </Navbar>
    )
}

Net zoals toen we hierboven anchor tags gebruikt hebben, wordt tijdens het navigeren met de React Bootstrap navbar, ook alles herladen. We kunnen hier de Nav.Link component echter niet zomaar vervangen met een Link of NavLink component. De Nav.Link component bevat namelijk Bootstrap klassen die niet aanwezig zijn in de react-router componenten. Om toch SPA links te gebruiken, en geen opmaak te verliezen, kunnen we een library installeren die integratie tussen react-router en react-bootstrap voorziet. We installeren ook de bijhorende TypeScript definitions zodat we type checking kunnen gebruiken.

pnpm add react-router-bootstrap
pnpm add --save-dev @types/react-router-bootstrap
import {LinkContainer} from 'react-router-bootstrap';

const NavBarBootstrap: FunctionComponent = () => {
    return (
        <Navbar bg="dark" expand="lg" variant="dark">
            <Container fluid>
                <LinkContainer to={'/'}>
                    <Navbar.Brand>Les 3: SPA</Navbar.Brand>
                </LinkContainer>
                <Navbar.Toggle aria-controls="basic-navbar-nav"/>
                <Navbar.Collapse id="basic-navbar-nav">
                    <Nav>
                        <LinkContainer to={'/'}>
                            <Nav.Link>Home</Nav.Link>
                        </LinkContainer>
                        <LinkContainer to={'/foo'}>
                            <Nav.Link>Foo</Nav.Link>
                        </LinkContainer>
                        <LinkContainer to={'/bar'}>
                            <Nav.Link>Bar</Nav.Link>
                        </LinkContainer>
                        <LinkContainer to={'/class'}>
                            <Nav.Link>Class</Nav.Link>
                        </LinkContainer>
                        <LinkContainer to={'/thisLinkProducesA404Error'}>
                            <Nav.Link>Error page</Nav.Link>
                        </LinkContainer>
                    </Nav>
                </Navbar.Collapse>
            </Container>
        </Navbar>
    )
}
 





 

 



 

 
 

 
 

 
 

 









Voorbeeldcode

Volledig uitgewerkte lesvoorbeelden met commentaaropen in new window

Laatst geüpdate:
Bijdragers: Sebastiaan Henau