My React (Native) app speaks GraphQL

Guillaume Diallo-Mulliez

Architecte Développeur

guitoof

@Guitoof

What's GraphQL ?

Some cool features

How to connect my React Native app to a GraphQL API ?

Why GraphQL ?

Over Fetching

http://api.star-wars/films

{
    "films": [
        {
            "title": "A New Hope",
            "director": "George Lucas",
            "producers": [
              "Gary Kurtz",
              "Rick McCallum"
            ],
            "releaseDate": "1977-05-25",
            "episodeID": 4,
            "planetIds": [3, 5, 13]
        },
        {
            "title": "The Empire Strikes Back",
            "director": "Irvin Kershner",
            "producers": [
              "Gary Kurtz",
              "Rick McCallum"
            ],
            "releaseDate": "1980-05-17",
            "episodeID": 5,
            "planetIds": [4, 2, 5]
        },
        {
            "title": "Return of the Jedi",
            "director": "Richard Marquand",
            "producers": [
              "Howard G. Kazanjian",
              "George Lucas",
              "Rick McCallum"
            ],
            "releaseDate": "1983-05-25",
            "episodeID": 6,
            "planetIds": [10, 7, 12]
        }
    ]   
}
http://api.star-wars/films

{
    "films": [
        {
            "title": "A New Hope",





            "releaseDate": "1977-05-25",
            "episodeID": 4,

        },
        {
            "title": "The Empire Strikes Back",





            "releaseDate": "1980-05-17",
            "episodeID": 5,

        },
        {
            "title": "Return of the Jedi",






            "releaseDate": "1983-05-25",
            "episodeID": 6,

        }
    ]   
}
http://api.star-wars/films

{
    "films": [
        {
            "title": "A New Hope",
            "director": "George Lucas",
            "producers": [
              "Gary Kurtz",
              "Rick McCallum"
            ],
            "releaseDate": "1977-05-25",
            "episodeID": 4,
            "planetIds": [3, 5, 13]
        },
    ]   
}




        http://api.star-wars/planets



        http://api.star-wars/films/4/planets

Under Fetching

What's GraphQL ?

A query language for your APIs 

Single Endpoint

http://api.star-wars/planets

http://api.star-wars/characters

http://api.star-wars/films/5/planets

http://api.star-wars/films/5/characters

http://api.star-wars/characters/42/friends

Rest

GraphQL

http://api.star-wars/graphql

Fetch what you need

A Graph structure

Auto Documented

Multilingual

GraphQL Users

More on GraphQL

?

What's

Apollo ?

A GraphQL client designed to  make it easy to build UI components that fetch data with GraphQL

What's Apollo ?

UI Component

Container

Apollo Client

GraphQL API

Data Store

Apollo Client users ?

First steps ?

Initialization

$ npm install apollo-client react-apollo apollo-link-http apollo-cache-inmemory
const client = new ApolloClient({
  link: new HttpLink({ uri: 'http://pokedex-graphql-api/' }),
  cache: new InMemoryCache()
});

export default class App extends React.Component {
  render() {
    return (
      <ApolloProvider client={client}>
        <PokedexApp />
      </ApolloProvider>
    );
  }
}
import { ApolloClient } from 'apollo-client';
import { HttpLink } from 'apollo-link-http';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { ApolloProvider } from 'react-apollo';

Import

Configure

Install

Queries

Fetching Data

Component









                pokemons


                                    item.image
                     item.name










    const { pokemons } = this.props.data;


          data={pokemons}


              <Image source={{ uri: item.image }}  />
              <Text>{item.name}</Text>





import React, { Component, Fragment } from 'react';
import { FlatList, Text, View } from 'react-native';

export default class Pokedex extends Component {
  render() {
    const { pokemons } = this.props.data;
    return (
        <FlatList
          data={pokemons}
          renderItem={({ item }) => (
            <Fragment key={item.id}>
              <Image source={{ uri: item.image }}  />
              <Text>{item.name}</Text>
            </Fragment>
          )}
        />
    );
  }
}
import { graphql } from 'react-apollo';
import gql from 'graphql-tag';
import Pokedex from './Pokedex.component';

const GET_POKEMONS_QUERY = gql`
  query {
    pokemons(first: 9) {
      id
      name
      image
    }
  }
`;

export default graphql(GET_POKEMONS_QUERY)(Pokedex);

Connected Component






  query {
    pokemons(first: 9) {
      id
      name
      image
    }
  }

Pokedex

Pokedex.container

class Pokedex extends Component {
    render() {
        const { pokemons } = this.props.data;
        ...
    }
}


        const { pokemons } = this.props.data;
const GET_POKEMONS_QUERY = gql`
  query GetPokemons {
    pokemons(first: 9) {
      id
      name
      image
    }
  }
`;

Connected Components

Pokemon

Pokemon.container

Pokedex

Pokedex.container

const GET_DETAILED_POKEMON_QUERY = gql`
  query GetDetailedPokemon($id: Int!) {
    pokemon(id: $id) {
        id
        name
        classification
        height {
          minimum
          maximum
        }
        weight {
          minimum
          maximum
        }
        evolutions {
          id
          name
        }
    }
  }
`;
const GET_POKEMONS_QUERY = gql`
  query GetPokemons {
    pokemons(first: 9) {
      id
      name
      image
    }
  }
`;

Connected Components

const GET_DETAILED_POKEMON_QUERY = gql`
  query GetDetailedPokemon($id: Int!) {
    pokemon(id: $id) {
        id
        name
        classification
        height {
          minimum
          maximum
        }
        weight {
          minimum
          maximum
        }
        evolutions {
          id
          name
        }
    }
  }
`;

Queries

 

 

Mutations

GET

 

 

 

POST

Mutations

Posting Data

Decorated Component

import { graphql } from 'react-apollo';
import gql from 'graphql-tag';
import Pokemon from './Pokemon.component';

const GIVE_NICKNAME_MUTATION = gql`
  mutation GiveNickname($pokemonId: ID!, $nickname: String) {
    giveNickname(pokemonId: $pokemonId, nickname: $nickname) {
      id
      name
      nickname
      image
    }
  }
`;

export default graphql(GIVE_NICKNAME_MUTATION)(Pokemon);

with a Mutation






  mutation GiveNickname($pokemonId: ID!, $nickname: String) {
    






    giveNickname(pokemonId: $pokemonId, nickname: $nickname) {








      id
      name
      nickname
      image
import { graphql } from 'react-apollo';
import gql from 'graphql-tag';
import Pokemon from './Pokemon.component';

const GIVE_NICKNAME_MUTATION = gql`
  mutation GiveNickname($pokemonId: ID!, $nickname: String) {
    giveNickname(pokemonId: $pokemonId, nickname: $nickname) {
      id
      name
      nickname
      image
    }
  }
`;

export default graphql(GIVE_NICKNAME_MUTATION)(Pokemon);
import { graphql } from 'react-apollo';
import gql from 'graphql-tag';
import Pokemon from './Pokemon.component';

const GIVE_NICKNAME_MUTATION = gql`
  mutation GiveNickname($pokemonId: ID!, $nickname: String) {
    giveNickname(pokemonId: $pokemonId, nickname: $nickname) {
      id
      name
      nickname
      image
    }
  }
`;

export default graphql(GIVE_NICKNAME_MUTATION)(Pokemon);
import { graphql } from 'react-apollo';
import gql from 'graphql-tag';
import Pokemon from './Pokemon.component';

const GIVE_NICKNAME_MUTATION = gql`
  mutation GiveNickname($pokemonId: ID!, $nickname: String) {
    giveNickname(pokemonId: $pokemonId, nickname: $nickname) {
      id
      name
      nickname
      image
    }
  }
`;

export default graphql(GIVE_NICKNAME_MUTATION)(Pokemon);















export default graphql(GIVE_NICKNAME_MUTATION)(Pokemon);

Decorated Component

call the Mutation prop

export default class Pokemon extends Component {

  _givePokemonNickname = () => {
    this.props.giveNickname({
      variables: {
        pokemonId: this.props.pokemonId,
        nickname: this.state.nickname
      }
    })
  }

  render() {
    return (
        ...
        <Button title="Rename" onPress={this._givePokemonNickname}
    );
  }
}
export default class Pokemon extends Component {

  _givePokemonNickname = () => {
    this.props.giveNickname({
      variables: {
        pokemonId: this.props.pokemonId,
        nickname: this.state.nickname
      }
    })
  }

  render() {
    return (
        ...
        <Button title="Rename" onPress={this._givePokemonNickname}
    );
  }
}














        <Button title="Rename" onPress={this._givePokemonNickname}



  _givePokemonNickname = () => {
    this.props.giveNickname({
      variables: {
        pokemonId: this.props.pokemonId,
        nickname: this.state.nickname
      }
    })
  }

Updated Component

update data after mutation

refetchQueries

update

optimisticResponse

mutation

response

update queries

mutation

response

mutation

Going further ?

       Loading state

class Pokedex extends Component {
    render() {
        const { pokemons } = this.props.data;
        ...
    }
}


        const { pokemons } = this.props.data;


                          loading
        if (loading) return <ActivityIndicator />;
class Pokedex extends Component {
    render() {
        const { pokemons, loading } = this.props.data;
        
    }
}


                          loading

       Error handling

class Pokedex extends Component {
    render() {
        const { pokemons } = this.props.data;
        ...
    }
}


        const { pokemons } = this.props.data;


                          error
        if (error) return <ErrorComponent error={error} />;
class Pokedex extends Component {
    render() {
        const { pokemons, error } = this.props.data;
        
    }
}


                          error

       Caching

VS

       Normalized Caching

Apollo Client

Data Store

Pokemon:1

Pokemon:2

...

Attack:1

Attack:2

{
    planets {
        name
        species {
            name
            characters {
                name
            }
        }
    }
}
    
{
    characters {
        name
        homeworld {
            name
            species {
                name
            }
        }
    }
}
    

       Flexible Caching

Apollo Client

Data Store

Pokemon:1

Pokemon:2

...

Attack:1

Attack:2

export default compose(
  graphql(GET_POKEMONS_QUERY, {
    options: {
        fetchPolicy: 'cache-and-network'
    }
  }),
)(Pokedex);

Fetch Policy

cache-first

cache-and-network

network-only

cache-only

       Caching

VS

       Authorization

import { ApolloClient } from 'apollo-client';
import { createHttpLink } from 'apollo-link-http';
import { setContext } from 'apollo-link-context';
import { InMemoryCache } from 'apollo-cache-inmemory';

const httpLink = createHttpLink({
  uri: '/graphql',
});

const authLink = setContext((_, { headers }) => {
  const token = localStorage.getItem('token');
  return {
    headers: {
      ...headers,
      authorization: token ? `Bearer ${token}` : '',
    }
  }
});

const client = new ApolloClient({
  link: authLink.concat(httpLink),
  cache: new InMemoryCache()
});


import { setContext } from 'apollo-link-context';






const authLink = setContext((_, { headers }) => {
  const token = localStorage.getItem('token');
  return {
    headers: {
      ...headers,
      authorization: token ? `Bearer ${token}` : '',
    }
  }
});


  link: authLink.concat(httpLink),

Ready for Take-off ?

Optimisic UI

Subscriptions

Pagination

Prefetching Data

Dev Tools (web)

a few more features ...

GraphQL

Useful links

Apollo

Thank You

Any Questions ?

My React (Native) app speaks GraphQL (Long)

By Guillaume Diallo-Mulliez

My React (Native) app speaks GraphQL (Long)

A presentation of GraphQL and how to connect a React (Native) app to it using Apollo Client.

  • 702