In this article, we'll take a look at how to convert an existing React Application that uses GraphQL to utilise React hooks.
I had the chance to deliver a few workshops for O'Reilly on React & GraphQL. When these workshops were delivered React Hooks were not a thing, unfortunately. The example that you see in this article is taken from that workshop.
You can access the codebase on GitHub.
The base application is relatively simple: there is a page to list cars. A standard React component is being used, and there's nothing special about it to be fair. The data is coming from a RESTful API that has a GraphQL layer on top of it. The API and the GraphQL service was something that I have built for demonstration purposes and therefore it's really simple.
This article is not introducing the basic concepts of GraphQL; however, if you're interested in learning more you can read this article, or get the Practical GraphQL: Become a GraphQL Ninja online video course.
Let's take a look at the React component which is listing all the cars:
import React, { Fragment } from 'react';
import { Query } from 'react-apollo';
import gql from 'graphql-tag';
import { Link } from '@reach/router';
export const GET_CARS = gql`
{
cars {
id
make
model
}
}
`;
export default function Cars() {
return (
<Query query={GET_CARS}>
{({ data, loading, error }) => {
if (loading) return <p>Loading ...</p>;
if (error) return <p>ERROR</p>;
return (
<Fragment>
<h1>A list of cars 🚗</h1>
{data.cars &&
data.cars.map((car) => (
<p key={car.id}>
{car.make} - {car.model} >>{' '}
<Link to={`/car/${car.id}`}>View more</Link>
</p>
))}
</Fragment>
);
}}
</Query>
);
}
The above code is something that you have probably come across and seen a few times before if you have worked with React & GraphQL. We store a GraphQL query in the GET_CARS
variable, and we execute that using the <Query>
component provided to us by react-apollo
. Notice that in the return
statement of our component, we are iterating through data.cars
to list and display the cars in our component.
This code - even though it's achieving something simple - already looks a bit too complicated. Let's see how we can rewrite it to make it more appealing and less convoluted.
Hooks were introduced with React 16.8, allowing developers to get better state management. Before hooks, developers had to manage the state via this.state
for class components, like so:
import React from 'react';
class MyClassComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0,
};
}
render() {
return (
<div>
<p>You clicked {this.state.count} times</p>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Click me
</button>
</div>
);
}
}
export default MyClassComponent;
The same piece of code can be replaced with the following utilising hooks:
import React, { useState } from 'react';
function MyHookComponent() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
);
}
export default MyHookComponent;
The above already looks a lot more expressive and readable because the amount of code achieving the same functionality has been reduced. Have you noticed something peculiar? We have changed our component from a class component to a functional component.
Using hooks, React solved the problem of lifecycle methods and state (management) for functional components, meaning that commonly applied patterns such as render-props or HOCs (Higher Order Components) are not needed anymore.
If you're wondering whether you could use Hooks inside class components, the answer lies within the React Docs, and I quote: "Hooks don't work inside classes".
Okay, now that we know what hooks are, we can go ahead and change our GraphQL implementation as well to leverage them.
First, we need to install the appropriate Apollo package: npm install @apollo/react-hooks
. Once this is done, there are two changes that we need to implement:
<ApolloProvider>
is imported// index.js
import { ApolloProvider } from '@apollo/react-hooks';
// ... code removed for brevity
ReactDOM.render(
<ApolloProvider client={client}>
<Pages />
</ApolloProvider>,
document.getElementById('root')
);
// cars.js
import React, { Fragment } from 'react';
import { useQuery } from '@apollo/react-hooks';
import gql from 'graphql-tag';
import { Link } from '@reach/router';
export const GET_CARS = gql`
{
cars {
id
make
model
}
}
`;
export default function Cars() {
const { data, loading, error } = useQuery(GET_CARS);
if (loading) return <p>Loading ...</p>;
if (error) return <p>ERROR</p>;
return (
<Fragment>
<h1>A list of cars 🚗</h1>
{data.cars &&
data.cars.map((car) => (
<p key={car.id}>
{car.make} - {car.model} >>{' '}
<Link to={`/car/${car.id}`}>View more</Link>
</p>
))}
</Fragment>
);
}
Plese check out this blogpost to learn more about
@apollo/react-hooks
Notice that in the code sample for cars.js
above, we are importing useQuery
from @apollo/react-hooks
. The query looks just like in the first example; however, this time around the component itself looks a lot simpler. The query is executed via const { data, loading, error } = useQuery(GET_CARS);
and there's no need to use the <Query>
component anymore. This is a massive reduction in the boilerplate code required to execute queries.
React hooks change how developers work with functional components and state in React. Hooks have additional use-cases, but the one that we have covered in this article is related to the Apollo GraphQL package and how it simplifies components where GraphQL queries are made, and data is processed.