Server-Side GraphQL with Apollo & NextJS — Part 2: The Client-Side

Ben Grunfeld
Jun 29, 2020 · 8 min read

Looking for a great (remote only) React Dev? Visit my profile on LinkedIn and say hi! 😃

In my previous article in this series, we went over how to set up a full-stack application using GraphQL, Apollo, and NextJS, and I finished off with the promise that in this article, I would go over each line on the Client Side in depth.

I’m going to try to reduce my number of broken promises this year by 1, and come good on this one. It’s still at double digits, but hey, nobody’s perfect, right? 😂

One of the main take-away’s from the last article was that you need both Client-Side and Server-Side GraphQL code to make it work. The Client sends the request and the Server receives the it, fetches the data, and sends it as a response.

If you want to learn all about Client-Side GraphQL syntax, please check out my other article on the subject: Basic Client-Side GraphQL Tutorial For Beginners.

Let’ Start with The Client-Side

We’re going to focus on the Client-Side in this article.

In the code below, we set up the client using apollo-boost, and then supply that as a prop to the ApolloProvider. The ApolloProvider is similar to React’s Context API, and is used to make the client available to any part of your app that is lower in the component tree — in this case BookInfo. Of course, any of BookInfo’s children would also have access to the client.

pages/index.jsx

There’s a LOT going on in the above file. We’ll break it down piece by piece. Read through the code, and you’ll see the following important parts:

  1. a GraphQL Query template string
  2. a GraphQL Mutation template string
  3. The BookInfo Component
  4. a useQuery hook that takes the Query template string as a param
  5. a useMutation hook that takes the Mutation template string as a param
  6. an updateCache function which updates the local cache with the new data
  7. a function called updateBookDetails which wraps the Mutation function and supplies it to the component, where it is triggered via an onClick event
components/BookInfo/BookInfo.jsx

Ok, let’s go over all the pieces one at a time:

We use the gql function to create a GraphQL Query that requests the data that we want. This Query will later be used by the useQuery hook. Specifically, it requests the author and name fields on the book object.

You can put anything into this Query that you learned about in my Basic Client-Side GraphQL article.

You can read more about the gql function here, but here is a short excerpt from the docs:

gql: A JavaScript template literal tag that parses GraphQL query strings into the standard GraphQL AST.

The Mutation template string also uses the gql function. It defines what data it wants to mutate, and sets up variables, so we can define what we want the new data to be later on.

If the dollar sign syntax is throwing you, please go and read my other article, which explains the syntax clearly.

This is a standard React component, but even though it does not take any props, it has access to the client we defined in pages/index.jsx, since it was wrapped in the ApolloProvider.

That is why we don’t need to define an endpoint for the useQuery or useMutation hooks or set up a new connection— they already have access to that data via the client which is available in the Context.

We use the useQuery hook to request data from the Server. To do so, we supply it with a GraphQL Query using the gql function.

After the hook runs and the Server returns something, the hook returns an object containing loading, error, and data properties, which you can destructure using ES6. You can use loading and error to check the state of the request, as we’ve done above, and of course, data will contain the data we requested if everything is ok.

If loading is false and there is no error, then the Query completed successfully.

A very cool feature is that any other component that displays data using a useQuery hook that takes the same Query string as a param will update when the Mutation is run.

Apollo will also cache the results when they return from the Server, so any subsequent identical Queries will use cached data, and should thus be very fast. If you’re worried about displaying stale data from the cache, read Apollo’s docs on how to pull fresh data.

There are also other params that useQuery can take, like variables and displayName. To see them all, check out the full useQuery API docs here.

If you wanted to make it a dynamic Query whose input is dependent on a variable that will be supplied to it, we could do something like this:

const GET_BOOK_BY_AUTHOR = gql`
query Book($author: String!) {
book(author: $author) {
name
author
}
}
`;
const BookInfo = ({ author }) => {
const { loading, error, data } = useQuery(GET_BOOK_DETAILS, { variables: { author },
});
return (<p>{data.book.name} - {data.book.author}</p>)
})

This way, we take author as a prop to the component, and then supply it as a variable to the useQuery hook at render time.

If you want the data on your page to update when an event happens on the Server, and you don’t want to use polling, have a look at Subscriptions. Subscriptions push data from the Server to any Client that is listening.

Lastly, useQuery runs automatically when the Component is rendered. If you want the Query to run later on, and NOT at render time — e.g. when a User clicks a button — then use useLazyQuery instead. useLazyQuery returns a function that executes the Query whenever you call it. Read about it here.

The useMutation hook is used to request that data be changed on the Server (i.e. a Mutation). Just like useQuery, we need to supply it with a Mutation template string. In the code above, that is SET_BOOK_DETAILS .

Now, we don’t want the request to automatically execute when the component renders —i.e. what the useQuery hook does. Instead, we want to be able to choose the exact time when it will execute. This may be in response to an event (e.g. User clicks on button) or anything else you like. It’s for you to decide.

To enable this choice of execution time, the useMutation hook returns a function which, when run, will execute the request. The hook also returns a data object which you can use to check on the status of the request after it is sent.

const QUERY_SRTING = gql`some mutation`const [updateBook, { 
loading: mutationLoading,
error: mutationError
}] = useMutation(QUERY_STRING)

Updating the Cache

The mutation only changes the data on the server. If this same data is being used in your app (especially in the UI) and you have already queried it, then it is likely being stored in the cache, which prevents you from needing to send out another request every time you want to get to that data.

So when we execute a mutation, we also want to update the cache once it is successful, so that any UI that uses the affected data gets updated. There are two ways to do this:

Updating a single entity

If there is only a single entity that you are updating, and it already exists in the cache, Apollo Client will update it automatically. The magic that happens in the background is that Apollo Server by default return the id of the entity, as well as the values that were changed, and Apollo Client then grabs these and performs the necessary update to the cache.

Manually Updating the Cache

If you are creating a new entity, deleting an old entity, or updating multiple entities, you will need to update the cache manually. We do this by supplying an update function to the useMutation hook. You can see this in the code above (even though it’s only a single existing entity, I added it so you could see a real working example).

const [updateBook] = useMutation(SET_BOOK_DETAILS, { update: updateCache });

The desired end result of this update function is that your cache should match your back-end’s version of the data.

const updateCache = (cache, { data: { updateBook } }) => {
const existingBook = cache.readQuery({
query: GET_BOOK_DETAILS,
});
cache.writeQuery({
query: GET_BOOK_DETAILS,
data: { book: updateBook },
});
};

The first thing you’ll notice is that updateCache takes a param called cache, which is a copy of the local Apollo Client cache.

There are two main methods that exist on the cache object — readyQuery and writeQuery. These two methods enable you to read from and write to the cache.

The { data: { updateBook } } object you see, which is the second param, contains the returned result of the mutation that occurred on the Server.

To update the cache, we first read the contents of the cache for the query we wish to target — in this case GET_BOOK_DETAILS. We then store the result in the variable existingBook. If we were going to update multiple values (e.g. add an item to an array), we would use this variable to update the data in the way that we wanted (since it’s only a single item, we don’t do anything with it) and use it in writeQuery, and then we finally use writeQuery to overwrite the existing data in the cache.

Just as the title suggests, we wrap the updateBook Mutation function and call it in the event below. Of course, you could go ahead and supply the update function directly to the event, but I find this to be a messier approach. It causes some code bloat, and you may want to run some other code before you go and execute it.

By wrapping the update function in another function, you can first perform some conditional logic before executing the request. You can also make some other changes to your app, etc. In my opinion, it’s simply a cleaner and more versatile approach.

Conclusion

In this article, we focussed on the Client Side of an Apollo app. In my next article — Part 3 of this series — we will go into the Server-Side in depth. Stay tuned, and I’ll update this article with a link to it when it’s ready.

Have a great day! 😁

The Startup

Get smarter at building your thing. Join The Startup’s +793K followers.

Sign up for Top 10 Stories

By The Startup

Get smarter at building your thing. Subscribe to receive The Startup's top 10 most read stories — delivered straight into your inbox, once a week. Take a look.

By signing up, you will create a Medium account if you don’t already have one. Review our Privacy Policy for more information about our privacy practices.

Check your inbox
Medium sent you an email at to complete your subscription.

Ben Grunfeld

Written by

I’m a Front End Engineer who loves React, NextJS, and GraphQL. Need a REMOTE React Developer? Contact me at: https://www.linkedin.com/in/bengrunfeld/

The Startup

Get smarter at building your thing. Follow to join The Startup’s +8 million monthly readers & +793K followers.

Ben Grunfeld

Written by

I’m a Front End Engineer who loves React, NextJS, and GraphQL. Need a REMOTE React Developer? Contact me at: https://www.linkedin.com/in/bengrunfeld/

The Startup

Get smarter at building your thing. Follow to join The Startup’s +8 million monthly readers & +793K followers.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store