React Hooks are stateful functions that are used to maintain the state in a functional component. Basically, they break down complex React components by splitting them into smaller functional blocks.
The main problem with React class components is the need to maintain lots of abstractions, such as higher-order components (HOCs) and render props. React Hooks maintain the logic as a function, eliminating the need to encapsulate it.
Take a look at the following example.
Classes VS React Hooks
Classes VS React Hooks by LucaColonnello using react, react-dom, react-scripts
GraphQL is a data query language that fetches only the data it needs rather than fetching all the data from the API. It has two operations: queries and mutations. For real-time data, GraphQL uses a concept called subscriptions.
There are two major React Hooks libraries: graphql-hooks
and apollo/react-hooks
. To help you determine which library is best for your next GraphQL project, let’s compare them, examine their features, and weigh the pros and cons.
We’ll spin up a quick project to facilitate our comparison. Let’s implement a chat application that enables the user to log in and send group messages.
I won’t spend too much time on the backend, but here’s a quick glimpse at how I set it up for this application:
Basically, I used Hasura to set up GraphQL and a Postgres database. This easy-to-use tool enables you to create a backend in minutes.
Our backend contains two tables:
The backend URL is https://hasura-infiite-loader.herokuapp.com/v1alpha1/graphql; the WebSocket URL is ws://hasura-infiite-loader.herokuapp.com/v1alpha1/graphql.
To implement our app using Apollo, React Hooks, and React, we must first set up a React project using the following command.
npx create-react-app apollo-react-hook-example
After that, install all the dependencies of the @apollo/react-hooks
package.
npm install @apollo/react-hooks apollo-client apollo-link-http apollo-link-ws apollo-link apollo-utilities apollo-cache-inmemory subscriptions-transport-ws
That’s a lot of packages! Let’s break them down one by one.
@apollo/react-hooks
provides all the React Hooks required to use GraphQL with apollo-client
. It contains useQuery
, useMutation
, and useSubscription
to execute all the GraphQL operationsapollo-client
provides all the packages you need to run the caching operations on the client side. It is often used with apollo-link-http
and apollo-cache-memory
apollo-link-http
is a chainable unit of operation that you can apply to your GraphQL request. It executes the unit one after another. Here we use an HTTP link to execute the GraphQL HTTP requestapollo-link-ws
creates a WebSocket link for the GraphQL clientapollo-link
the two functionalities described above fall under apollo-link
apollo-utilities
provides utility functions for apollo-client
apollo-cache-inmemory
provides caching functionalities for GraphQL requestssubscription-transport-ws
is used with apollo-link-ws
to facilitate GraphQL subscriptionsNow it’s time to set up @apollo/react-hooks
with our application. Import all the packages into App.js
.
import ApolloClient from "apollo-client"; import { ApolloProvider } from "@apollo/react-hooks"; import { WebSocketLink } from "apollo-link-ws"; import { HttpLink } from "apollo-link-http"; import { split } from "apollo-link"; import { getMainDefinition } from "apollo-utilities"; import { InMemoryCache } from "apollo-cache-inmemory";
Set up the HTTP and WebSocket links with the server.
const httpLink = new HttpLink({ uri: "https://hasura-infiite-loader.herokuapp.com/v1alpha1/graphql" // use https for secure endpoint }); // Create a WebSocket link: const wsLink = new WebSocketLink({ uri: "ws://hasura-infiite-loader.herokuapp.com/v1alpha1/graphql", // use wss for a secure endpoint options: { reconnect: true } });
Once we have httpLink
and wsLink
, we need to split the request links so we can send different data to each link.
// using the ability to split links, you can send data to each link // depending on what kind of operation is being sent const link = split( // split based on operation type ({ query }) => { const { kind, operation } = getMainDefinition(query); return kind === "OperationDefinition" && operation === "subscription"; }, wsLink, httpLink );
Let’s create the Apollo client and configure it to Apollo Provider
// Instantiate client const client = new ApolloClient({ link, cache: new InMemoryCache() }); function App() { return ( <ApolloProvider client={client}> <ThemeProvider theme={customTheme}> <div className="App"> <Routes /> </div> </ThemeProvider> </ApolloProvider> ); }
Complete the source code for App.js
.
import React from "react"; import logo from "./logo.svg"; import "./App.css"; import customTheme from "./theme"; import { ThemeProvider } from "@chakra-ui/core"; import Routes from "./routes"; import ApolloClient from "apollo-client"; import { ApolloProvider } from "@apollo/react-hooks"; import { WebSocketLink } from "apollo-link-ws"; import { HttpLink } from "apollo-link-http"; import { split } from "apollo-link"; import { getMainDefinition } from "apollo-utilities"; import { InMemoryCache } from "apollo-cache-inmemory"; const httpLink = new HttpLink({ uri: "https://hasura-infiite-loader.herokuapp.com/v1alpha1/graphql" // use https for secure endpoint }); // Create a WebSocket link: const wsLink = new WebSocketLink({ uri: "ws://hasura-infiite-loader.herokuapp.com/v1alpha1/graphql", // use wss for a secure endpoint options: { reconnect: true } }); // using the ability to split links, you can send data to each link // depending on what kind of operation is being sent const link = split( // split based on operation type ({ query }) => { const { kind, operation } = getMainDefinition(query); return kind === "OperationDefinition" && operation === "subscription"; }, wsLink, httpLink ); // Instantiate client const client = new ApolloClient({ link, cache: new InMemoryCache() }); function App() { return ( <ApolloProvider client={client}> <ThemeProvider theme={customTheme}> <div className="App"> <Routes /> </div> </ThemeProvider> </ApolloProvider> ); } export default App;
Now we’ll create Routes.js
for our application.
import React from "react"; import { BrowserRouter as Router, Route, Switch } from "react-router-dom"; import LoginComponent from "./components/login"; import Chat from "./components/Chat"; const Routes = () => ( <Router> <Route exact path="/" component={LoginComponent} /> <Route path="/chat" component={Chat} /> </Router> ); export default Routes;
We have three main components:
Let’s examine these in more detail.
Functionality for the login component is pretty simple. Our app will have a form where for the user to enter a name and password.
The GraphQL operation we need here is mutation. We’ll use a React Hook called useMutation
. We’ll also use react-hook-form
for form validation and chakraUI
for UI.
import { useMutation } from "@apollo/react-hooks"; import gql from "graphql-tag"; const LOGIN_USER = gql` mutation InsertUsers($name: String!, $password: String!) { insert_users(objects: { name: $name, password: $password }) { returning { id name } } } `;
We have a mutation GraphQL operation that takes name
and password
as parameters and executes the insert_users
mutation.
Next, define the useMutation
hooks inside the login component with mutation GraphQL.
const [insert_users, { data }] = useMutation(LOGIN_USER);
Here is the complete source code for Login
/index.js
:
import React, { useState, useEffect } from "react"; import { useForm } from "react-hook-form"; import { FormErrorMessage, FormLabel, FormControl, Input, Button, Box } from "@chakra-ui/core"; import { useMutation } from "@apollo/react-hooks"; import gql from "graphql-tag"; const LOGIN_USER = gql` mutation InsertUsers($name: String!, $password: String!) { insert_users(objects: { name: $name, password: $password }) { returning { id name } } } `; const Login = ({ history }) => { const [state, setState] = useState({ name: "", password: "" }); const [insert_users, { data }] = useMutation(LOGIN_USER); useEffect(() => { const user = data && data.insert_users.returning[0]; if (user) { localStorage.setItem("user", JSON.stringify(user)); history.push("/chat"); } }, [data]); const { handleSubmit, errors, register, formState } = useForm(); function validateName(value) { let error; if (!value) { error = "Name is required"; } return error || true; } function validatePassword(value) { let error; if (value.length <= 4) { error = "Password should be 6 digit long"; } return error || true; } const onInputChange = e => { setState({ ...state, [e.target.name]: e.target.value }); }; const onSubmit = () => { insert_users({ variables: { name: state.name, password: state.password } }); setState({ name: "", password: "" }); }; return ( <Box> <form onSubmit={handleSubmit(onSubmit)}> <FormControl isInvalid={errors.name}> <FormLabel htmlFor="name">Name</FormLabel> <Input name="name" placeholder="name" onChange={onInputChange} ref={register({ validate: validateName })} /> <FormErrorMessage> {errors.name && errors.name.message} </FormErrorMessage> </FormControl> <FormControl isInvalid={errors.password}> <FormLabel htmlFor="name">Password</FormLabel> <Input name="password" type="password" placeholder="password" onChange={onInputChange} ref={register({ validate: validatePassword })} /> <FormErrorMessage> {errors.password && errors.password.message} </FormErrorMessage> </FormControl> <Button mt={4} variantColor="teal" isLoading={formState.isSubmitting} type="submit" > Submit </Button> </form> </Box> ); }; export default Login;
The chat component will primarily use two GraphQL operations: mutation and subscription. Since our chat app is a real-time application, we need to subscribe to get the updated data.
For that, we need the useSubscription
React Hook to subscribe and the useMutation
Hook to make the HTTP POST request on GraphQL.
import { useMutation, useSubscription } from "@apollo/react-hooks"; import gql from "graphql-tag"; const MESSAGES_SUBSCRIPTION = gql` subscription { messages { id text users { id name } } } `; const SUBMIT_MESSAGES = gql` mutation InsertMessages($text: String!, $userid: Int!) { insert_messages(objects: { text: $text, created_user: $userid }) { returning { text created_user users { name id } id } } } `;
MESSAGES_SUBSCRIPTION
is a subscription GraphQL schema definition. SUBMIT_MESSAGES
is a mutation GraphQL schema definition.
We’ll use both in our chat component.
const [insert_messages, { returnData }] = useMutation(SUBMIT_MESSAGES); const { loading, error, data: { messages } = [] } = useSubscription( MESSAGES_SUBSCRIPTION );
Messages from useSubscription
will return updated data whenever there is a change in messages from GraphQL.
Here is the complete source code for Chat
/index.js
:
import React, { useState, useEffect } from "react"; import { Box, Flex, Input } from "@chakra-ui/core"; import ChatItem from "../ChatItem"; import { useMutation, useSubscription } from "@apollo/react-hooks"; import gql from "graphql-tag"; const MESSAGES_SUBSCRIPTION = gql` subscription { messages { id text users { id name } } } `; const SUBMIT_MESSAGES = gql` mutation InsertMessages($text: String!, $userid: Int!) { insert_messages(objects: { text: $text, created_user: $userid }) { returning { text created_user users { name id } id } } } `; const Chat = () => { const [state, setState] = useState({ text: "" }); const [insert_messages, { returnData }] = useMutation(SUBMIT_MESSAGES); const { loading, error, data: { messages } = [] } = useSubscription( MESSAGES_SUBSCRIPTION ); const onInputChage = e => { setState({ [e.target.name]: e.target.value }); }; const onEnter = e => { if (e.key === "Enter") { let user = localStorage.getItem("user"); user = JSON.parse(user); insert_messages({ variables: { text: state.text, userid: user.id } }); setState({ text: "" }); } }; return ( <Box h="100vh" w="40%" margin="auto"> <Flex direction="column" h="100%"> <Box bg="blue" h="90%" w="100%" border="solid 1px" overflowY="scroll"> {messages && messages.map(message => { return <ChatItem item={message} />; })} </Box> <Box bg="green" h="10%" w="100%"> <Input placeholder="Enter a message" name="text" value={state.text} onChange={onInputChage} onKeyDown={onEnter} size="md" /> </Box> </Flex> </Box> ); }; export default Chat;
ChatItem
/index.js
:
import React from "react"; import { Box, Flex, Avatar, Heading, Text } from "@chakra-ui/core"; const ChatItem = ({ item }) => { return ( <Box h="60px"> <Flex direction="row" alignItems="center" height="100%"> <Avatar size="sm" padding="4px" marginLeft="10px" /> <Flex direction="column" margin="5px"> <Text fontSize="xl" margin="0"> {item.users.name} </Text> <Text margin="0">{item.text}</Text> </Flex> </Flex> </Box> ); }; export default ChatItem;
So far, we’ve shown how to use @apollo/react-hooks
with React. Now let’s walk through how to set up and use graphql-hooks
with a React application.
npm install graphql-hooks subscriptions-transport-ws
graphql-hooks
provides hooks for GraphQL operations, such as useQuery
, useMutation
, and useSubscriptions
subscriptions-transport-ws
-provides SubscriptionClient
for WebSocket to use in GraphQL subscriptionsApp.js
:
import React from "react"; import customTheme from "./theme"; import { ThemeProvider } from "@chakra-ui/core"; import { GraphQLClient, ClientContext } from "graphql-hooks"; import { SubscriptionClient } from "subscriptions-transport-ws"; import Routes from "./routes"; import "./App.css"; const client = new GraphQLClient({ url: "https://hasura-infiite-loader.herokuapp.com/v1alpha1/graphql", subscriptionClient: new SubscriptionClient( "ws://hasura-infiite-loader.herokuapp.com/v1alpha1/graphql" ) }); function App() { return ( <ClientContext.Provider value={client}> <ThemeProvider theme={customTheme}> <div className="App"> <Routes /> </div> </ThemeProvider> </ClientContext.Provider> ); } export default App;
We created a GraphQL client with HTTP and WebSocket links and used it with Context Provider.
Now that we’ve set up GraphQL Hooks, we can use it in our components. We’ll create the same components we created during the @apollo/react-hooks
setup.
Spoiler alert: there is not much of a change in components.
This will be similar to the Apollo setup except for two things: we’re going to import graphql-hooks
, and we don’t need graphql-tags
to define the schema.
Otherwise, the steps are the same.
import React, { useState, useEffect } from "react"; import { useForm } from "react-hook-form"; import { FormErrorMessage, FormLabel, FormControl, Input, Button, Box } from "@chakra-ui/core"; import { useMutation } from "graphql-hooks"; const LOGIN_USER = ` mutation InsertUsers($name: String!, $password: String!) { insert_users(objects: { name: $name, password: $password }) { returning { id name } } } `; const Login = ({ history }) => { const [state, setState] = useState({ name: "", password: "" }); const [insert_users, { data }] = useMutation(LOGIN_USER); useEffect(() => { const user = data && data.insert_users.returning[0]; if (user) { localStorage.setItem("user", JSON.stringify(user)); history.push("/chat"); } }, [data]); const { handleSubmit, errors, register, formState } = useForm(); function validateName(value) { let error; if (!value) { error = "Name is required"; } return error || true; } function validatePassword(value) { let error; if (value.length <= 4) { error = "Password should be 6 digit long"; } return error || true; } const onInputChange = e => { setState({ ...state, [e.target.name]: e.target.value }); }; const onSubmit = () => { insert_users({ variables: { name: state.name, password: state.password } }); setState({ name: "", password: "" }); }; return ( <Box w="50%" margin="auto"> <form onSubmit={handleSubmit(onSubmit)}> <FormControl isInvalid={errors.name}> <FormLabel htmlFor="name">Name</FormLabel> <Input name="name" placeholder="name" onChange={onInputChange} ref={register({ validate: validateName })} /> <FormErrorMessage> {errors.name && errors.name.message} </FormErrorMessage> </FormControl> <FormControl isInvalid={errors.password}> <FormLabel htmlFor="name">Password</FormLabel> <Input name="password" type="password" placeholder="password" onChange={onInputChange} ref={register({ validate: validatePassword })} /> <FormErrorMessage> {errors.password && errors.password.message} </FormErrorMessage> </FormControl> <Button mt={4} variantColor="teal" isLoading={formState.isSubmitting} type="submit" > Submit </Button> </form> </Box> ); }; export default Login;
Chat
/index.js
import React, { useState, useEffect } from "react"; import { Box, Flex, Input } from "@chakra-ui/core"; import ChatItem from "../ChatItem"; import { useMutation, useSubscription } from "graphql-hooks"; const MESSAGES_SUBSCRIPTION = ` subscription { messages { id text users { id name } } } `; const SUBMIT_MESSAGES = ` mutation InsertMessages($text: String!, $userid: Int!) { insert_messages(objects: { text: $text, created_user: $userid }) { returning { text created_user users { name id } id } } } `; const Chat = () => { const [state, setState] = useState({ text: "", data: [] }); const [errors, setErrors] = useState(null); const [insert_messages, { returnData }] = useMutation(SUBMIT_MESSAGES); // const { loading, error, data: { messages } = [] } = useSubscription( // MESSAGES_SUBSCRIPTION // ); useSubscription({ query: MESSAGES_SUBSCRIPTION }, ({ data, error }) => { if (errors && errors.length > 0) { setErrors(errors[0]); return; } setState({ ...state, data: data.messages }); }); const onInputChage = e => { setState({ ...state, [e.target.name]: e.target.value }); }; const onEnter = e => { if (e.key === "Enter") { let user = localStorage.getItem("user"); user = JSON.parse(user); insert_messages({ variables: { text: state.text, userid: user.id } }); setState({ ...state, text: "" }); } }; return ( <Box h="100vh" w="40%" margin="auto"> <Flex direction="column" h="100%"> <Box bg="blue" h="90%" w="100%" border="solid 1px" overflowY="scroll"> {state.data.map(message => { return <ChatItem item={message} />; })} </Box> <Box bg="green" h="10%" w="100%"> <Input placeholder="Enter a message" name="text" value={state.text} onChange={onInputChage} onKeyDown={onEnter} size="md" /> </Box> </Flex> </Box> ); }; export default Chat;
ChatItem
/index.js
import React from "react"; import { Box, Flex, Avatar, Heading, Text } from "@chakra-ui/core"; const ChatItem = ({ item }) => { return ( <Box h="60px"> <Flex direction="row" alignItems="center" height="100%"> <Avatar size="sm" name={item.users.name} padding="4px" marginLeft="10px" /> <Flex direction="column" margin="5px"> <Text fontSize="xl" margin="0"> {item.users.name} </Text> <Text margin="0">{item.text}</Text> </Flex> </Flex> </Box> ); }; export default ChatItem;
Let’s summarize the difference between graphql-hooks
and apollo-react-hooks
by analyzing some of the main concepts.
As far as GraphQL operations such as query, mutation, and subscription, both libraries are similar. They both have the same set of hooks that can be used for GraphQL operations.
Both Apollo hooks and GraphQL hooks have options for caching.
GraphQL Hooks include graphql-hooks-memcache
.
import { GraphQLClient } from 'graphql-hooks' import memCache from 'graphql-hooks-memcache' const client = new GraphQLClient({ url: '/graphql', cache: memCache() })
Meanwhile, Apollo Hooks provides apollo-cache-inmemory
.
import { InMemoryCache } from 'apollo-cache-inmemory'; import { HttpLink } from 'apollo-link-http'; import { ApolloClient } from 'apollo-client'; const client = new ApolloClient({ link: new HttpLink(), cache: new InMemoryCache() });
Another advantage of Apollo caching is that there are additional options to configure the caching, such as getting the data ID from the object and cache redirect. Apollo also provides options for cache interaction.
Since Apollo Provides an Apollo Link, we can control the execution of the GraphQL operation by providing links. Common Apollo link functionalities include retries, live queries, alternative caching layers, and offline support.
Both GraphQL Hooks and Apollo provide packages for server-side rendering. In my experience, both work well.
You should now have a basic understanding of the packages for implementing React Hooks for GraphQL. So which one is best for your GraphQL project? There’s no right or wrong answer — it all depends on your app’s unique needs and your personal preference. I tend to gravitate toward graphql-hooks
because it is simple to use and easy to implement, but I’d encourage you to try both and see which you like best.
LogRocket is like a DVR for web and mobile apps, recording literally everything that happens on your site. Instead of guessing why problems happen, you can aggregate and report on problematic GraphQL requests to quickly understand the root cause. In addition, you can track Apollo client state and inspect GraphQL queries' key-value pairs.
LogRocket instruments your app to record baseline performance timings such as page load time, time to first byte, slow network requests, and also logs Redux, NgRx, and Vuex actions/state. Start monitoring for free.Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowDeno is a popular JavaScript runtime, and it recently launched version 2.0 with several new features, bug fixes, and improvements […]
Generate OpenAPI API clients in Angular to speed up frontend development, reduce errors, and ensure consistency with this hands-on guide.
Making carousels can be time-consuming, but it doesn’t have to be. Learn how to use React Snap Carousel to simplify the process.
Consider using a React form library to mitigate the challenges of building and managing forms and surveys.
One Reply to "Comparing hooks libraries for GraphQL"
Could you please share link to repository? Thanks 🙂