e
React router, tyylikirjastot
Sovelluksemme ulkoasu on tällä hetkellä melko karu:

Haluamme tilanteeseen muutoksen. Aloitetaan sovelluksen navigaatiorakenteesta.
On erittäin tyypillistä, että web-sovelluksissa on navigaatiopalkki, jonka avulla on mahdollista vaihtaa sovelluksen näkymää. Muistiinpanosovelluksemme voisi sisältää pääsivun:

ja oma sivunsa muistiinpanojen näyttämiseen:

sekä muistiinpanojen luomiseen:

Vanhan koulukunnan web-sovelluksessa sovelluksen näyttämän sivun vaihto tapahtui siten, että selain teki palvelimelle uuden HTTP GET ‑pyynnön ja renderöi sitten palvelimen palauttaman, uutta näkymää vastaavan HTML-koodin.
Single page -sovelluksissa taas ollaan todellisuudessa koko ajan samalla sivulla, ja selaimessa suoritettava JavaScript-koodi luo illuusion eri "sivuista". Jos näkymää vaihdettaessa tehdään HTTP-kutsuja, niiden avulla haetaan ainoastaan JSON-muotoista dataa, jota uuden näkymän näyttäminen ehkä edellyttää.
Navigaatiopalkki ja useita näkymiä sisältävä sovellus olisi helppo toteuttaa Reactilla, esim. siten että sovelluksen tila page muistaisi millä sivulla käyttäjä on, ja oikea näkymä renderöitäisiin tämän perusteella:
const App = () => {
const [page, setPage] = useState('home')
const toPage = (page) => (event) => {
event.preventDefault()
setPage(page)
}
const content = () => {
if (page === 'home') {
return <Home />
} else if (page === 'notes') {
return <Notes />
} else if (page === 'users') {
return <Users />
}
}
return (
<div>
<div>
<a href="" onClick={toPage('home')} >
home
</a>
<a href="" onClick={toPage('notes')}>
notes
</a>
<a href="" onClick={toPage('users')} >
users
</a>
</div>
{content()}
</div>
)
}Menetelmä ei kuitenkaan ole optimaalinen: sivuston osoite pysyy samana vaikka välillä ollaankin eri näkymässä. Jokaisella näkymällä tulisi kuitenkin olla oma osoitteensa, jotta esim. kirjanmerkkien tekeminen olisi mahdollista. Myöskään selaimen back-painike ei toimi loogisesti jos sivuja ei vastaa oma osoite, eli back ei vie edelliseksi katsottuun sovelluksen näkymään vaan jonnekin ihan muualle.
React Router
Kirjasto React Router, tarjoaa onneksi erinomaisen ratkaisun React-sovelluksen navigaation hallintaan.
Asennetaan React Router:
npm install react-router-domLuodaan uusi komponentti, joka toimii sovelluksen pääsivuna
const Home = () => {
return (
<div>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
</div>
)
}
export default HomeEriytetään sovelluksen aiempi komponentissa App ollut päänäkymä omaksi komponentiksi, Siirretään kuitenkin muistiinpanojen tilan käsittely komponentin ulkopuolelle:
// list of notes passed as parameter
const NoteList = ({ notes }) => { // content mostly same as was in component App
// reference to NoteForm is removed
}Komponentti App muuttuu nyt seuraavasti
import { useState, useEffect } from 'react'
import noteService from './services/notes'
import {
BrowserRouter as Router,
Routes, Route, Link
} from 'react-router-dom'
import NoteList from './components/NoteList'
import Home from './components/Home'
import Footer from './components/Footer'
import NoteForm from './components/NoteForm'
const App = () => {
const [notes, setNotes] = useState([])
useEffect(() => {
noteService.getAll().then(initialNotes => {
setNotes(initialNotes)
})
}, [])
const addNote = noteObject => {
noteService.create(noteObject).then(returnedNote => {
setNotes(notes.concat(returnedNote))
})
}
const padding = {
padding: 5
}
return (
<Router> <div> <Link style={padding} to="/">home</Link> <Link style={padding} to="/notes">notes</Link> <Link style={padding} to="/create">new note</Link> </div>
<Routes> <Route path="/notes" element={ <NoteList notes={notes} /> } /> <Route path="/create" element={ <NoteForm createNote={addNote}/> } /> <Route path="/" element={<Home />} /> </Routes> <Footer /> </Router> )
}
export default AppReititys eli komponenttien ehdollinen, selaimen URL:iin perustuva renderöinti otetaan käyttöön sijoittamalla komponentteja Router-komponentin lapsiksi eli Router-tagien sisälle.
Ensimmäisenä on määritelty sovelluksen navigaatiopalkki komponentin Link avulla. Attribuutti to määrittelee miten selaimen osoitetta muutetaan linkkiä klikatessa:
<div>
<Link style={padding} to="/">home</Link>
<Link style={padding} to="/notes">notes</Link>
<Link style={padding} to="/create">new note</Link>
</div>Seuraavana määritellään sovelluksen reititys komponentin Routes avulla. Komponentin sisälle määritellään Route:n avulla joukko sääntöjä ja niitä vastaavat renderöitävät komponentit:
<Routes>
<Route path="/notes" element={
<NoteList notes={notes} />
} />
<Route path="/create" element={
<NoteForm createNote={addNote}/>
} />
<Route path="/" element={<Home />} />
</Routes>Jos ollaan sovelluksen juuriosoitteessa, renderöidään komponentti Home:

Klikatessa navigaatiopalkista "notes", vaihtuu selaimen osoiterivin osoitteeksi notes, ja renderöidään komponentti NoteList:

Vastaavasti, kun klikataan "new note" osoitteeksi tulee create renderöidään komponentti NoteForm.
Normaalissa Web-sivussa selaimen osoiterivillä olevan osoitteen vaihtuminen aiheuttaa sivun uudelleenlataamisen. React Routeria käyttäen näin ei kuitenkaan tapahdtu vaan reititys tehdään täysin JavaScriptin avulla frontendissa.
Käyttämämme Router-komponentti on BrowserRouter:
import {
BrowserRouter as Router, Routes, Route, Link
} from 'react-router-dom'Dokumentaation mukaan
BrowserRouter is a Router that uses the HTML5 history API (pushState, replaceState and the popstate event) to keep your UI in sync with the URL.
BrowserRouter mahdollistaa HTML5 history API:n avulla sen, että selaimen osoiterivillä olevaa URL:ia voidaan käyttää React-sovelluksen sisäiseen "reitittämiseen", eli vaikka osoiterivillä oleva URL muuttuu, sivun sisältöä manipuloidaan ainoastaan JavaScriptillä, eikä selain lataa uutta sisältöä palvelimelta. Selaimen toiminta back- ja forward-toimintojen ja kirjanmerkkien tekemisen suhteen on kuitenkin intuitiivista eli toimii kuten perinteisillä verkkosivuilla.
Sovelluksen tämänhetkinen koodi on kokonaisuudessaan GitHubissa, branchissa part5-10.
Parametrisoitu reitti
Päätetään siirtää yksittäisen muistiinpanon tarkemmat tiedot omaan näkymään, johon päästään muistiinpanon nimeä klikkaamalla:

Nimen klikattavuus on toteutettu komponenttiin NoteList seuraavasti:
import { Link } from 'react-router-dom'
const NoteList = ({ notes }) => {
// ...
return (
<div>
<h1>Notes</h1>
<Notification message={errorMessage} />
{!user && loginForm()}
<div>
<button onClick={() => setShowAll(!showAll)}>
show {showAll ? 'important' : 'all'}
</button>
</div>
<ul>
{notesToShow.map(note => (
<li key={note.id}>
<Link to={`/notes/${note.id}`}>{note.content}</Link> </li>
))}
</ul>
</div>
)
}
export default NoteListKäytössä on siis jälleen Link. Esimerkiksi muistiinpanon, jonka id on 12345 nimen klikkaaminen aiheuttaa selaimen osoitteen arvon päivittymisen muotoon notes/12345.
Parametrisoitu URL määritellään komponentissa App olevaan reititykseen seuraavasti:
<Router>
// ...
<Routes>
<Route path="/notes/:id" element={ <Note notes={notes} toggleImportanceOf={toggleImportanceOf} /> } /> <Route path="/notes" element={<Notes notes={notes} />} />
<Route path="/users" element={user ? <Users /> : <Navigate replace to="/login" />} />
<Route path="/login" element={<Login onLogin={login} />} />
<Route path="/" element={<Home />} />
</Routes>
</Router>Yksittäisen muistiinpanon näkymän renderöivä route siis määritellään "Expressin tyyliin" merkitsemällä reitin parametrina oleva osa merkinnällä :id näin:
<Route path="/notes/:id" element={<Note notes={notes} ... />} />Kun selain siirtyy muistiinpanon yksilöivään osoitteeseen, esim. /notes/12345, renderöidään komponentti Note, jota olemme nyt hieman joutuneet muuttamaan:
import { useParams } from 'react-router-dom'
const Note = ({ notes, toggleImportance }) => {
const id = useParams().id const note = notes.find(n => n.id === id)
const label = note.important ? 'make not important' : 'make important'
return (
<li className="note">
<span>{note.content}</span>
<button onClick={() => toggleImportance(id)}>{label}</button>
</li>
)
}
export default NoteToisin kuin aiemmin, komponentti Note saa nyt parametrikseen kaikki muistiinpanot propsina notes ja se pääsee URL:n yksilöivään osaan eli näytettävän muistiinpanon id:hen käsiksi React Routerin funktion useParams avulla.
useNavigate
Backend tukee jo muistiinpanojen poistamista. Toteutetaan tätä varten nappi sovellukseen yksittäisten muistiinpanojen sivulle:

Lisätään komponenttiin App poiston suorittava käsittelijä, joka annetaan komponentille Note:
const App = () => {
const deleteNote = (id) => { noteService.remove(id).then(() => { setNotes(notes.filter(n => n.id !== id)) }) }
return (
// ...
<Routes>
<Route path="/notes/:id" element={
<Note
notes={notes}
toggleImportanceOf={toggleImportanceOf}
deleteNote={deleteNote} />
} />
<Route path="/notes" element={
<NoteList notes={notes} />
} />
<Route path="/create" element={
<NoteForm createNote={addNote}/>
} />
<Route path="/" element={<Home />} />
</Routes>
<Footer />
</Router>
)
} Komponentti Note muuttuu seuraavasti:
import { useParams, useNavigate } from 'react-router-dom'
const Note = ({ notes, toggleImportanceOf, deleteNote }) => { const id = useParams().id
const navigate = useNavigate() const note = notes.find(n => n.id === id)
const label = note.important ? 'make not important' : 'make important'
const handleDelete = () => { if (window.confirm(`Delete note "${note.content}"?`)) { deleteNote(id) navigate('/notes') } }
return (
<li className="note">
<span>{note.content}</span>
<button onClick={() => toggleImportanceOf(id)}>{label}</button>
<button onClick={handleDelete}>delete</button> </li>
)
}
export default NoteKun muistiinpano poistuu, navigoidaan takaisin kaikkien muistiinpanojen sivulle. Tämä tapahtuu kutsumalla React Routerin funktion useNavigate palauttamaa funktiota halutulla osoitteella navigate('/notes') .
Käyttämämme React Router ‑kirjaston funktiot useParams ja useNavigate ovat molemmat hook-funktioita samaan tapaan kuin esim. moneen kertaan käyttämämme useState ja useEffect. Kuten muistamme osasta 1, hook-funktioiden käyttöön liittyy tiettyjä sääntöjä.
Muutetaan myös komponenttia NoteForm siten, että uuden muistiinpanon lisäämisen jälkeen navigoidaan käyttäjä kaikkien muistiinpanojen sivulle:
import { useState } from 'react'
import { useNavigate } from 'react-router-dom'
const NoteForm = ({ createNote }) => {
const [newNote, setNewNote] = useState('')
const navigate = useNavigate()
const addNote = event => {
event.preventDefault()
createNote({
content: newNote,
important: true
})
navigate('/notes') setNewNote('')
}
return (
<div>
<h2>Create a new note</h2>
<form onSubmit={addNote}>
<input
value={newNote}
onChange={event => setNewNote(event.target.value)}
placeholder="write note content here"
/>
<button type="submit">save</button>
</form>
</div>
)
}Parametrisoitu reitti revisited
Sovelluksessa on eräs hieman ikävä seikka. Komponentti Note saa propseina kaikki muistiinpanot, vaikka se näyttää niistä ainoastaan sen, jonka id vastaa URL:n parametrisoitua osaa:
const Note = ({ notes, toggleImportance }) => {
const id = useParams().id
const note = notes.find(n => n.id === Number(id))
// ...
}Olisiko sovellusta mahdollista muuttaa siten, että Note saisi propsina ainoastaan näytettävän muistiinpanon:
import { useParams, useNavigate } from 'react-router-dom'
const Note = ({ note, id, toggleImportanceOf, deleteNote }) => { const id = useParams().id
const navigate = useNavigate()
// ...
return (
<li className="note">
<span>{note.content}</span>
<button onClick={() => toggleImportanceOf(id)}>{label}</button>
<button onClick={handleDelete}>delete</button>
</li>
)
}
export default NoteEräs tapa on selvittää näytettävän muistiinpanon id komponentissa jo App React Routerin hook-funktion useMatch avulla.
useMatch-hookin käyttö ei ole mahdollista samassa komponentissa, joka määrittelee sovelluksen reititettävän osan. Siirretään Router komponentin App ulkopuolelle:
ReactDOM.createRoot(document.getElementById('root')).render(
<Router> <App />
</Router>)Komponentti App muuttuu seuraavasti:
import {
// ...
useMatch} from 'react-router-dom'
const App = () => {
// ...
const match = useMatch('/notes/:id') const note = match ? notes.find(note => note.id === match.params.id) : null
return (
<div>
<div>
<Link style={padding} to="/">home</Link>
// ...
</div>
<Routes>
<Route path="/notes/:id" element={
<Note
note={note} toggleImportanceOf={toggleImportanceOf}
deleteNote={deleteNote}
/>
} />
<Route path="/notes" element={
<NoteList notes={notes} />
} />
<Route path="/create" element={
<NoteForm createNote={addNote}/>
} />
<Route path="/" element={<Home />} />
</Routes>
<div>
<em>Note app, Department of Computer Science 2026</em>
</div>
</div>
)
} Joka kerta, kun komponentti App renderöidään eli käytännössä aina kun sovelluksen osoiterivillä oleva URL vaihtuu, suoritetaan komento
const match = useMatch('/notes/:id')Jos URL on muotoa /notes/:id, eli vastaa yksittäisen muistiinpanon URL:ia, muuttuja match saa arvokseen olion, jonka avulla polun parametroitu osa, eli muistiinpanon id voidaan selvittää. Näin saadaan haettua renderöitävä muistiinpano:
const note = match
? notes.find(note => note.id === match.params.id)
: nullSovelluksessamme on vielä pieni vika. Jos selain uudelleen ladataan yksittäisen muistiinpanon sivulla, seurauksena on virhe:

Ongelma johtuu siitä, että sivua yritetään renderöidä ennen kuin muistiinpanot on haettu backendista. Pääsemme ongelmasta eroon ehdollisella renderöinnillä:
const Note = ({ note, toggleImportanceOf, deleteNote }) => {
const id = useParams().id
const navigate = useNavigate()
if(!note) { return null }
return (
//...
)
}Sovelluksessa on vielä eräs ikävä piirre, kirjautumiseen liittyvä logiikka on edelleen kaikki muistiinpanot listaavalla sivulla. Jätämme kuitenkin toiminnallisuuden tähän hieman vajavaiseen tilaan.
Sovelluksen tämänhetkinen koodi on kokonaisuudessaan GitHubissa, branchissa part5-11.
Osassa 2 on jo katsottu kahta tapaa tyylien lisäämiseen, eli vanhan koulukunnan yksittäistä CSS-tiedostoa ja inline-tyylejä. Katsotaan tässä osassa vielä muutamaa tapaa.
Eräs lähestymistapa sovelluksen tyylien määrittelyyn on valmiin "UI-frameworkin" eli käyttöliittymätyylikirjaston käyttö.
Ensimmäinen laajaa kuuluisuutta saanut UI-framework oli Twitterin kehittämä Bootstrap. Viime vuosina UI-frameworkeja on noussut kuin sieniä sateella. Valikoima on niin iso, ettei tyhjentävää listaa kannata edes yrittää tehdä.
Monet UI-frameworkit sisältävät web-sovellusten käyttöön valmiiksi määriteltyjä teemoja sekä "komponentteja", kuten painikkeita, menuja ja taulukkoja. Termi komponentti on edellä kirjoitettu hipsuissa sillä kyse ei ole samasta asiasta kuin React-komponentti. Useimmiten UI-frameworkeja käytetään sisällyttämällä sovellukseen frameworkin määrittelemät CSS-tyylitiedostot sekä JavaScript-koodi.
Monista UI-frameworkeista on tehty React-ystävällinen versio, jossa UI-frameworkin avulla määritellyistä "komponenteista" on tehty React-komponentteja. Esim. Bootstrapista on olemassa parikin React-versiota, joista suosituin on React-Bootstrap.
Bootstrapin sijaan katsotaan seuraavaksi tämän hetken kenties suosituinta UI-frameworkia eli Googlen kehittämän "muotokielen" Material Designin toteuttavaa React-kirjastoa MaterialUI.
Asennetaan kirjasto:
npm install @mui/material @emotion/react @emotion/styledMaterialUI:ta käytettäessa koko sovelluksen sisältö renderöidään useimmiten komponentin Container sisälle:
import { Container } from '@mui/material'
const App = () => {
// ...
return (
<Container>
// ...
</Container>
)
}Taulukko
Aloitetaan komponentista NoteList ja renderöidään muistiinpanojen lista taulukkona, joka näyttää myös muistiinpanon luoneen käyttäjän:
import { useState, useEffect } from 'react'
import { Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper } from '@mui/material'
//...
const NoteList = ({ notes }) => {
// ...
return (
<div>
// ...
<h2>Notes</h2>
<TableContainer component={Paper}>
<Table>
<TableHead>
<TableRow>
<TableCell>content</TableCell>
<TableCell>user</TableCell>
<TableCell>important</TableCell>
</TableRow>
</TableHead>
<TableBody>
{notes.map(note => (
<TableRow key={note.id}>
<TableCell>
<Link to={`/notes/${note.id}`}>
{note.content}
</Link>
</TableCell>
<TableCell>
{note.user.name}
</TableCell>
<TableCell>
{note.important ? 'yes': ''}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</div>
)
}
export default NoteListTaulukko näyttää seuraavalta:

Lomake
Parannellaan seuraavaksi uuden muistiinpanon luovaa näkymää NoteForm käyttäen komponentteja TextField ja Button:
import { TextField, Button } from '@mui/material'
// ...
const NoteForm = ({ createNote }) => {
// ...
return (
<div>
<h2>Create a new note</h2>
<form onSubmit={addNote}>
<TextField
label="note content"
value={newNote}
onChange={event => setNewNote(event.target.value)}
/>
<div>
<Button type="submit" variant="contained" style={{ marginTop: 10 }}>
save
</Button>
</div>
</form>
</div>
)
}
export default NoteFormLopputulos on elegantti:

Notifikaatio
Parannellaan sovelluksen notifikaatiot näyttävää komponenttia MaterialUI:n Alert komponentin avulla:
import { Alert } from '@mui/material'
const Notification = ({ notification }) => {
if (notification === null) {
return null
}
return (
<Alert style={{ marginTop: 10, marginBottom: 10 }} severity={notification.type}>
{notification.text}
</Alert>
)
}
export default NotificationSiirretään notifikaatiokomponentti ja sen tilan hallinta komponenttiin App:
const App = () => {
const [notes, setNotes] = useState([])
const [notification, setNotification] = useState(null)
// ...
const addNote = noteObject => {
noteService.create(noteObject).then(returnedNote => {
setNotes(notes.concat(returnedNote))
setNotification({ text: `Note '${returnedNote.content}' added!`, type: 'success' }) setTimeout(() => {
setNotification(null)
}, 5000)
})
}
return (
<Container>
<div>
<Link style={padding} to="/">home</Link>
<Link style={padding} to="/notes">notes</Link>
<Link style={padding} to="/create">new note</Link>
</div>
<Notification notification={notification} />
<Routes>
<Route path="/notes/:id" element={
<Note
note={note}
toggleImportanceOf={toggleImportanceOf}
deleteNote={deleteNote}
/>
} />
<Route path="/notes" element={
<NoteList notes={notes} setNotification={setNotification} />
} />
<Route path="/create" element={
<NoteForm createNote={addNote} />
} />
<Route path="/" element={<Home />} />
</Routes>
<Footer />
</Container>
)
}Alert on ulkoasultaan tyylikäs:

Navigaatiovalikko
Navigaatiovalikko toteutetaan komponentin AppBar avulla.
Jos sovelletaan suoraan dokumentaation esimerkkiä
<AppBar position="static">
<Toolbar>
<Button color="inherit"><Link to="/">home</Link></Button>
<Button color="inherit"><Link to="/notes">notes</Link></Button>
<Button color="inherit"><Link to="/create">new note</Link></Button>
</Toolbar>
</AppBar>saadaan kyllä toimiva ratkaisu, mutta sen ulkonäkö ei ole paras mahdollinen:

Dokumentaatiota lueskelemalla löytyy parempi tapa eli component prop, jonka avulla voidaan muuttaa se miten MaterialUI-komponentin juurielementti renderöityy.
Määrittelemällä
<Button color="inherit" component={Link} to="/">
home
</Button>renderöidään komponentti Button siten, että sen juurikomponenttina onkin react-router-dom-kirjaston komponentti Link, jolle siirtyy polun kertova prop to.
Navigaatiopalkin koodi kokonaisuudessaan on seuraava
<AppBar position="static">
<Toolbar>
<Button color="inherit" component={Link} to="/">home</Button>
<Button color="inherit" component={Link} to="/notes">notes</Button>
<Button color="inherit" component={Link} to="/create">new note</Button>
</Toolbar>
</AppBar>ja lopputulos on haluamamme kaltainen:

Huomaamme kuitenkin, että kun hiiri viedään navigaatiopalkin päälle, on hover-indikaattori liian huomaamaton. Korjataan tilanne määrittelemällä tilanteisiin hieman parempi taustaväri:
const style = { '&:hover': { bgcolor: 'rgba(255,255,255,0.3)' } }
return (
<Container>
<AppBar position="static">
<Toolbar>
<Button color="inherit" component={Link} to="/" sx={style}>
home
</Button>
<Button color="inherit" component={Link} to="/notes" sx={style}>
notes
</Button>
<Button color="inherit" component={Link} to="/create" sx={style}>
new note
</Button>
</Toolbar>
</AppBar>
// ...
)Olemme vihdoin tyytyväisiä:

Sovelluksen tämänhetkinen koodi on kokonaisuudessaan GitHubissa, branchissa part5-12.
Styled components
Tapoja liittää tyylejä React-sovellukseen on jo näkemiemme lisäksi muitakin.
Mielenkiintoisen näkökulman tyylien määrittelyyn tarjoaa ES6:n tagged template literal ‑syntaksia hyödyntävä styled-components-kirjasto.
Asennetaan styled-components ja tehdään sen avulla muistiinpanosovellukseen (versioon ennen MaterialUI:n asentamista) muutama tyylillinen muutos. Tehdään ensin kaksi tyylimäärittelyä käytettävää komponenttia:
import styled from 'styled-components'
const Button = styled.button`
background: Bisque;
font-size: 1em;
margin: 1em;
padding: 0.25em 1em;
border: 2px solid Chocolate;
border-radius: 3px;
`
const Input = styled.input`
margin: 0.25em;
width: 300px;
`Koodi luo HTML:n elementeistä button ja input tyyleillä rikastetut versiot ja sijoittaa ne muuttujiin Button ja Input.
Tyylien määrittelyn syntaksi on varsin mielenkiintoinen, sillä CSS-määrittelyt asetetaan backtick-hipsujen sisään.
Määritellyt komponentit toimivat kuten normaalit button ja input, ja sovelluksessa käytetään niitä tavanomaiseen tapaan:
const NoteForm = ({ createNote }) => {
// ...
return (
<div>
<h2>Create a new note</h2>
<form onSubmit={addNote}>
<Input> value={newNote}
onChange={event => setNewNote(event.target.value)}
placeholder="write note content here"
/>
<Button type="submit">save</Button> </form>
</div>
)
}Lomake näyttää nyt seuraavalta:

Määritellään vielä seuraavat tyylien lisäämiseen tarkoitetut komponentit, jotka ovat kaikki rikastettuja versioita div-elementistä:
const Page = styled.div`
padding: 1em;
background: papayawhip;
`
const Navigation = styled.div`
background: BurlyWood;
padding: 1em;
`
const Footer = styled.div`
background: Chocolate;
padding: 1em;
margin-top: 1em;
`Otetaan uudet komponentit käyttöön sovelluksessa:
const App = () => {
// ...
return (
<Page> <Navigation> <Link style={padding} to="/">home</Link>
<Link style={padding} to="/notes">notes</Link>
<Link style={padding} to="/create">new note</Link>
</Navigation>
<Routes>
<Route path="/notes/:id" element={
<Note
note={note}
toggleImportanceOf={toggleImportanceOf}
deleteNote={deleteNote}
/>
} />
<Route path="/notes" element={
<NoteList notes={notes} />
} />
<Route path="/create" element={
<NoteForm createNote={addNote}/>
} />
<Route path="/" element={<Home />} />
</Routes>
<Footer> Note app, Department of Computer Science, University of Helsinki 2026 </Footer> </Page> )
}Lopputulos on seuraavassa:

styled-components on nostanut tasaisesti suosiotaan viime aikoina ja tällä hetkellä näyttääkin, että se on melko monien mielestä paras tapa React-sovellusten tyylien määrittelyyn.









