Manage Local State using Apollo by extending the GraphQL Schema on the Client

Nik Graf
InstructorNik Graf
Share this video with your friends

Social Share Links

Send Tweet
Published 5 years ago
Updated 5 years ago

With the introduction of apollo-link-state Apollo for the first time introduced a way to manage local state through GraphQL queries and mutations. This can be achieved using the @client directive.

In this lesson we will leverage this feature to keep track of starred recipes and store the information in the localStorage.

Instructor: [00:00] In this lesson, we want to extend our application to allow users starring their favorite recipes. Therefore, we want to add an isStarred field to the recipe. Since our schema doesn't support this field, we plan to keep this information solely on the client. Namely, store it in a local storage.

[00:19] To query a field only on the client, we can leverage the @client decorator, and add it to a field in a query or a mutation. Once added, this field is never queried on the remote endpoint, yet there is no resolve with resolveTheField.

[00:36] We need to provide one. In our app.js file, we create resolvers object, and for the type, recipe, add an isStarred resolver. Temporarily return the value false for all recipes. We then can use the resolver's object, and pass it into the client state property, during the Apollo client initialization.

[00:59] This is all we need to already being able to retrieve the isStarred value in our recipes list. We render a star next to each of the titles, and the color should change, depending on the state. Orange, if the recipe is starred, and gray if it's not.

[01:24] Let's refresh the page, and verify that all stars are inactive. Great. Next up, we need a mutation, allowing us to update the isStarred field of a recipe. Therefore, we extend our client-side resolvers with an updateRecipeStarred mutation. Our plan is to store a starred recipes array in the local storage. Before we update it, we retrieve the list.

[02:02] In case the variable isStarred is set to true, we append the current ID. In case it's set to false, we filled out existing recipe ID. In the end, we return an object with the type name and the isStarred value. This is useful in case a developer wants to query the updated value. Since we now have the mutation, and know how we store the starred recipes, you also can update the retrieving resolver.

[02:47] In the resolver, we check if the starred recipes array includes the ID of the current parent recipe, and return the result. At this point, we have everything we need to start using our mutation. Therefore, we add a new mutation to the recipes.js file.

[03:16] The mutation accepts two variables, ID and isStarred. This matches what we implemented in our mutation. In our case, we won't query any data from the mutation, but we have to append the @client decorator to make sure this mutation is resolved only on the client. After that, we import the mutation component and wrap the star span with it.

[03:44] Then we pass in our updateRecipeStarred mutation. Since this mutation affects the result of our recipe query, we provide the two refetch queries. Once again, set awaitRefetchQueries to true. After that, we replace the span with a button and add an onClick handler. Once clicked, we invoke the mutation with the current recipe ID and the reverse isStarred value.

[04:33] Next, we add a class named starButton. To make sure the star looks actually nice, I prepared this class name before the lesson, and added the stars to the index.html file. Depending on the loading state, we add in animation. Last, but not least, render text in case an error occurred. Now, we should be able to star our recipes.

[05:07] We refresh the page and give it a try. Voila, works like a charm. It even works after checking the vegetarian filter. To verify that our stars were cached in the local storage, we refresh the page once again. Great, our two recipes are still starred.

Syarif Banjar
Syarif Banjar
~ 5 years ago

Is the recipe actually updated on the server without returning a response?, what if the localStorege data has been deleted?

Nik Graf
Nik Grafinstructor
~ 5 years ago

@Shareef the favorite state is only stored in localStorage. If it is wiped the information is gone as well as it's not stored on any server. Depending on your requirements this might not what you want and a mutation to persist the data might be better.

Alec
Alec
~ 5 years ago

@Nik what tool do you use here to get the star font / emoji in vscode?

umut
umut
~ 5 years ago

Thanks Nik, fast and nice explanation. Very good example of usage in a short time!

janppires
janppires
~ 5 years ago

hi @Alec, google search "macos emoji" for the emoji list.

Nik Graf
Nik Grafinstructor
~ 5 years ago

@Nik what tool do you use here to get the star font / emoji in vscode?

Ctrl + Cmd + Space opens the selector

Bress B
Bress B
~ 5 years ago

Do resolvers have to be created for each localstorage state? In terms of remote querying remote data, are resolvers defined behind the scenes by Apollo?

Also since the example resolvers is passed into clientState of client object, does this mean that all properties/fields resolvers passed into clientState are designated as localStorage only? Thank you

Overall I find this video very confusing as you're introducing quite a few concepts without providing sufficient backgrounder.

Nik Graf
Nik Grafinstructor
~ 5 years ago

@Bress yes, each local state part of the query needs a resolved. In a resolver you can return more complex data structures (which must map the query structure).

Sorry for introducing too many new concepts. You could check out the new official docs here: https://www.apollographql.com/docs/react/essentials/local-state/ I hope this clarifies things.

Markdown supported.
Become a member to join the discussionEnroll Today