Ten post jest drugim z serii o GraphQL w React:
Spis Treści
- Wprowadzenie
- Użycie klienta Apollo Client do załadowania danych z serwera
- Podsumowanie
Wprowadzenie
Ponieważ w poprzednim poście przy pomocy biblioteki Apollo Server postawiliśmy serwer GraphQL, dziś używając Apollo Client stworzymy część kliencką aplikacji. Za jej pomocą pobierzemy dane z serwera.
Użycie klienta Apollo Client do załadowania danych z serwera
Wystartowanie serwera
W poprzednim poście utworzyliśmy następujące API wystawione na zewnątrz:
type Query { animals: [Animal] owners: [Owner] animal(name: String!): Animal owner(id: Int!): Owner } type Mutation { createOwner(owner: OwnerInput!): Owner! }
Uruchommy więc serwer poleceniem
npm start
Serwer uruchomi się pod adresem http://localhost:4000
, ten adres przekażemy do instancji aplikacji klienckiej.
Utworzenie klienta i pobranie danych z serwera
Udostępnienie klienta w aplikacji
Zainstalujmy bibliotekę Apollo Client i GraphQL poleceniem
npm install @apollo/client graphql
Po instalacji nalezy utworzyć instancję klasy ApolloClient
:
const client = new ApolloClient({ uri: 'http://localhost:4000', cache: new InMemoryCache() });
Aby móc korzystać z usług klienta w całej aplikacji za pomocą hooka useQuery
, powinniśmy użyć providera ApolloProvider
i przekazać mu stworzoną instancję klienta:
ReactDOM.render( <React.StrictMode> <ApolloProvider client={client}> <App /> </ApolloProvider> </React.StrictMode>, document.getElementById('root') );
Pobranie i wyświetlenie danych za pomocą hooka useQuery i zapisanie danych za pomocą hooka useMutation
W naszej aplikacji użyjemy 2 komponentów które będą ładować dane, Animals
i Owners
:
const App = () => { return ( <div> <Animals /> <Owners /> </div> ); }
Komponent Animals i pochodne – ładowanie danych przez useQuery
Komponent Animals
wykorzysta hook useQuery wy wywołać następujące zapytanie:
const {data} = useQuery(gql` query { animals { name } } `);
Jak możemy zauważyć w Dev Tools, na endpoint /
zostanie wysłane przekazane w hooku query, które zostanie przez serwer zresolvowane:
W obiekcie data
znajduje się pole animals
, zawierające listę obiektów z parametrem name. Komponent wyświetla listę elementów, które wewnątrz odpytają o szczegóły konkretnego zwierzęcia:
const Animals = () => { const {data} = useQuery(gql` query { animals { name } } `); return ( <p> Animals: {data?.animals.map(({name}) => ( <Animal name={name} /> ))} </p> ) }
Komponent Animal
jest komponentem przechodnim, mającym zapamiętać czy dany obiekt został kliknięty. Nie jest dla nas bardzo istotny (pełny kod będzie dostępny na Githubie), przejdźmy więc od razu do komponentu AnimalData
który załaduje dane.
Komponent AnimalData
wykorzysta hook useQuery aby załadować dane konkretnego zwierzęcia. W zapytaniu użyjemy variables
by móc dynamicznie przekazać dane do zapytania:
const {data} = useQuery(gql` query ($name: String!){ animal(name: $name) { name birthPlace } } `, { variables: { name, } })
Nazwa pola w obiekcie variables
musi odpowiadać nazwie zmiennej użytej w kodzie query. Na serwer pod endpoint /
wysyłane jest pełne zapytanie wraz ze zmiennymi, którymi ma być ono uzupełnione:
W odpowiedzi w obiekcie data
otrzymamy obiekt animal
zawierający pola name
i birthPlace
, które zostaną wyświetlone:
return ( <p> Name: {data.animal.name} <br /> Birth place: {data.animal.birthPlace} </p> )
Komponent Owners i zapisywanie danych przez useMutation
Ponieważ w komponencie Owners
chcemy jednoczesnie pobierać dane i wysyłać je na serwer, hook useQuery poza danymi zwróci również funkcję która ponownie wywoła zapytanie:
const {data, refetch} = useQuery(gql` query { owners { name } } `);
Hook useMutation udostępni nam funkcję która wyśle dane na serwer:
const [createOwner] = useMutation(gql` mutation ($name: String!){ createOwner(owner: {name: $name}) { id, name, } } `, {onCompleted: refetch});
W opcjach zdefiniowaliśmy również, że po zapisie na serwerze lista ma zostać odświeżona.
Samo wywołanie funkcji createOwner
jest standardowe:
const [name, setName] = useState('') // wypełniany w formularzu const onSubmit = e => { e.preventDefault() createOwner({ variables: { name, } }) setName('') }
Dane standardowo wysyłane są na endpoint /
:
Kod aplikacji na Github
Kod aplikacji można pobrać pod adresem:
https://github.com/radek-anuszewski/graphql-client-example
Pełny kod aplikacji
import React, {useState} from 'react'; import {gql, useMutation, useQuery} from "@apollo/client"; const AnimalData = ({name}) => { const {data} = useQuery(gql` query ($name: String!){ animal(name: $name) { name birthPlace } } `, { variables: { name, } }) if (!data) { return null; } return ( <p> Name: {data.animal.name} <br /> Birth place: {data.animal.birthPlace} </p> ) } const Animal = ({name}) => { const [clicked, setClicked] = useState(false) if (!clicked) { return ( <p> <button onClick={() => setClicked(true)}> {name} </button> </p> ) } return <AnimalData name={name} /> } const Animals = () => { const {data} = useQuery(gql` query { animals { name } } `); return ( <p> Animals: {data?.animals.map(({name}) => ( <Animal name={name} /> ))} </p> ) } const Owners = () => { const {data, refetch} = useQuery(gql` query { owners { name } } `); const [createOwner] = useMutation(gql` mutation ($name: String!){ createOwner(owner: {name: $name}) { id, name, } } `, {onCompleted: refetch}); const [name, setName] = useState('') const onSubmit = e => { e.preventDefault() createOwner({ variables: { name, } }) setName('') } return ( <p> Owners: <div> {data?.owners.map(el => el.name).join(', ')} </div> <form onSubmit={onSubmit}> <input value={name} onChange={e => setName(e.target.value)} /> <button type="submit">Submit</button> </form> </p> ) } const App = () => { return ( <div> <Animals /> <Owners /> </div> ); } export default App;
Podsumowanie
Platforma Apollo Data Graph pozwala sprawnie zarządzać zarówno częścią serwerową jak i kliencką. Już przykłady zawarte w poście pokazują, że warto zainteresować się technologią GraphQL. W kolejnych postach będą prezentowane bardziej zaawansowane przypadki użycia.