GraphQL with React Native¶
This section covers how to use GraphQL with React Native using Apollo Client.
Configuration¶
- Install the required dependencies
$ npm install apollo-boost react-apollo graphql --save
Create an ApolloClient instance and point it to the Hasura Data GraphQL URL via Apollo Link.
import { ApolloClient } from 'apollo-client'; import { HttpLink } from 'apollo-link-http'; import { InMemoryCache } from 'apollo-cache-inmemory'; const GRAPHQL_URL = "https://data.<cluster-name>.hasura-app.io/v1alpha1/graphql"; const httpLink = new HttpLink({ uri: GRAPHQL_URL}); const client = new ApolloClient({ link: httpLink, cache: new InMemoryCache({ addTypename: false }) });
Note
Important: You have to configure
addTypenameto false in theInMemoryCacheconstructor.If you have to authorize your queries and mutations, you need to pass request headers to the Apollo Client using a middleware.
const GRAPHQL_URL = `https://data.<cluster-name>.hasura-app.io/v1alpha1/graphql` const httpLink = new HttpLink({ uri: GRAPHQL_URL}); // adding auth headers const authMiddleware = new ApolloLink((operation, forward) => { AsyncStorage.getItem("@<cluster-name>:myapp").then((session) => { operation.setContext({ headers: { authorization: session ? "Bearer " + session.token : null } }); }) return forward(operation); }); // Creating a client instance with auth middlewar const client = new ApolloClient({ link: concat(authMiddleware, httpLink), cache: new InMemoryCache({ addTypename: false }) });
Connect the client to your component tree using the
ApolloProvidercomponent. It is important to putApolloProviderabove every component where you need the GraphQL data. For example, it could be before registering your root component.import { ApolloProvider } from 'react-apollo'; import { ApolloClient } from 'apollo-client'; import { HttpLink } from 'apollo-link-http'; import { InMemoryCache } from 'apollo-cache-inmemory'; import { App } from './App'; const GRAPHQL_URL = "https://data.<cluster-name>.hasura-app.io/v1alpha1/graphql"; const client = new ApolloClient({ link: new HttpLink({uri: GRAPHQL_URL}), cache: new InMemoryCache() }); const AppWithClient= () => ( <ApolloProvider client={client}> <App/> </ApolloProvider> ); AppRegistry.registerComponent('MyApplication', () => AppWithClient);
That’s it. You can now make queries and mutations in all the children components.
Examples¶
We will use a simple TODO-list schema for demonstrating queries with React Native components.
Table Name: todos
| column | type |
|---|---|
| id | serial NOT NULL primary key |
| task | text NOT NULL |
| completed | bool NOT NULL |
| user_id | int NOT NULL |
Queries and mutations¶
Firstly, we will define our required queries and mutations as graphql strings.
const FETCH_TODOS = gql`
query fetch_todos{
todos {
id
task
completed
user_id
}
}
`;
const INSERT_TODO = gql`
mutation insert_todos ($objects: [todos_input!]){
insert_todos(objects: $objects) {
affected_rows
returning {
id
}
}
}
`;
const UPDATE_TODO = gql`
mutation update_todos{
update_todos(where: {id: {_eq: $todo_id}} _set: {completed: $completed}) {
affected_rows
}
}
`;
const DELETE_TODO = gql`
mutation delete_todos{
delete_todos(where: {id: {_eq: $todo_id}}) {
affected_rows
}
}
`;
export {
INSERT_TODO,
FETCH_TODOS,
UPDATE_TODO,
DELETE_TODO
}
In the above queries:
todosabove is the name of the table. This is the convention followed by Hasura for GraphQL queries.insert_todosis a convention when inserting data into a table. It is of typeinsert_<TABLE_NAME>, where <TABLE_NAME> is to be replaced with the name of the table to which data is being inserted.- You can insert more than one row at a time. Denoted by
$objects: [todos_input]. todos_inputis the type of the data that will be inserted into the table. The convention followed is<TABLE_NAME>_inputwhere <TABLE_NAME> is to be replaced with the name of the table to which data is being inserted.affected_rowsis the number of rows that were inserted.- The
returningkey specifies the data you want returned after a successful insertion. In this case, we are asking for theid,taskandcompletedcolumns. update_todosis a convention when updating data in a table. It is of typeupdate_<TABLE_NAME>, where <TABLE_NAME> is to be replaced with the name of the table where you want to update data.where: {id: {_eq: 4}}checks that theidof the row that is being updated is4.- The
delete_todosquery is very similar to the update mutation, the only difference is that the convention to delete data from a table isdelete_<TABLE_NAME>where <TABLE_NAME> is to be replaced with the name of the table where you want to delete data.
Now lets write sample React Native components that use these queries.
Query Components¶
To fetch all todos, and render the tasks as a list of <Text>, you can use
const TodoListComponent = () => (
<Query
query={FETCH_TODOS}
>
{({loading, error, data}) => {
return data.todos.map((todo, index) => {
return (
<Text>{todo.task}</Text>
);
});
}}
</Query>
)
Mutation Components¶
Insert¶
Below is the code snippet for a Button that inserts an element in the todos table.
const AddButton = (props) => (
<Mutation
mutation={INSERT_TODO}
>
{(insert_todos, {data}) => (
<Button
title="Insert Todo"
style={props.style}
onPress={() => {
insert_todos({
variables: {
objects: [{
task: "Sample todo task",
completed: false,
user_id: 44
}]
}
});
}}
/>
)}
</Mutation>
)
In most cases, you also need to update the memory cache of the Apollo client in order to reflect changes in the UI. To do that, you just have to add an update prop in the Mutation component:
update= {(cache, {data: {insert_todos}}) => {
const data = cache.readQuery({ query: FETCH_TODOS});
const newTodo = {
task: "Sample todo task"
completed: false,
user_id: 44,
id: insert_todos.returning[0].id
}
data.todos.push(newTodo);
cache.writeQuery({query: FETCH_TODOS, data})
}}
Update¶
The Button below, sets the completed status of a task (id = 4) to true.
// this component should receive a prop called `todo` which is the todo item object to be updated
const UpdateButton= (props) => {
return (
<Mutation
mutation={
gql`
mutation update_todos{
update_todos(where: {id: {_eq: 4}} _set: {completed: true}) {
affected_rows
}
}
`
}
>
{
(update_todos) => (
<Button
onPress={() => {
update_todos({
variables: {
todo_id: props.todo.id,
completed: !props.todo.completed
}
})
}}
title="Update todo"
>
)
}
</Mutation>
)
}
To update the Apollo cache after performing the mutation, you just have to add an update prop in the Mutation component:
update={(cache) => {
const data = cache.readQuery({ query: FETCH_TODOS});
cache.writeQuery({
query: FETCH_TODOS,
data: {
...data,
todos: data.todos.map((todo) => {
if (todo.id === props.todo.id) {
return {
...todo,
completed: !todo.completed
}
}
return todo;
})
}
})
}}
Delete¶
Finally, if you want a Button to delete a task with id = 4, you can use
// this component should receive a prop called `todo` which is the todo item to be updated
const DeleteButton = (props) => (
<Mutation
mutation={DELETE_TODO}
>
{(delete_todos, {data}) => (
<Button
title="Delete"
style={props.style}
onPress={() => {
delete_todos({
variables: {
todo_id: props.todo.id
}
});
}}
/>
)}
</Mutation>
);
To update the cache after deletion, add the following update prop to the Mutation component:
update= {(cache) => {
const data = cache.readQuery({ query: FETCH_TODOS});
cache.writeQuery({
query: FETCH_TODOS,
data: {
...data,
todos: data.todos.filter((todo) => (props.todo.id !== todo.id))
}
})
}}