Idea: Introduction to Redux Toolkit.
Background: https://redux-toolkit.js.org/introduction/getting-started og Usage Guide
Crediting: Opgaven er baseret på eksempel fra Udemy kurset: Modern React with Redux [2023]
I denne opgave skal der laves en lille React-Redux App baseret på Redux-Toolkit. Applikationen vil benytte ConfigStore til konfigurering af Redux Store samt createSlice til at oprette Slices til storen.
Der skal desuden anvendes Hooks (useSelector, useDispatch), reducers og Array-helper metoderne filter og reduce. Der benyttes desuden et moderne CSS framework Bulma ( https://bulma.io/ ) til styling.
Appen der skal udvikles skal se således ud:
Det er en lille applikation der kan benyttes til at holde "styr" på de biler der er ejet af Zealand (he he).
Det skal være muligt at:
Step 1 (Opstart - installation af Node pakker )
Opret et nyt projekt med kommandoen: 'npx create-react-app cars' i din ReactRedux mappen (eller hvor du ellers finder det passende at have dit nye projekt).
Naviger til mappen 'cd cars' og kør: 'npm install @reduxjs/toolkit react-redux bulma' for at insallere de nødvendige node-moduler inklusiv bulma til stylling af appen.
Kør 'npm start' for at afprøve at installationen er gået godt - du skulle gerne se følgende i din browser:
Når du har verificeret at appen køre slettes hele indholdet af src-mappen.
Step 2 (App.js og index.js)
Opret en ny fil i src-mappen: App.js - filen skal indeholde:
Opret tilsvarende filen index.js med følgende indhold:
Verificer at du får vist følgende i browseren:
Vi har nu fået alt "boilerplate" koden på plads og kan begynde at "designe" vores React-Redux-Toolkit app. Ved et nærmere kik på appens side kan vi se at den naturligt kan opdeles i 4 komponenter: CarForm til oprettelse af en ny bil, CarSearch til søgning/filtrering af biler, CarList til at vise bilerne og CarValue til at vise total prisen.
Step 3 (components - CarForm.js, CarList.js, CarSearch.js og CarValue.js)
Opret en ny mappe components med filerne: CarForm.js, CarList.js, CarSearch.js og CarValue.js
Alle filerne skal til at starte med blot indeholde en simpel definition på funktions komponenter ala:
Step 4 (Opdater App.js)
Tilføj import statements til de nye komponenter og tilføj tags med komponenterne så følgende vises i browseren:
Nu vi har identificeret og klargjort komponenterne er det tid til at få designet store og slices. Selve Redux Storen kommer til at indeholde en state for name, cost og searchTerm der skal indeholde values fra input-felterne samt cars der skal indeholde data om de enkelte biler. Bemærk at totalCost ikke behøver sin egen state da den kan udledes (derived) dvs beregnes ud fra data i arrayet cars.
Da name og cost naturligt hører sammen og er knyttet til samme component vil vi placere dem i samme slice: formSlice. searchTerm og cars (data) høre også naturligt sammen og vil blive placeret i samme slice: carsSlice.
Næste skridt er at bestemme hvilke reducere der skal være i de 2 slices.
Step 5 (Store og Slices - store.js, formSlice.js og carSlice.js)
Opret en mappe: store der skal indeholde mappen slices. Opret filerne formSlice.js og carsSlice.js i mappen slices under store.
a) formSlice.js
Først skal 'createSlice' importeres fra '@reduxjs/toolkit' og der skal laves et nyt slice objekt, hvor
Endeligt skal "mini-reducer-functions" og "combined-reducer" eksporters.
Bemærk: "mini-reducer-functions" er en slags "action-creators" da de returnerer et "action-objekt"
Følgende kode indsættes i formSlice.js:
b) carsSlice.js
På tilsvarende måde skal vi have carsSlice implementeret. Her vil vi benytte nanoid fra redux-toolkit til at autogenerere et unikt id.
Bemærk vi kalder staten til cars for data (så der ikke er navne sammenfald med slice'ens name).
Bemærk også at vi antager, at payload til addCar har formatet: {name: '...', cost: ..} og til removeCar formatet: id - der er id på den car der skal slettes
Følgende kode indsættes i carsSlice.js:
c) store/index.js
Først
skal configureStore importeres fra '@reduxjs/toolkit' og alle combined-reducere og mini-reducer-functions importeres fra slice'ene. Egentligt er det tilstrækkeligt at importere combined-reducers, da det er dem der skal registreres i configurationen af storen, men ved at eksportere alle mini-reducerne sammen med storen, behøver man kun en import fra storen og ikke mange sætninger med import af de enkelte slices.
Følgende kode indsættes i store/index.js:
d) src/index.js
Vi skal nu tilføje react-redux til vores app, det gøres ved at importere Provider fra 'react-redux' og vores nye store fra './store':
Følgende kode tilføjes i src/index.js:
<App> skal wrappes ind i <Provider store={store}>:
Verificer at appen stadigt køre og at der ikke er meldt fejl i consolen - det skulle gerne stadigt se således ud:
Efter vi har haft fokus på Redux delen af vores applikation er de på tide at rette fokus mod React delen og dermed få noget indhold på komponenterne.
Step 6 (CarForm.js)
Først skal vi ha udsiftet det midlertidige <div> tag med "CarList" til et <div> tag der indeholder overskriften "Add Car" samt et <form> tag med labels, input-field og submit button, så user kan indtaste name og cost til rn ny bil.
Indsæt følgende kode i CarForm.js:
Bemærk: name, handelNameChange, cost og handleCostChange i de to <input> tag samt handleSubmit i <form> tag er ikke defineret endnu!
Bemærk: i <input> til cost benyttes value = {cost || ' '} og type="number", ellers vil der default stå '0' og det kan ikke slettes samt det vil være muligt at skrive bogstaver ind i feltet.
Bemærk: der er benyttet en række className="...". det er af hensyn til senere styling.
name og cost skal bindes til storen, mere præcist til state name og state cost i formSlice, det gøres ved at benytte hook-metoden useSelector( ), fx:
Ved at benytte destructoring, kan disse linier sammenskrives til:
a) handleNameChange
Når et onChange - event "affyres" fra
et <input> kan den af useren indtastede værdi tilgås med: event.target.value. Denne value gives som argument til "mini-reduceren" changeName(event.target.value). "mini-reduceren" fungere som en action-creator som returnere et action-objekt der kan gives til dispatcheren:
b) handleCostChange
Som argument til changeCost reduceren giver vi argumentet 'parseInt(event.target.value) || 0' da value skal konverteres til et heltal, '|| 0' for at undgå NaN (Not a Number) hvis user indtaster noget der ikke er et tal:
c) handleSubmit
Vi benytter event.perventDefault( ) for at forhindre Browseren i at udføre et automatisk default submit.
Bemærk vi havde antaget at payload til addCar reduceren var af formen: {name: ... , cost: ...}, men da både name og value hedder det samme, kan det sammenskrives til blot: {name, cost}:
d) Import hooks og reducers
Til slut mangler vi blot at importere hooks og reducere samt at få adgang til dispatch'eren:
Verificer at appen stadigt køre og at der ikke er meldt fejl i consolen - det skulle gerne stadigt se således ud:
Step 7 (CarList.js)
CarList komponenten skal nu opdateres i stil med CarForm komponenten:
Gennemgå koden og se om du forstår hvad der sker, fx hvordan map anvendes og hvorfor key-attributtet sættes i <div key={car.id}> tagget.
Verificer at appen stadigt køre og at der ikke er meldt fejl i consolen - det skulle gerne stadigt se således ud:
Afprøv at der kan oprettes biler og slettes biler.
Step 8 (styles.css og src/index.js)
Det er nu tid til at lave lidt styling, du er selvfølgelig velkommen til at lave din egen css fil, men du kan også blot downloade: styles.css og indsætte den i din src-mappe. Bemærk vi benytter et css-framework der hedder Bulma: ( https://bulma.io/ ) og som blev installeret under step 1.
Husk at importe både bulma.css og style.css i src/index.js filen:
Verificer at appen ny er stylet og ser ud som følger:
Bemærk at efter submit bliver input felterne ikke nulstillet, det vil vi gøre noget ved i næste step.
Step 9 (Reset af form inputfelter efter submit - CarForm.js, formSlice.js)
Umiddelbart er det nærliggende i at tilføje: dispatch(changeCost(0) og dispatch(changeName(' ') efter dispatch(addCar(...) i
handleSubmit funktionen.
Afprøv at det faktisk virker.
Det er i midlertid ikke en skalerbar løsning bare at kalde en række dispatch'er funktioner til reset af større forms med mange input felter. Vi vil istedet benytte en alternativ løsning og udnytte at når der trykkes på submit button vil den kalde handleSubmit der igen
kalder dispatch med et action-objekt af formen: {type: 'cars/addCar'} (toolkit laver dette objekt for os). Dispatcheren vil automatisk kalle alle Combined-reducere (dvs både Cars og Form). Pt er det kun Cars reduceren addCar der håndtere denne type action, men vi kan extende Form reduceren til også at håndtere denne type actions vha extraReducers.
Tilføj den ekstra reducer til formSlice i formSlice.js (under reducers - ps husk komma efter reducers: {...}, ):
og importer addCar fra './carsSlice':
Afprøv at det virker.
Step 10 (CarSearch.js)
I dette step skal vi implementere CarSearch komponenten færdig, erstat den eksisterende kode med:
Gennemgå og forstå koden (minder meget om de øvrige komponenter), afprøv at det virker som forventet.
Step 11 (CarValue.js)
I dette step skal vi implementere CarValue komponenten færdig.
CarValue komponenten har en "derived state" totalCost der kan beregnes ved at filtrerer bil arrayet data så det kun indeholder biler med et name hvor searchTerm indgår og så beregne den samlede sum af deres cost.
Her benyttes både array-helper metoderne filter og reduce, desuden benyttes "destructoring" af state da vi kun er interesseret i data og searchTerm ifm beregningen.
Erstat den eksisterende kode i CarValue.js med:
Gennemgå koden og verificer at totalCost virker og bliver vist i Browseren:
Congratulation! - Yes Redux-Toolkit is still amazing!
/ Henrik H