React Native with GraphQL
TL;DR; React Native apps with Expo.io and GraphQL (React-Apollo). Less pain, less code.
Here at Facebook Developer Circle Malang, we held React Native workshop using Expo.io and Lumen. Everyone (including me) are encouraged to create a mobile apps using React Native. And then I had an idea using some kind of RESTful API like swapi.co or pokeapi.co but with GraphQL. After hours of researching, I decided to use Starwars GraphQL server from GraphQL.org.
My stack will be looks like this:
- Expo
- React Navigation (for screen navigation)
- React Apollo (This awesome package has
graphql
andgql
in it)
Quite simple, huh?! OK, let’s code!
Integrating Apollo Provider with React Navigation
First, define your route and add it to React Navigation’s StackNavigator
. Example:
// main.js
import { StackNavigator } from "react-navigation";
import App from "./src/App";
import Film from "./src/Film";
import FilmDetail from "./src/Film/FilmDetail";
const routes = {
Home: {
name: "Home",
screen: App,
},
Film: {
name: "Film",
screen: Film,
},
FilmDetail: {
name: "FilmDetail",
screen: FilmDetail,
},
};
const MainApp = StackNavigator(routes);
Second, in main entrypoint file (mine is main.js
), import ApolloClient
, ApolloProvider
, createNetworkInterface
from react-apollo
module and initialize an ApolloClient
.
// main.js
import {
ApolloClient,
ApolloProvider,
createNetworkInterface,
} from "react-apollo";
const client = new ApolloClient({
networkInterface: createNetworkInterface({
uri: "GRAPHQL_SERVER_ENDPOINT",
}),
});
And then, crate a class for wrapping your router with ApolloProvider
and register it as your root component.
// main.js
class AppContainer extends React.Component {
render() {
return (
<ApolloProvider client={client}>
<MainApp />
</ApolloProvider>
);
}
}
Expo.registerRootComponent(AppContainer);
So the full code for the main entrypoint looks like this:
// main.js
import Expo from "expo";
import React from "react";
import { StackNavigator } from "react-navigation";
import App from "./src/App";
import Film from "./src/Film";
import FilmDetail from "./src/Film/FilmDetail";
const routes = {
Home: {
name: "Home",
screen: App,
},
Film: {
name: "Film",
screen: Film,
},
FilmDetail: {
name: "FilmDetail",
screen: FilmDetail,
},
};
const MainApp = StackNavigator(routes);
class AppContainer extends React.Component {
render() {
return (
<ApolloProvider client={client}>
<MainApp />
</ApolloProvider>
);
}
}
Expo.registerRootComponent(AppContainer);
GraphQL Query on Component
If you’re not familiar with GraphQL query, you might want to read GraphQL Query documentation first. On this example, we just do a query to get a detail with an argument and variable from Starwars API.
With GraphQL, we can specify which field(s) do we need just like SELECT
in SQL query. We just need title
, openingCrawl
, director
, producers
, and releaseDate
fields for this example. With GraphQL, we can query like this
film(id: $id) {
title
openingCrawl
director
producers
releaseDate
}
We just need to specify the object name (it’s film
in this example), arguments (it’s id: $id
), and fields (within curly braces). Please do note that we need to add either comma, new line or even space for each fields. Valid query example
// With comma
film(id: $id) { title,episodeID, openingCrawl, director }
// With new line
film(id: $id) {
title
openingCrawl
director
producers
releaseDate
}
// With space
film(id: $id) { title openingCrawl director producers releaseDate }
You can pick your flavor. Personally, I like the one with new line. It’s much more readable if we deals with lots of fields later. Let’s go ahead.
Now we can wrap the object with a query
statement
query($id: ID!) {
film(id: $id) {
title
openingCrawl
director
producers
releaseDate
}
}
If you’re looking at the query
statement, it has $id
and ID!
on its parameter (just like a normal function pattern). Basically, GraphQL has a type system which mean that we need to specify the type for each of its paramater.
So, we can say that query
statement has a parameter called $id
which has type of ID
(GraphQL built-in type). And the exclamation mark (ID!
) means that the field is non-nullable or required.
And if you’re looking at the film
object, it also has a parameter called id
. We can passing the arguments from query
statement to the film
object (just like normal function) just like the code above.
OK, so how do we get the $id
? React-Apollo comes with a handy way how to get the variable(s). Please refer to this page. Basically, React-Apollo is just a High Order Component for React. The graphql
container has a pattern like this
graphql(query, [config])(component)
As you can see, the first parameter of the container is the query and the second is the config. We can get the $id
from the config. Note: since React Native support decorators
syntax, I will use it.
@graphql(gql`
query($id: ID!) {
film(id: $id) {
title
openingCrawl
director
producers
releaseDate
}
}
`, {
options: (props) => ({
variables: {
id: props.navigation.state.params.id, // navigation object comes from React Navigation
},
}),
})
You’ll notice that we specify the variables called id
on the options
section. It will get injected into the query and will be read by grapqhl query as $id
on runtime. The code will looks like this
// FilmDetail.js
import React, { Component, PropTypes } from "react";
import { gql, graphql } from "react-apollo";
import { Text, View } from "react-native";
@graphql(
gql`
query($id: ID!) {
film(id: $id) {
title
openingCrawl
director
producers
releaseDate
}
}
`,
{
options: (props) => ({
variables: {
id: props.navigation.state.params.id,
},
}),
}
)
class FilmDetail extends Component {
static navigationOptions = ({ navigation }) => ({
title: navigation.state.params.title,
});
render() {
const { film } = this.props.data;
return (
<View style={styles.container}>
<Text style={styles.title}>{film.title}</Text>
<View style={styles.rowContainer}>
<Text style={styles.bold}>Released Date:</Text>
<Text> {moment(film.releaseDate).format("DD MMMM Y")}</Text>
</View>
<View style={styles.rowContainer}>
<Text style={styles.bold}>Director:</Text>
<Text> {film.director}</Text>
</View>
<View style={styles.rowContainer}>
<Text style={styles.bold}>Producers:</Text>
<Text> {film.producers.join(", ")}</Text>
</View>
<View style={styles.openingCrawlContainer}>
<Text style={styles.openingCrawlText}>{film.openingCrawl}</Text>
</View>
</View>
);
}
}
export default FilmDetail;
React-Apollo will wrap the query result inside the props.data
. If you want to get the result, you can access it via this.props.data
object. For this example, we have film
object for the query. React-Apollo will inject the film
object into props.data
. So we can access it by using this.props.data.film
.
render() {
const { film } = this.props.data; // we can also perform destructuring here
return (
<View style={styles.container}>
<Text style={styles.title}>{film.title}</Text>
<View style={styles.rowContainer}>
<Text style={styles.bold}>Released Date:</Text><Text> {moment(film.releaseDate).format('DD MMMM Y')}</Text>
</View>
<View style={styles.rowContainer}>
<Text style={styles.bold}>Director:</Text><Text> {film.director}</Text>
</View>
<View style={styles.rowContainer}>
<Text style={styles.bold}>Producers:</Text><Text> {film.producers.join(', ')}</Text>
</View>
<View style={styles.openingCrawlContainer}>
<Text style={styles.openingCrawlText}>{film.openingCrawl}</Text>
</View>
</View>
);
}
The data
prop has some other useful properties which can be accessed directly from data
. For example, data.loading
or data.error
. Please do check the data.loading
and data.error
before render the page. Logically, you can show the loading bar if data.loading
is true
or you can show the error message if data.error
is not null
.
You can check the fully working example on here