React Hooks , introduced in version 16.8.0, are reusable stateful logic functions. They aim to simplify the development of complex components by splitting them into small functional blocks that are easier to manage, test and reuse.
Using hooks removes the need for many abstractions like Higher Order Components (HOC) and render props. They allow you to add functionality to the application without having to change the component hierarchy and without having to encapsulate components.
Also, it often makes the code more readable and maintainable.
As an example, here is a simple React clock component written using classes:
And here the same component is written using hooks:
In our example, both useState and useEffect are React hooks.
What is GraphQL?
GraphQL is a data query language designed for API. The language is meant to be declarative, strongly typed and exhaustive.
The design has two main types of operations: queries and mutations. The former is used when retrieving data, the latter for updating data.
Another important improvement of GraphQL is that multiple operations can be sent and retrieved using a single endpoint (usually /graphql) and a single network request. This reduces the number of roundtrips and overall data transfer, which is very important on mobile devices and bad network situations.
Here's an example of a GraphQL mutation which adds a new user (and chooses which field to get a result):
And here's an example of a GraphQL query which gets the list of users:
In order to use GraphQL in a React application using hooks, we are going to use graphql-hooks , a small library with hooks support and optional Server-Side Rendering and caching support.
Here's an example of how a real-world component might look like:
Getting started on the server
To get started, let's create an application server which serves a React application.
The graphql.js file is where our tutorial will focus on. Here's the starting version:
The GraphQL adapter we chose on the server is Mercurius . We evaluated other solutions but it had higher performances, as you can see in the comparison below.
Before moving on, let’s analyze the options passed to the adapter.
The very first thing to define when creating a GraphQL API is the schema. The schema must define all the queries, mutation and types supported by the API. In particular, Query and Mutation are considered “entry-point” types and of them should be present in every schema.
In our case, we define only Query (for now) and we define a single query, users, which will return a list (which is denoted using square brackets) of User. The User is the only other type in the schema, which contains a single string field called name. As you might wonder, GraphQL is language-agnostic, so the definition of language syntax tries to be similar to most used languages.
In our example, we also passed the graphiql option set to true. This will add graphiql (note the i ), an in-browser IDE for GraphQL to the server, at the /graphiql route.
Getting started on the client
The initial version of the client application is very simple: only two routes, one of them is a simple "hello world" one. Let's see what they look like.
Here's the application main file:
And here’s the initial version of the ListUsers route:
As you can see, the first version already uses hooks, but it's not connected to GraphQL. Yet. Let's fix this!
Add a mutation to persist the data
In order to let the client update persisted data, we first have to modify the server.
Let's start by adding a mutation to our schema and resolvers in graphql.js:
The server is now able to handle a GraphQL mutation, which is the only way to modify the data.
This can be tested in graphiql by running this operation:
Make sure you don't forget to pass the operation variables:
And then you can verify that the data has been persisted by running the following query:
Close the loop: connect the client
As said earlier, the first implementation of the ListUsers route was only storing data in browser memory without using the server at all.
Since the server is now capable of persisting data, let's connect it to the client.
First, let's modify the main application file to instantiate a graphql-hooks client and add the context provider to the application:
Then, modify the route to use the useQuery and useMutation hook. Here's how the new ListUsers route will look like:
There are many values returned by the useHook query, but for now, let's focus on the main ones. data contains the results of the query returned by the server, while refetch is a callback that enables the client to ask for a data refresh on-demand.
The useMutation hook instead simply returns an async function which will trigger a mutation on the server.
Notice that the component uses both React Hooks (useState) and graphql-hooks hooks. This is an example of where the hooks approach makes it really simple to add new functionality to components (even via external libraries) without having to reorganize the component hierarchy.
And there it is: a fully working client-server application using GraphQL and React hooks.
So far, the users GraphQL has returned all the users stored in the system. As you might imagine, this is not useful in the real world where you usually have thousands or millions of them. So, let's implement pagination.
To achieve this, we are going to use a mechanism you have already seen: query variables. If you look at the createNewUser method above, you will notice that it passes variables to the mutation. This is also possible for queries.
Let's start by modifying the schema and the resolvers in the graphql.js file of the server:
As you can see, the user's query is now parameterized. That's all we need to modify on the server, so let's switch back on the client.
Let's create a new PaginationPage, which will be rendered under the /users path.
Once again, we mix up React and GraphQL hooks in order to implement our component.
The biggest change is on useQuery call, where we now pass query variables.
Finally, we can add the page to the client application index:
Let's also add it to the server index for SSR:
To ensure the best user experience, we don't want to make server queries that we already performed, we should use caching.
graphql-hooks has support for custom caching plugins which solve this problem for us.
Despite what the name suggests, this plugin is not a server plugin backed by MemCache storage, but it is a client plugin which caches every operation by creating a hash of it. The cache is held in memory using a Least Recently Used (LRU) policy.
To use it, simply pass it the client application main file when instantiating the graphql-hooks client:
That's it, it’s that simple!
The last step of this tutorial to have a fully working real-case example is to hook up graphql-hooks in a server-side rendered (SSR) application.
Doing this will allow the same client code to be reused on the server to return fully rendered pages. This improves browser boot and time to be interactive time and also improves search engine optimisation.
Also, by using state passing and rehydration, we can make sure no cache miss happens on the client after initial loading.
To get started, we are going to modify the client initialization to use isomorphic-unfetch . This enables fetch support on server (Node.js) and polyfills on the client if needed.
The newly modified server app shell will look like this:
The main modification is the introduction of the same GraphQL client on the server, with slightly different options: first, we have to specify the full client URL, then we have to specify the fetch method to use.
Then we use getInitialState from graphql-hooks-ssr in order to get the state to send to the client, rendered as serialized JSON inside the renderScripts function.
Once the server is ready, let's modify the client. We have to modify the application main file in order to account for the initialState of the cache and the rehydration of the server-side rendered page.
The main modification are passing the initialState to the cache plugin and to replace the react-dom render with hydrate.
As shown in this post, graphql-hooks is a powerful package to easily add GraphQL to your React application using a very easy to use approach. Also, its plugin-based approach makes it very trivial to add complex features like caching and SSR without any cumbersome abstractions and without revolutionizing your components or application structure.
At NearForm, we have vast experience in building solutions across a broad tech stack to deliver reduced complexities and overcome common hurdles. If you are creating modern applications and leveraging web technologies, contact us to learn more about how we can help. You might also like some of our previous blog posts on React: