---
title: '8. Manage local state'
description: How to store and query local data in the Apollo cache
---
Time to accomplish: _15 Minutes_
In almost every app we build, we display a combination of remote data from our graph API and local data such as network status, form state, and more. What's awesome about Apollo Client is that it allows us to store local data inside the Apollo cache and query it alongside our remote data with GraphQL.
We recommend managing local state in the Apollo cache instead of bringing in another state management library like Redux so the Apollo cache can be a single source of truth.
Managing local data with Apollo Client is very similar to how you've already managed remote data in this tutorial. You'll write a client schema and resolvers for your local data. You'll also learn to query it with GraphQL just by specifying the `@client` directive. Let's dive in!
### Write a local schema
Just like how a schema is the first step toward defining our data model on the server, writing a local schema is the first step we take on the client.
Navigate to `src/resolvers.js` and copy the following code to create your client schema (as well as blank client resolvers for later):
_src/resolvers.js_
```js
import gql from 'graphql-tag';
export const typeDefs = gql`
extend type Query {
isLoggedIn: Boolean!
cartItems: [ID!]!
}
extend type Launch {
isInCart: Boolean!
}
extend type Mutation {
addOrRemoveFromCart(id: ID!): [Launch]
}
`;
export const resolvers = {};
```
To build a client schema, we **extend** the types of our server schema and wrap it with the `gql` function. Using the extend keyword allows us to combine both schemas inside developer tooling like Apollo VSCode and Apollo DevTools.
We can also add local fields to server data by extending types from our server. Here, we're adding the `isInCart` local field to the `Launch` type we receive back from our graph API.
## Initialize the store
Now that we've created our client schema, let's learn how to initialize the store. Since queries execute as soon as the component mounts, it's important for us to warm the Apollo cache with some default state so those queries don't error out. We will need to write initial data to the cache for both `isLoggedIn` and `cartItems`:
Jump back to `src/index.js` and notice we had already added a `cache.writeData` call to prepare the cache in the last section. While we're here, make sure to also import the `typeDefs` and `resolvers` that we just created so we can use them later:
_src/index.js_
```js{1,11-12,15-20}
import { resolvers, typeDefs } from './resolvers';
const client = new ApolloClient({
cache,
link: new HttpLink({
uri: 'http://localhost:4000/graphql',
headers: {
authorization: localStorage.getItem('token'),
},
}),
typeDefs,
resolvers,
});
cache.writeData({
data: {
isLoggedIn: !!localStorage.getItem('token'),
cartItems: [],
},
});
```
Now that we've added default state to the Apollo cache, let's learn how to query local data from within our React components.
## Query local data
Querying local data from the Apollo cache is almost the same as querying remote data from a graph API. The only difference is that you add a `@client` directive to a local field to tell Apollo Client to pull it from the cache.
Let's look at an example where we query the `isLoggedIn` field we wrote to the cache in the last mutation exercise.
_src/index.js_
```jsx{8-12,15}
import { ApolloProvider, useQuery } from '@apollo/react-hooks';
import gql from 'graphql-tag';
import Pages from './pages';
import Login from './pages/login';
import injectStyles from './styles';
const IS_LOGGED_IN = gql`
query IsUserLoggedIn {
isLoggedIn @client
}
`;
function IsLoggedIn() {
const { data } = useQuery(IS_LOGGED_IN);
return data.isLoggedIn ?
ERROR: {error.message}
; return (No items in your cart
) : ({data.bookTrips.message}
: ( ); } ``` In this example, we're directly calling `cache.writeData` to reset the state of the `cartItems` after the `BookTrips` mutation is sent to the server. This direct write is performed inside of the update function, which is passed our Apollo Client instance. ### Local resolvers We're not done yet! What if we wanted to perform a more complicated local data update such as adding or removing items from a list? For this situation, we'll use a local resolver. Local resolvers have the same function signature as remote resolvers (`(parent, args, context, info) => data`). The only difference is that the Apollo cache is already added to the context for you. Inside your resolver, you'll use the cache to read and write data. Let's write the local resolver for the `addOrRemoveFromCart` mutation. You should place this resolver underneath the `Launch` resolver we wrote earlier. _src/resolvers.js_ ```js export const resolvers = { Mutation: { addOrRemoveFromCart: (_, { id }, { cache }) => { const { cartItems } = cache.readQuery({ query: GET_CART_ITEMS }); const data = { cartItems: cartItems.includes(id) ? cartItems.filter(i => i !== id) : [...cartItems, id], }; cache.writeQuery({ query: GET_CART_ITEMS, data }); return data.cartItems; }, }, }; ``` In this resolver, we destructure the Apollo `cache` from the context in order to read the query that fetches cart items. Once we have our cart data, we either remove or add the cart item's `id` passed into the mutation to the list. Finally, we return the updated list from the mutation. Let's see how we call the `addOrRemoveFromCart` mutation in a component: _src/containers/action-button.js_ ```js import gql from 'graphql-tag'; const TOGGLE_CART = gql` mutation addOrRemoveFromCart($launchId: ID!) { addOrRemoveFromCart(id: $launchId) @client } `; ``` Just like before, the only thing we need to add to our mutation is a `@client` directive to tell Apollo to resolve this mutation from the cache instead of a remote server. Now that our local mutation is complete, let's build out the rest of the `ActionButton` component so we can finish building the cart: _src/containers/action-button.js_ ```jsx import React from 'react'; import { useMutation } from '@apollo/react-hooks'; import gql from 'graphql-tag'; import { GET_LAUNCH_DETAILS } from '../pages/launch'; import Button from '../components/button'; const CANCEL_TRIP = gql` mutation cancel($launchId: ID!) { cancelTrip(launchId: $launchId) { success message launches { id isBooked } } } `; export default function ActionButton({ isBooked, id, isInCart }) { const [mutate, { loading, error }] = useMutation( isBooked ? CANCEL_TRIP : TOGGLE_CART, { variables: { launchId: id }, refetchQueries: [ { query: GET_LAUNCH_DETAILS, variables: { launchId: id }, }, ] } ); if (loading) returnLoading...
; if (error) returnAn error occurred
; return (