You probably have heard about GraphQL. It’s the common interface between client and server for data fetching and manipulations. My experience with GraphQL so far is great.
- I spend less time to write data fetching code. All of the data I need returns in a single round trip. For example, if I have to get a list of categories and the posts inside of each category. If I use REST, I have to call 1 API to get the list of categories and call N apis to get list of posts in each category. But with GraphQL, I have to call only API one time to get everything that I need.
- I can specify which fields the I really need and reduce the network data.
- I don’t need a document to understand the response data. GraphQL tool describes them all.
There are 2 GraphQL clients, they are Relay and Apollo. Relay is the official framework for GraphQL from Facebook. I find that Relay is harder to understand and Apollo is very easy to use. Apollo also has more features than Relay.
In this article, I will show you the example from my experience working with Apollo and Redux. Let’s start:
Contents
Setup a React app with Apollo, Redux
The first thing when working with Apollo is we need to have GraphQL endpoint. In the example below, I put the endpoint URI in .env file so we can change it dynamically. Having the URI, we can create the network interface as well as the Apollo client.
We wrap our app with ApolloProvider which provides the GraphQL features to our application. This provider also acts as a Redux provider, so we don’t need to create another Redux Provider anymore.
import React from 'react'; import ReactDOM from 'react-dom'; import App from './App'; import registerServiceWorker from './registerServiceWorker'; import configureStore from './config/configureStore'; import { createNetworkInterface, ApolloClient, ApolloProvider } from 'react-apollo'; const networkInterface = createNetworkInterface({ uri: process.env.REACT_APP_GRAPHQL_URL }); const client = new ApolloClient({networkInterface}); const store = configureStore(client); function render(Component) { ReactDOM.render( ( <ApolloProvider client={client} store={store}> <App /> </ApolloProvider> ), document.getElementById('root') ); } render(App); registerServiceWorker();
Login with GraphQL
Login is a common task for any React application. You need to make sure that our backend already has login mutation. Think mutation like a POST request. Using a mutation, you can send data to backend and transform backend data. Here is the example.
import React, { Component } from 'react'; import { gql, graphql } from 'react-apollo'; import { connect } from 'react-redux'; import { setAccessToken } from './reducer'; import cx from 'classnames'; const LoginQuery = gql` mutation logIn($user: LoginInput!) { logIn(input: $user) { access_token } } `; const withMutation = graphql(LoginQuery, { props: ({ mutate }) => ({ loginUser: user => mutate({ variables: { user }, }), }), }); const mapDispatchToProp = (dispatch, ownProps) => ({ login: (username, password) => { ownProps .loginUser(input) .then(response => { dispatch(setAccessToken(response.data.accessToken)); }, err => { console.log(err); }); } }); class LoginForm extends Component { onSubmit = (event) => { event.preventDefault(); let {loading, login} = this.props; if (loading) return false; let username = this.usernameInput.value; let password = this.passwordInput.value; login({username, password}); } render() { let { data } = this.props; return ( <div className="auth-form"> <form name="loginForm" onSubmit={this.onSubmit}> <div className="form-title"> Login </div> { data.error && <div className="form-error-message">{data.error.message}</div> } <div className="form-group-container"> <div className="form-group"> <input type="text" name="username" ref={input => this.usernameInput = input} required /> <label>Email or Phone</label> </div> <div className="form-group"> <input type="password" name="password" ref={input => this.passwordInput = input} required /> <label>Password</label> </div> </div> <input type="submit" value='Login' className={cx({ inactive: data.loading })} /> <br/> </form> </div> ); } } export default withMutation(connect(mapStateToProps, mapDispatchToProp)(LoginForm));
In the example, we use gql to parse the GraphQL query and pass it into GraphQL to connect our component to GraphQL provider. We also inject the login input into loginUser method.
The LoginForm component is the presentation component. It will call the login method which is provide by our container. When login success, we call loginUserSuccess to store our access token.
The data object that you see in the render method comes from Apollo, it provides some helpful properties like:
- loading: indicate that a request is still running
- error: indicate that request has been failed
JWT Authentication
If you use JWT authentication, you probably have to inject access token into the request headers. In the previous example, we have store the token into Redux store. How to get it and inject into request header? Well, we have to update our network interface. Take a look in this example:
const networkInterface = createNetworkInterface({ uri: process.env.REACT_APP_GRAPHQL_URL }); const client = new ApolloClient({networkInterface}); const store = configureStore(client); networkInterface.use([{ applyMiddleware(req, next) { if (!req.options.headers) { req.options.headers = {}; } const accessToken = store.getState().auth.accessToken; if (accessToken) { req.options.headers.Authorization = 'Bearer ' + accessToken; } next(); } }]);
Query data with parameter
We have a category with ID is 1. How do we fetch category data and render our component? Here is an example:
import { gql, graphql } from 'react-apollo'; import CategoryPage from './view'; const categoryQuery = gql` query category($id: String) { category(id: $id) { id name } } `; export default graphql(categoryQuery, { options: props => ({ variables: { id: props.categoryId } }) })(CategoryPage);
Sometimes, the category comes from URL like this: /category/:categoryId. In this case, you can get the category from props.match.params
export default graphql(categoryQuery, { options: props => ({ variables: { id: props.match.params.categoryId } }) })(CategoryPage);
Manually refetch data
Apollo doesn’t automatically refetch data for us, even when variables has been changed. But it provides refetch method in the props.data. You can call this method manually.
import React, {Component} from 'react'; import { gql, graphql } from 'react-apollo'; import ProductItem from './ProductItem'; class SearchBox extends Component { search = (keyword) => { this.props.data.refetch({ varibles: { keyword: keyword } }); } render() { return ( <div> <input type="text" placeholder="Search anything!" onChange={e => search(e.target.value)} /> <div classname="result"> { data.products && data.products.length > 0 && data.products.map(product => { <ProductItem product={product} /> })} </div> </div> ); } } const productQuery = gql` query products($keyword: String) { products(keyword: $keyword) { id name price description } } `; export default graphql(productQuery)(SearchBox);
If your data comes from Redux store, you can use componentWillReceiveProps to detect data changes and manually refetch data.
Compose many queries together
In most of the examples we only have one query per component. But sometimes we need to have more than 1 query per component, for example: we want to to have fetch data and delete an item feature in one component. How do we do it ? Luckily, Apollo provides compose function which helps us connect many query together. Here is an example
import { graphql, compose } from 'react-apollo'; import { connect } from 'react-redux'; export default compose( graphql(query, queryOptions), graphql(mutation, mutationOptions), connect(mapStateToProps, mapDispatchToProps) )(Component);
Thanks for reading, if you have a cool GraphQL + Apollo trick, let me now. I will put it into the article and we spread our experience to everyone.