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