GraphQL Client
In this article, we created a simple GraphQL server using Apollo Server. Although dealing with Apollo server and querying it directly is fun, Apollo really shines when paired with a view layer like React. While I have experience with other GraphQL Clients such as urql, I must confess Apollo Client, which is primarily a state management library, remains my favorite.
Getting Started 🚀
Create a new project from scratch:
$ npx create-react-app library-app
Inside the app, install the required apollo
and graphQL
dependencies:
$ npm i @apollo/client graphql
@apollo/client is a package that contains all that is needed to set up Apollo Client while graphql is a package that helps with parsing graphQL queries into valid GraphQL documents.
Initialize GraphQL Client
With the dependencies in place, we can initialize an instance of the Apollo client.
Start by importing the necessary symbols from the @apolloclient
package.
src/index.js
// ...
import { ApolloClient, ApolloProvider, HttpLink, InMemoryCache, gql } from "@apollo/client"
const client = new ApolloClient({
cache: new InMemoryCache(),
link: new HttpLink({
uri: "http://localhost:4000",
}),
})
const root = ReactDOM.createRoot(document.getElementById("root"))
root.render(
<ApolloProvider client={client}>
<App />
</ApolloProvider>
)
We create client object, which is an instance of ApolloClient. The client receives two arguments:
-
- cache is an instance of InMemoryCache and is used by Apollo Client to cache results after fetching them,
-
- link is an instance of HttpLink. It connects GraphQL to our server. The uri parameter receives the url of the server
To complete the setup, we wrap our React app with the ApolloProvider component, also imported from @apollo/client
. This will avail the client instance to the entire application.
The ApolloProvider component leverages React’s Context API under the hood to avail the client instance to the entire React component tree.
Make GraphQL queries
To write GraphQL queries, we use the gql
function from the graphql
package. The query is written as a template string as shown below:
src/queries.js
import { gql } from "@apollo/client";
export const ALL_AUTHORS = gql`
query {
authorList {
id
name
age
}
}
`;
export const GET_BOOKS = gql`
query {
bookList {
id
title
author {
name
age
}
published
}
}
`;
When executed, the two queries will fetch a list of books and their authors from the GraphQL server:
import { GET_BOOKS } from './queries';
client.query({ query: GET_BOOKS }).then((res) => {
console.log(res);
});
This will log the fetched books data.
Fetching Data Using GraphQL hooks
Apollo Client provides a great hooks API for fetching and mutating data. To retrieve data from the server, we use the useQuery hook:
import { useQuery } from "@apollo/client";
import { GET_BOOKS } from './queries';
function App() {
const { data } = useQuery(GET_BOOKS)
// ...
}
Import and call the useQuery
hook, passing in your defined query as an argument. This will return an object with a data
property that contains the result of the query. Other properties contained in the result object are loading
and error
, which you can use to determine what to render.
GraphQL Variables
To make the app more dynamic and allow user input, GrapQL provides a way to pass variables. For example, let’s say we want to add a search feature to our library app that allows users to search for books by title. We could write a GraphQL query that accepts a author
variable to filter the results:
src/queries.js
// ...
export const SEARCH_BOOKS = gql`
query findBooksByAuthor($author: String!) {
findBooksByAuthor(where: { author: $author }) {
title
published
}
}
`
We name our query findBooksByAuthor
and declare the variable(s) we want to use for the query and their types. The syntax is $variableName: type
.
To pass in the author
variable when executing the query, you can use the useQuery hook and pass in an options object with a variables property:
// ...
function SearchForm({ author }) {
const { data, loading } = useQuery(SEARCH_BOOKS, {
variables: { author }
});
if (loading) {
return <p>Loading...</p>;
}
if (data) {
return (
<ul>
{data.bookList.map(book => (
<li key={book.title}>{book.title} by {book.author}</li>
))}
</ul>
);
}
return null;
}
Here, we are passing the author
value as a prop to the SearchForm component, and passing it as a variable
to the useQuery
hook.
useLazyQuery
When React renders a component that has useQuery, Apollo Client automatically executes that query. But sometimes, you will want to execute a query only in response to user events e.g. when a button is clicked.
useLazyQuery is perfect for running event-based queries for example the search or filtering functionality. Unlike useQuery, useLazyQuery does not execute its associated query immediately. Instead, useLazyQuery returns a query function in its results that you can call in response to an event.
Let’s say we have a form that allows users to search books by author:
import { useLazyQuery } from 'react-apollo';
import { SEARCH_BOOKS } from './queries';
function SearchForm({ author }) {
const [searchBooks, { data, loading }] = useLazyQuery(SEARCH_BOOKS);
const handleSubmit = e => {
e.preventDefault();
searchBooks({ variables: { author } });
};
return (
<form onSubmit={handleSubmit}>
<input type="text" value={author} onChange={e => setauthor(e.target.value)} />
<button type="submit">Search</button>
{loading && <p>Loading...</p>}
{data && (
<ul>
{data.books.map(book => (
<li key={book.title}>{book.title} by {book.author}</li>
))}
</ul>
)}
</form>
);
}
When the form is submitted, the handleSubmit
function is called, which calls the searchBooks
function with the author
variable. This manually triggers the execution of the GraphQL query, and the data
and loading
variables are updated with the results of the query.
Alternatively, we can also stick with useQuery and use its skip field that enables the query to run only if a certain condition is met. This prevents the query from being executed automatically on component render:
function SearchForm({ author }) {
const { data, loading } = useQuery(SEARCH_BOOKS, {
variables: { author },
skip: !author
});
const handleSubmit = e => {
e.preventDefault();
// No need to manually execute the query here, as it will be automatically
// executed when the author value changes
};
// ...
}
We set the value of skip to the negation of author
. This will cause the query to be skipped (and not executed) if author is falsy (e.g. an empty string) and will cause the query to be executed if author has a truthy value (e.g. a non-empty string).
Apollo Cache
How To Update Data After a Mutation
After running a mutation, the next natural thing would be to update your UI inorder to reflect the mutated data. There are a couple of ways to achive this:
Refetch Queries
Update Apollo Cache
Conclusion
In this article, we learned how to set up a simple library app with React, GraphQL, and Apollo. We installed the necessary dependencies, set up the Apollo client, and wrote a GraphQL query to fetch a list of books from a server.