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.
- 801