b
React ja GraphQL
Toteutetaan seuraavaksi React-sovellus, joka käyttää toteuttamaamme GraphQL-palvelinta. Palvelimen tämänhetkinen koodi on kokonaisuudessaan GitHubissa, branchissa part8-3.
GraphQL:ää on periaatteessa mahdollista käyttää HTTP POST ‑pyyntöjen avulla. Seuraavassa esimerkki Postmanilla tehdystä kyselystä.

Kommunikointi tapahtuu siis osoitteeseen http://localhost:4000/graphql kohdistuvina POST-pyyntöinä, ja itse kysely lähetetään pyynnön mukana merkkijonona avaimen query arvona.
Voisimmekin hoitaa React-sovelluksen ja GraphQL:n kommunikoinnin Axiosilla. Tämä ei kuitenkaan ole useimmiten järkevää ja on parempi idea käyttää korkeamman tason kirjastoa, joka pystyy abstrahoimaan kommunikoinnin turhia detaljeja. Tällä hetkellä järkeviä vaihtoehtoja on kaksi: Facebookin Relay ja Apollo Client. Näistä Apollo on ylivoimaisesti suositumpi ja myös meidän valintamme.
Apollo client
Luodaan uusi React-sovellus ja asennetaan sovellukseen Apollo Clientin vaatimat riippuvuudet.
npm install @apollo/client graphqlKorvataan tiedoston main.jsx oletussisältö seuraavalla ohjelmarungolla:
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import App from './App.jsx'
import { ApolloClient, gql, HttpLink, InMemoryCache } from '@apollo/client'
const client = new ApolloClient({
link: new HttpLink({
uri: 'http://localhost:4000',
}),
cache: new InMemoryCache(),
})
const query = gql`
query {
allPersons {
name
phone
address {
street
city
}
id
}
}
`
client.query({ query }).then((response) => {
console.log(response.data)
})
createRoot(document.getElementById('root')).render(
<StrictMode>
<App />
</StrictMode>,
)Koodi aloittaa luomalla client-olion, jonka avulla se lähettää kyselyn palvelimelle:
client.query({ query }).then((response) => {
console.log(response.data)
})Palvelimen palauttama vastaus tulostuu konsoliin:

Kyselyn muodostavan template literalin eteen on lisätty gql-tagi, joka importataan @apollo/client-paketista:
import { ApolloClient, gql, HttpLink, InMemoryCache } from '@apollo/client'
// ...
const query = gql` query {
allPersons {
name
phone
address {
street
city
}
id
}
}
`Tagin ansiosta VS Coden GraphQL-lisäosa ja muut ohjelmistotyökalut tunnistavat määrittelyn GraphQL:ksi, ja esimerkiksi editorin syntaksikorostus alkaa toimia. Palvelinpuolella teimme saman asian lisäämällä template literalin eteen tyypin määrittävän kommentin, koska palvelinpuolella käytettävä @apollo/server-kirjasto ei sisällä vastaavaa gql-tagia.
Sovellus pystyy siis kommunikoimaan GraphQL-palvelimen kanssa olion client välityksellä. Client saadaan sovelluksen kaikkien komponenttien saataville käärimällä komponentti App komponentin ApolloProvider lapseksi:
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import App from './App.jsx'
import { ApolloClient, gql, HttpLink, InMemoryCache } from '@apollo/client'
import { ApolloProvider } from '@apollo/client/react'
const client = new ApolloClient({
link: new HttpLink({
uri: 'http://localhost:4000',
}),
cache: new InMemoryCache(),
})
// ...
createRoot(document.getElementById('root')).render(
<StrictMode>
<ApolloProvider client={client}> <App />
</ApolloProvider> </StrictMode>,
)Kyselyjen tekeminen
Olemme valmiina toteuttamaan sovelluksen päänäkymän, joka listaa kaikkien henkilöiden puhelinnumerot.
Apollo Client tarjoaa muutaman vaihtoehtoisen tavan kyselyjen tekemiselle. Tämän hetken vallitseva käytäntö on hook-funktion useQuery käyttäminen.
Tehdään kysely tiedostossa App.jsx. Koodi näyttää seuraavalta:
import { gql } from '@apollo/client'
import { useQuery } from '@apollo/client/react'
const ALL_PERSONS = gql`
query {
allPersons {
name
phone
id
}
}
`
const App = () => {
const result = useQuery(ALL_PERSONS)
if (result.loading) {
return <div>loading...</div>
}
return (
<div>
{result.data.allPersons.map(p => p.name).join(', ')}
</div>
)
}
export default AppHook-funktion useQuery kutsuminen suorittaa parametrina annetun kyselyn. Hookin kutsuminen palauttaa olion, jolla on useita kenttiä. Kenttä loading on arvoltaan tosi, jos kyselyyn ei ole saatu vielä vastausta. Tässä tilanteessa renderöitävä koodi on
if ( result.loading ) {
return <div>loading...</div>
}Kun tulos on valmis, otetaan tuloksen kentästä data kyselyn allPersons vastaus ja renderöidään luettelossa olevat nimet ruudulle.
<div>
{result.data.allPersons.map(p => p.name).join(', ')}
</div>Eriytetään henkilöiden näyttäminen omaan komponenttiinsa tiedostoon src/components/Persons.jsx:
const Persons = ({ persons }) => {
return (
<div>
<h2>Persons</h2>
{persons.map(p =>
<div key={p.name}>
{p.name} {p.phone}
</div>
)}
</div>
)
}
export default PersonsKomponentti App siis hoitaa edelleen kyselyn ja välittää tuloksen uuden komponentin renderöitäväksi:
import { gql } from '@apollo/client'
import { useQuery } from '@apollo/client/react'
import Persons from './components/Persons'
// ...
const App = () => {
const result = useQuery(ALL_PERSONS)
if (result.loading) {
return <div>loading...</div>
}
return <Persons persons={result.data.allPersons} />}Nimetyt kyselyt ja muuttujat
Toteutetaan sovellukseen ominaisuus, jonka avulla on mahdollisuus nähdä yksittäisen henkilön osoitetiedot. Palvelimen tarjoama kysely findPerson sopii hyvin tarkoitukseen.
Edellisessä luvussa tekemissämme kyselyissä parametri oli kovakoodattuna kyselyyn:
query {
findPerson(name: "Arto Hellas") {
phone
city
street
id
}
}Kun teemme kyselyjä ohjelmallisesti, on kyselyn parametrit pystyttävä antamaan dynaamisesti.
Tähän tarkoitukseen sopivat GraphQL:n muuttujat. Muuttujia käyttääksemme on kysely myös nimettävä.
Sopiva muoto kyselylle on seuraava:
query findPersonByName($nameToSearch: String!) {
findPerson(name: $nameToSearch) {
name
phone
address {
street
city
}
}
}Kyselyn nimenä on findPersonByName, ja se saa yhden merkkijonomuotoisen parametrin $nameToSearch.
Myös Apollo Explorer mahdollistaa muuttujia sisältävän kyselyjen tekemisen. Tällöin muuttujille on annettava arvot kohdassa Variables:

Äsken käyttämämme useQuery toimii hyvin tilanteissa, joissa kysely on tarkoitus suorittaa heti komponentin renderöinnin yhteydessä. Nyt kuitenkin haluamme tehdä kyselyn vasta siinä vaiheessa kun käyttäjä haluaa nähdä jonkin henkilön tiedot, eli kysely tehdään vasta sitä tarvittaessa.
Yksi mahdollisuus olisi käyttää tässä tilanteessa hookia useLazyQuery jonka avulla on mahdollista muodostaa kysely joka suoritetaan siinä vaiheessa kun käyttäjä haluaa nähdä yksittäisen henkilön tulokset.
Päädymme kuitenkin nyt siistimpään ratkaisuun hyödyntämällä useQuery:n optiota skip, jonka avulla voidaan määritellä kyselyjä, joita ei suoriteta jos jokin ehto on tosi.
Muutosten jälkeen tiedosto Persons.jsx näyttää seuraavalta:
import { useState } from 'react'
import { gql } from '@apollo/client'
import { useQuery } from '@apollo/client/react'
const FIND_PERSON = gql`
query findPersonByName($nameToSearch: String!) {
findPerson(name: $nameToSearch) {
name
phone
id
address {
street
city
}
}
}
`
const Person = ({ person, onClose }) => {
return (
<div>
<h2>{person.name}</h2>
<div>
{person.address.street} {person.address.city}
</div>
<div>{person.phone}</div>
<button onClick={onClose}>close</button>
</div>
)
}
const Persons = ({ persons }) => {
const [nameToSearch, setNameToSearch] = useState(null) const result = useQuery(FIND_PERSON, { variables: { nameToSearch }, skip: !nameToSearch, })
if (nameToSearch && result.data) { return ( <Person person={result.data.findPerson} onClose={() => setNameToSearch(null)} /> ) }
return (
<div>
<h2>Persons</h2>
{persons.map((p) => (
<div key={p.name}>
{p.name} {p.phone}
<button onClick={() => setNameToSearch(p.name)}> show address </button> </div>
))}
</div>
)
}
export default PersonsKoodi on muuttunut paljon, ja kaikki lisäykset eivät ole täysin ilmeisiä.
Jos henkilön yhteydessä olevaa nappia show address painetaan, asetetaan henkilön nimi tilan nameToSearch arvoksi:
<button onClick={() => setNameToSearch(p.name)}>
show address
</button>Tämä saa aikaan sen, että komponentti renderöidään uudelleen. Renderöinnin yhteydessä suoritetaan kysely FIND_PERSON eli henkilön tarkempien tietojen haku jos muuttujalla nameToSearch on arvo:
const result = useQuery(FIND_PERSON, {
variables: { nameToSearch },
skip: !nameToSearch,})Eli jos yksittäisen henkilön osoitetietoja ei haluta näkyviin on nameToSearch arvo null ja kyselyä ei suoriteta.
Jos tilalla nameToSearch on arvo ja kyselyn suoritus on valmis, renderöidään komponentin Person avulla yksittäisen henkilön tarkemmat tiedot:
if (nameToSearch && result.data) {
return (
<Person
person={result.data.findPerson}
onClose={() => setNameToSearch(null)}
/>
)
}Yksittäisen henkilön näkymä on seuraavanlainen:

Yksittäisen henkilön näkymästä palataan kaikkien henkilöiden näkymään sijoittamalla tilan muuttujan nameToSearch arvoksi null.
Sovelluksen tämänhetkinen koodi on kokonaisuudessaan GitHubissa, branchissa part8-1.
Välimuisti ja Devtools
Kun haemme monta kertaa esim. Arto Hellaksen tiedot, huomaamme selaimen developer-konsolin välilehteä Network seuraamalla mielenkiintoisen asian: kysely backendiin tapahtuu ainoastaan tietojen ensimmäisellä katsomiskerralla. Tämän jälkeen, siitäkin huolimatta, että koodi tekee saman kyselyn uudelleen, ei kyselyä lähetetä backendille.
Apollo client tallettaa kyselyjen tulokset cacheen eli välimuistiin ja optimoi suoritusta siten, että jos kyselyn vastaus on jo välimuistissa, ei kyselyä lähetetä ollenkaan palvelimelle.
Chromeen on mahdollista asentaa lisäosa Apollo Client Devtools, jonka avulla voidaan tarkastella mm. välimuistin tilaa:

Välimuisti näyttää Arto Hellaksen osoitetiedot kyselyn findPerson jälkeen:

Mutaatioiden tekeminen
Toteutetaan sovellukseen mahdollisuus uusien henkilöiden lisäämiseen.
Edellisessä luvussa kovakoodasimme mutaatioiden parametrit. Tarvitsemme nyt muuttujia käyttävän version henkilön lisäävästä mutaatiosta:
const CREATE_PERSON = gql`
mutation createPerson(
$name: String!
$street: String!
$city: String!
$phone: String
) {
addPerson(name: $name, street: $street, city: $city, phone: $phone) {
name
phone
id
address {
street
city
}
}
}
`Mutaatioiden tekemiseen sopivan toiminnallisuuden tarjoaa hook-funktio useMutation.
Tehdään sovellukseen uusi komponentti PersonForm uuden henkilön lisämiseen. Tiedoston src/components/PersonForm.jsx sisältö on seuraava:
import { useState } from 'react'
import { gql } from '@apollo/client'
import { useMutation } from '@apollo/client/react'
const CREATE_PERSON = gql`
mutation createPerson(
$name: String!
$street: String!
$city: String!
$phone: String
) {
addPerson(name: $name, street: $street, city: $city, phone: $phone) {
name
phone
id
address {
street
city
}
}
}
`
const PersonForm = () => {
const [name, setName] = useState('')
const [phone, setPhone] = useState('')
const [street, setStreet] = useState('')
const [city, setCity] = useState('')
const [createPerson] = useMutation(CREATE_PERSON)
const submit = (event) => {
event.preventDefault()
createPerson({ variables: { name, phone, street, city } })
setName('')
setPhone('')
setStreet('')
setCity('')
}
return (
<div>
<h2>create new</h2>
<form onSubmit={submit}>
<div>
name <input value={name}
onChange={({ target }) => setName(target.value)}
/>
</div>
<div>
phone <input value={phone}
onChange={({ target }) => setPhone(target.value)}
/>
</div>
<div>
street <input value={street}
onChange={({ target }) => setStreet(target.value)}
/>
</div>
<div>
city <input value={city}
onChange={({ target }) => setCity(target.value)}
/>
</div>
<button type='submit'>add!</button>
</form>
</div>
)
}
export default PersonFormLomakkeen koodi on suoraviivainen, mielenkiintoiset rivit on korostettu. Mutaation suorittava funktio saadaan luotua useMutation-hookin avulla. Hook palauttaa kyselyfunktion taulukon ensimmäisenä alkiona:
const [createPerson] = useMutation(CREATE_PERSON)Kyselyä tehtäessä määritellään kyselyn muuttujille arvot:
createPerson({ variables: { name, phone, street, city } })Otetaan PersonForm-komponentti käyttöön tiedostossa App.jsx:
import { gql } from '@apollo/client'
import { useQuery } from '@apollo/client/react'
import PersonForm from './components/PersonForm'import Persons from './components/Persons'
// ...
const App = () => {
const result = useQuery(ALL_PERSONS)
if (result.loading) {
return <div>loading...</div>
}
return ( <div> <Persons persons={result.data.allPersons} /> <PersonForm /> </div> )}
export default AppLisäys kyllä toimii, mutta sovelluksen näkymä ei päivity. Syynä tälle on se, että Apollo Client ei osaa automaattisesti päivittää sovelluksen välimuistia, se sisältää edelleen ennen lisäystä olevan tilanteen. Saamme kyllä uuden käyttäjän näkyviin uudelleenlataamalla selaimen, sillä Apollon välimuisti nollautuu uudelleenlatauksen yhteydessä. Tilanteeseen on kuitenkin pakko löytää joku järkevämpi ratkaisu.
Välimuistin päivitys
Ongelma voidaan ratkaista muutamallakin eri tavalla. Eräs tapa on määritellä kaikki henkilöt hakeva kysely pollaamaan palvelinta, eli suorittamaan kysely palvelimelle toistuvasti tietyin väliajoin.
Muutos on pieni, määritellään pollausväliksi kaksi sekuntia:
const App = () => {
const result = useQuery(ALL_PERSONS, {
pollInterval: 2000 })
if (result.loading) {
return <div>loading...</div>
}
return (
<div>
<Persons persons = {result.data.allPersons}/>
<PersonForm />
</div>
)
}
export default AppYksinkertaisuuden lisäksi ratkaisun hyvä puoli on se, että aina kun joku käyttäjä lisää palvelimelle uuden henkilön, se ilmestyy pollauksen ansiosta heti kaikkien sovelluksen käyttäjien selaimeen.
Ikävänä puolena pollauksessa on tietenkin sen aiheuttama turha verkkoliikenne. Lisäksi sivu voi alkaa välkkyä, sillä komponentti renderöidään uudelleen jokaisen kyselyn päivityksen yhteydessä ja result.loading hetken aikaa tosi eli näytöllä vilahtaa silmänräpäyksen ajan loading...-teksti.
Toinen helppo tapa välimuistin synkronoimiseen on määritellä useMutation-hookin option refetchQueries avulla, että kaikki henkilöt hakeva kysely tulee suorittaa mutaation yhteydessä uudelleen:
// ...
const ALL_PERSONS = gql` query { allPersons { name phone id } }`
const PersonForm = () => {
const [name, setName] = useState('')
const [phone, setPhone] = useState('')
const [street, setStreet] = useState('')
const [city, setCity] = useState('')
const [createPerson] = useMutation(CREATE_PERSON, { refetchQueries: [{ query: ALL_PERSONS }], })
// ...
}Edut ja haitat tällä ratkaisulla ovat melkeinpä päinvastaiset pollaukseen. Nyt verkkoliikennettä ei synny kuin tarpeen vaatiessa, eli kyselyjä ei tehdä varalta. Toisaalta jos joku muu käyttäjä päivittää palvelimen tilaa, muutokset eivät nyt siirry kaikille käyttäjille.
Jos haluat tehdä useita kyselyitä, voit välittää useita olioita refetchQueries-taulukkoon. Tällä tavalla voit päivittää sovelluksesi eri osia samanaikaisesti. Esimerkki:
const [createPerson] = useMutation(CREATE_PERSON, {
refetchQueries: [
{ query: ALL_PERSONS },
{ query: OTHER_QUERY },
{ query: ANOTHER_QUERY },
], // lisää niin monta kyselyä kuin tarvitset
})Muitakin tapoja välimuistin tilan päivittämiseksi on, niistä lisää myöhemmin tässä osassa.
Sovellukseen on tällä hetkellä määritelty kyselyjä komponenttien koodin sekaan. Eriytetään kyselyjen määrittely omaan tiedostoonsa src/queries.js:
import { gql } from '@apollo/client'
export const ALL_PERSONS = gql`
query {
allPersons {
name
phone
id
}
}
`
export const FIND_PERSON = gql`
query findPersonByName($nameToSearch: String!) {
findPerson(name: $nameToSearch) {
name
phone
id
address {
street
city
}
}
}
`
export const CREATE_PERSON = gql`
mutation createPerson(
$name: String!
$street: String!
$city: String!
$phone: String
) {
addPerson(name: $name, street: $street, city: $city, phone: $phone) {
name
phone
id
address {
street
city
}
}
}
`Jokainen komponentti importtaa tarvitsemansa kyselyt:
import { ALL_PERSONS } from './queries'
const App = () => {
const result = useQuery(ALL_PERSONS)
// ...
}Sovelluksen tämänhetkinen koodi on kokonaisuudessaan GitHubissa, branchissa part8-2.
Mutaatioiden virheiden käsittely
Jos yritämme luoda epävalidia henkilöä esimerkiksi käyttäen sovelluksessa jo olemassa olevaa nimeä, ei tapahdu mitään. Henkilöä ei lisätä sovellukseen, mutta toisaalta emme saa myöskään minkäänlaista virheilmoitusta.
Määrittelimme aiemmin palvelimelle tarkistuksen, joka estää toisen samannimisen henkilön lisäämisen ja heittää virheen tällaisessa tilanteessa. Virhettä ei kuitenkaan käsitellä frontendissä vielä mitenkään. useMutation-hookin option onError avulla on mahdollista rekisteröidä mutaatioille virheenkäsittelijäfunktio.
Rekisteröidään mutaatiolle virheenkäsittelijä. Komponentti PersonForm vastaanottaa propsina funktion setError, jota käytetään virheestä kertovan viestin asettamiseen:
const PersonForm = ({ setError }) => { // ...
const [ createPerson ] = useMutation(CREATE_PERSON, {
refetchQueries: [ {query: ALL_PERSONS } ],
onError: (error) => setError(error.message), })
// ...
}Luodaan notifikaatiolle oma komponentti tiedostoon scr/components/Notify.jsx:
const Notify = ({ errorMessage }) => {
if (!errorMessage) {
return null
}
return (
<div style={{ color: 'red' }}>
{errorMessage}
</div>
)
}
export default NotifyKomponentti saa propsina mahdollisen virheviestin. Jos virheviesti on asetettu, se renderöidään näytölle.
Renderöidään virheviestin näyttävä Notify-komponentti tiedostossa App.jsx:
import Notify from './components/Notify'
// ...
const App = () => {
const [errorMessage, setErrorMessage] = useState(null)
const result = useQuery(ALL_PERSONS)
if (result.loading) {
return <div>loading...</div>
}
const notify = (message) => { setErrorMessage(message) setTimeout(() => { setErrorMessage(null) }, 10000) }
return (
<div>
<Notify errorMessage={errorMessage} /> <Persons persons = {result.data.allPersons} />
<PersonForm setError={notify} /> </div>
)
}Poikkeuksesta tiedotetaan nyt käyttäjälle yksinkertaisella notifikaatiolla.

Sovelluksen tämänhetkinen koodi on kokonaisuudessaan GitHubissa, branchissa part8-3.
Puhelinnumeron päivitys
Tehdään sovellukseen mahdollisuus vaihtaa henkilöiden puhelinnumeroita. Ratkaisu on lähes samanlainen kuin uuden henkilön lisäykseen käytetty.
Mutaatio edellyttää jälleen muuttujien käyttöä. Lisätään seuraava kysely tiedostoon queries.js:
export const EDIT_NUMBER = gql`
mutation editNumber($name: String!, $phone: String!) {
editNumber(name: $name, phone: $phone) {
name
phone
address {
street
city
}
id
}
}
`Luodaan puhelinnumeron päivitystä varten sovellukseen uusi komponentti PhoneForm tiedostoon src/components/PhoneForm.jsx. Komponentti lisää sovellukseen lomakkeen, jolla voi syötää uuden puhelinnumeron haluamalleen henkilölle. Mielenkiintoiset osat koodia ovat korostettuna:
import { useState } from 'react'
import { useMutation } from '@apollo/client/react'
import { EDIT_NUMBER } from '../queries'
const PhoneForm = () => {
const [name, setName] = useState('')
const [phone, setPhone] = useState('')
const [ changeNumber ] = useMutation(EDIT_NUMBER)
const submit = (event) => {
event.preventDefault()
changeNumber({ variables: { name, phone } })
setName('')
setPhone('')
}
return (
<div>
<h2>change number</h2>
<form onSubmit={submit}>
<div>
name <input
value={name}
onChange={({ target }) => setName(target.value)}
/>
</div>
<div>
phone <input
value={phone}
onChange={({ target }) => setPhone(target.value)}
/>
</div>
<button type='submit'>change number</button>
</form>
</div>
)
}
export default PhoneFormKomponentti PhoneForm on suoraviivainen: se kysyy lomakkeen avulla henkilön nimeä ja uutta puhelinnumeroa. Kun lomake lähetetään, kutsutaan numeron päivityksen hoitavaa funktiota changeNumber, joka on luotu useMutation-hookilla.
Otetaan uusi komponentti käyttöön tiedostossa App.jsx:
import PhoneForm from './components/PhoneForm'
const App = () => {
// ...
return (
<div>
<Notify errorMessage={errorMessage} />
<Persons persons={result.data.allPersons} />
<PersonForm setError={notify} />
<PhoneForm setError={notify} /> </div>
)
}Ulkoasu on karu mutta toimiva:

Kun numero muutetaan, päivittyy se hieman yllättäen automaattisesti komponentin Persons renderöimään nimien ja numeroiden listaan. Tämä johtuu siitä, että koska henkilöillä on identifioiva, tyyppiä ID oleva kenttä, päivittyy henkilö välimuistissa uusilla tiedoilla päivitysoperaation yhteydessä.
Sovelluksessa on vielä pieni ongelma. Jos yritämme vaihtaa olemattomaan nimeen liittyvän puhelinnumeron, ei mitään näytä tapahtuvan. Syynä tälle on se, että jos nimeä vastaavaa henkilöä ei löydy, vastataan kyselyyn null:

Koska kyseessä ei ole GraphQL:n kannalta virhetilanne, ei onError-virheenkäsittelijän rekisteröimisestä olisi tässä tilanteessa hyötyä.
Voimme generoida virheilmoituksen useMutation-hookin toisena parametrina palauttaman mutaation tuloksen kertovan olion result avulla.
import { useEffect, useState } from 'react'import { useMutation } from '@apollo/client/react'
import { EDIT_NUMBER } from '../queries'
const PhoneForm = ({ setError }) => { const [name, setName] = useState('')
const [phone, setPhone] = useState('')
const [ changeNumber, result ] = useMutation(EDIT_NUMBER)
const submit = (event) => {
event.preventDefault()
changeNumber({ variables: { name, phone } })
setName('')
setPhone('')
}
useEffect(() => { if (result.data && result.data.editNumber === null) { setError('person not found') } }, [result.data, setError])
// ...
}Jos henkilöä ei löytynyt, eli kyselyn tulos result.data.editNumber on null, asettaa komponentti propseina saamansa callback-funktion avulla sopivan virheilmoituksen. Virheilmoituksen asettamista kontrolloidaan useEffect-hookin avulla, ja hook suoritetaan aina kun mutaation tulos result.data muuttuu.
Sovelluksen tämänhetkinen koodi on GitHubissa, branchissa part8-4.
Apollo Client ja sovelluksen tila
Esimerkissämme sovelluksen tilan käsittely on siirtynyt suurimmaksi osaksi Apollo Clientin vastuulle. Tämä onkin melko tyypillinen ratkaisu GraphQL-sovelluksissa. Esimerkkimme käyttää Reactin komponenttien tilaa ainoastaan lomakkeen tilan hallintaan sekä virhetilanteesta kertovan notifikaation näyttämiseen. GraphQL:ää käytettäessä voikin olla, että ei ole enää kovin perusteltuja syitä siirtää sovelluksen tilaa ollenkaan Reduxiin.
Apollo mahdollistaa tarvittaessa myös sovelluksen paikallisen tilan tallettamisen Apollon välimuistiin.





