Welcome to my blog to read this article.


Many of the examples in this document come from official documentation, but I have also added some of my understanding of the stack in the writing process. If you prefer official documentation, please go to the official website/official documentation

Why Apollo? No redux complicated actions, reducer, dispatch… Make global store management simple and straightforward!

usereduxManaging state, focusing on how to get the data; whileapolloFocus on what data you need. This is very important to understand!

All right, without further ado, let’s get started!

The preparatory work

Create the React project

  1. You can usecreate-react-appCreate a React app quicklycreate-react-appFriends can know in advance.
npm i create-react-app -g

create-react-app react-apollo-client-demo --typescript

cd react-apollo-client-demo

npm start
Copy the code
  1. You can also set up React projects online on CodesandBox. Easy and fast!

Build GraphQL service

  1. You can fork on GithubgraphpackProject and then useMaking accountThe logincodesandboxAnd import this project, you can build an online GraphQL service with zero configuration. This document is available at the time of writingcodesandboxSet up a service for your reference:kdvmr.sse.codesandbox.io/
  2. You can also set up your own GraphQL service locally. Since this document is not covered, the setup steps are not provided.

Install the required packages

Redux, Redux, Redux, React-Redux, React-Redux, React-Redux, Etc.

PS: Back in Apollo 1.0, local state management functionality (described later in this document) relied on related technologies such as Redux. But now that Apollo has been upgraded to 2.0, it has completely abandoned its reliance on Redux.

npm install apollo-boost react-apollo graphql --save
Copy the code

Let’s take a look at what these three bags do:

  • apollo-boost: contains the core packages needed to set up the Apollo Client. If you need to customize the project as you wish, you can install a separate package of your own:apollo-client: Apollo client package
    • apollo-cache-inmemory: Indicates the officially recommended cache package
    • apollo-link-http: package used to get remote data
    • apollo-link-error: package used to handle errors
    • apollo-link-state: local state management package (version 2.5 has been integrated intoapollo-client)
  • React – Apollo: React layer integration
  • Graphql: Parse the GraphQL query

Instantiate the Apollo client

Note that apollo-boost and Apollo-client both provide ApolloClient, but they require slightly different parameters. See their respective apis for details:

  • apollo-boostExported Apollo Client object (detailsAPI) : A large collection object that integrates official core functionality
  • apollo-clientExported Apollo Client object (detailsAPI) : the default is where the App isThe same hostOn theGraphQL endpoint, to customizeuriStill need to introduceapollo-link-httpThe package. If you’re usingapollo v1.x, can be directly fromapollo-clientBags exportcreateNetworkInterfaceFor the method and usage, see (1.x to 2.x guide)[Github.com/apollograph…]

Let’s take a look at instantiating the Apollo client using Apollo-Boost:

import ApolloClient from 'apollo-boost'

const client = new ApolloClient({
    / / if you really can't find ready-made server, you can use the Apollo's website: https://48p1r2roz4.sse.codesandbox.io or this tutorial services: https://kdvmr.sse.codesandbox.io/
    uri: 'Your GraphQL service link'
})
Copy the code

And instantiating the Apollo client with apollo-client:

import ApolloClient from 'apollo-client'
import { createHttpLink } from 'apollo-link-http'

const client = new ApolloClient({
  link: createHttpLink({ 
    uri: 'Your GraphQL service link'})})Copy the code

Write GraphQL query statements

If you’re not familiar with GraphQL syntax, take a look at GraphQL basics

To illustrate the GraphQL query, let’s take a look at some code using a normal request:

import { gql } from 'apollo-boost'

// Instantiate Apollo client

client.query({
  query: gql` { rates(currency: "CNY") { currency } } `
}).then(result= > console.log(result));
Copy the code

In addition to importing GQL from Apollo-Boost, you can also import it from the graphQL-tag package:

import gql from 'graphql-tag';

gql`... `
Copy the code

The obvious purpose of GQL () is to parse query strings into query documents.

Connect the Apollo client to React

// ...
import React from 'react'
import { ApolloProvider } from 'react-apollo'

const App: React = () => {
  // ...
  
  return (
    <ApolloProvider client={client}>
      <div>App content</div>
    </ApolloProvider>
  )
}

export default App
Copy the code

ApolloProvider (verbose API) has a required parameter client.

Like Redux, which wraps the React App with the component, React-Apollo needs the
component to wrap the entire React App in order to place the instantiated client in context. It can be accessed anywhere in the component tree. Alternatively, you can wrap the component with withApollo to get the client instance inside the component (there are many other ways to get the instance, described later in the documentation), see withApollo() for details.

Query and Mutation components

In GraphQL, the query operation represents query, and the mutation operation represents add, delete, and change. They correspond to GET and POST requests of REST API. However, it should be noted that query may not be a GET request in the actual request process.

Get data –The Query component

import { Query } from "react-apollo"; import { gql } from "apollo-boost"; const ExchangeRates = () => ( <Query query={gql` { rates(currency: "USD") { currency rate } } `} > { ({ loading, error, data }) => { if (loading) return <p>Loading... </p>; if (error) return <p>Error :(</p>; return data.rates.map(({ currency, rate }) => ( <div key={currency}> <p>{currency}: {rate}</p> </div> )); } } </Query> );Copy the code

Congratulations, you just created your first React Query component! 🎉 🎉 🎉

Code parsing:

As you can see, there is an anonymous function in the Query component. This anonymous function takes parameters, such as Loading, error, and data. They represent the loading status of the component, the loading error of the component, and the data that the component is loaded into.

The Query component is the React component exported from The React – Apollo, which shares GraphQL data with the UI using render Prop mode. The Query component also has a number of other props, such as:

  • children(Display the UI to render based on the query results)
  • variables(Used to pass query parameters to GQL ())
  • skip(Skip this query, for example, authentication fails during login, we skip this query, login fails)

See the Query API for more props

Update data –Mutation component

Updating data includes additions, modifications, and deletions using the Mutation component. The Mutation component, like the Query component, uses the Render Prop mode, but the props are different, the Mutation API

import gql from 'graphql-tag';
import { Mutation } from "react-apollo";

const ADD_TODO = gql`
  mutation AddTodo($type: String!) {
    addTodo(type: $type) {
      id
      type
    }
  }
`;

const AddTodo = () => {
  let input;

  return (
    <Mutation mutation={ADD_TODO}>
      {
        (addTodo, { data }) => (
          <div>
            <form
              onSubmit={e => {
                e.preventDefault();
                addTodo({ variables: { type: input.value } });
                input.value = "";
              }}
            >
              <input
                ref={node => {
                  input = node;
                }}
              />
              <button type="submit">Add Todo</button>
            </form>
          </div>
        )
      }
    </Mutation>
  );
};
Copy the code

Let’s comb through the code:

  • First, create formutationThe GraphQL (mutation), mutation requires a parameter of type stringtype. It wraps the GraphQL statement for mutation ingqlMethod, and pass it toMutation componenttheprops.
  • Mutation componentI need oneAnonymous functionsAs aA subroutineAlso called the Render Prop function, same as the Query component, but with different parameters.
  • Render prop functionThe first argument to isMutation componentInternally definedmutate()Function. To improve the readability of the code, this is calledaddTodo. You can also use it directly"Mutate"Represents the mutate function to tell by calling itApollo ClientMutation is triggered (which triggers a POST request to submit the form. You can see in the onSubmit event that the addTodo function was called).
  • Render prop functionThe second argument to is an object that has multiple properties, including data (mutation, the return value of the POST request), Loading (loading state), and error (error information during loading)The Query component.

The mutate function (the addTodo function named above) optionally accepts variables such as:

  • optimisticResponse
  • RefetchQueries and update (these functions are used later to update the cache)
  • IgnoreResults: Ignores the results returned by the mutation operation (i.e. ignores the value returned by the POST request)

You can also pass these values to the Mutation component as props. For details, go to the Mutate API

So at this point, we can make a client request, we can get a result back from the server, and then we can work on what to do with that data and render it to the UI. Let’s take a look at how redux handles this step:

  • Dispatch triggers a data request
  • The Reducer processes new data based on previously defined actions and saves the data in the store
  • The React-redux connect connects the Store and React components
  • MapStateToProps/mapDisToProps complete render prop.

Apollo did this manually, line by line.

  • cache.writeQuery()

Yes, you read that correctly, it is this API, the above redux requires a lot of code to complete the data update! WriteQuery is a way of telling the Apollo Client: We’ve successfully sent a POST request and got the result back, now we’re sending it to you and you can update your local cache. When you perform query or mutation, the UI will automatically update the USER interface (UI) based on the new data.

Update cache — internal automatic query after mutation

Sometimes, when mutation is performed, the GraphQL server and Apollo cache become out of sync. This happens when the update being performed depends on data already in the local cache. For example, when a list was cached locally, when an item in the list was deleted or a new data was added, after mutation, the GraphQL server and the local cache were inconsistent. We needed a way to tell Apollo Client to update the query of the list of items. To obtain the list data of new items after mutation; Or we just submitted a form using mutation, and the form’s data was not cached locally, so we didn’t need a new query to update the local cache.

Let’s look at some code:

import gql from 'graphql-tag';
import { Mutation } from "react-apollo";

const ADD_TODO = gql`
  mutation AddTodo($type: String!) {
    addTodo(type: $type) {
      id
      type
    }
  }
`;

const GET_TODOS = gql`
  query GetTodos {
    todos
  }
`;

const AddTodo = () => {
  let input;

  return (
    <Mutation
      mutation={ADD_TODO}
      update={(cache, { data: { addTodo } }) => {
        const { todos } = cache.readQuery({ query: GET_TODOS });
        cache.writeQuery({
          query: GET_TODOS,
          data: { todos: todos.concat([addTodo]) },
        });
      }}
    >
      {addTodo => (
        <div>
          <form
            onSubmit={e => {
              e.preventDefault();
              addTodo({ variables: { type: input.value } });
              input.value = "";
            }}
          >
            <input
              ref={node => {
                input = node;
              }}
            />
            <button type="submit">Add Todo</button>
          </form>
        </div>
      )}
    </Mutation>
  );
};
Copy the code

As you can see from this code, the update() function can be passed to the Mutation component as props, but it can also be passed to the mutate function as prop, i.e. :

// Use the mutate (rename to addTodo) function above as an example
addTodo({
  variables: { type: input.value },
  update: (cache, data: { addTodo }) = > {
    // ...}})Copy the code

Update: (cache: DataProxy, mutationResult: FetchResult) : used to update the cache after mutation

Parameters:

  • cacheI can talk about this parameter in detail in several lectures, so I’m just going to go into it in detailAPI
    • The cache is usuallyInMemoryCacheConstructor provided to the Apollo Client when creating the Apollo Client. Please return toCreate an Apollo clientReview)
    • InMemoryCacheFrom a single packageapollo-cache-inmemory. If you useapollo-boostThe package is already included in the package, so there is no need to install it again.
    • Cache has several utility functions, for examplecache.readQueryandcache.writeQuery, which allow you to read and write to the cache using GraphQL.
    • There are other ways, for examplecache.readFragment.cache.writeFragmentandcache.writeDataIn detailAPI.
  • mutationResultAn object whose data property stores the results of mutation (the data obtained after the POST request)API
    • If you specifyPositive response, will be updated twiceupdateFunction: One is an optimistic result and the other is an actual result.
    • You can use your variation results to do socache.writeQueryUpdate the cache.

As for the update function, when you call cache.writeQuery within it, the update operation triggers broadcastQueries within Apollo. The broadcast query triggers an automatic update of the data associated with the mutation in the cache — automatically querying and updating the UI using the affected component’s GraphQL. So when mutation was performed, we didn’t have to manually perform the query for the relevant component, as the Apollo Client did all the work internally, unlike all the data processing redux did after Dispatch.

Sometimes, the UPDATE function does not need to update the cache for all mutations (such as submitting a form). So, Apollo provides a separate cache.writeQuery method that triggers a query of the relevant cache to update the local cache. Note that only a call to cache.writequery () within the update function triggers broadcast behavior. Anywhere else, cache.writequery only writes to the cache, and changes made are not broadcast to the view layer. To avoid confusion in the code, it is recommended to use the Client. writeQuery method of the Apollo Client instance object Client to write data to the cache when the update function is not in use.

Parsing code:

Since we need to update the query that displays the TODOS list, we first read the data from the cache using cache.readQuery. We then merged the mutation new TODO with the existing TODO list and wrote the queried data back to the cache using cache.writequery. Now that we have specified an update function, once the new toDO is returned from the server, our user interface uses it for reactive updates (broadcast to the GraphQL queries of other components related to this cached data to update the relevant caches to the UI).

Apollo also provides a way to modify the local cache in time to quickly render the UI and trigger queries for the relevant cache, and then actually update the local cache when the query returns new data, as described in Optimistic Updates.

Based on the optimistic UI, if you run the same query twice, you won’t see the loading indicator (the loading field returned by the Apollo Client). Apollo detects whether the current request parameters have changed and decides whether to send a new request to the server.

Apollo stereotype cacheAPI

import gql from 'graphql-tag'; import { Mutation, Query } from "react-apollo"; const UPDATE_TODO = gql` mutation UpdateTodo($id: String! , $type: String!) { updateTodo(id: $id, type: $type) { id type } } `; / / note: The toDOS data obtained by graphQL is an array of objects containing the ID and type fields, Const GET_TODOS = GQL 'query GetTodos {todos}'; const Todos = () => ( <Query query={GET_TODOS}> {({ loading, error, data }) => { if (loading) return <p>Loading... </p>; if (error) return <p>Error :(</p>; return data.todos.map(({ id, type }) => { let input; return ( <Mutation mutation={UPDATE_TODO} key={id}> {updateTodo => ( <div> <p>{type}</p> <form onSubmit={e => { e.preventDefault(); updateTodo({ variables: { id, type: input.value } }); input.value = ""; }} > <input ref={node => { input = node; }} /> <button type="submit">Update Todo</button> </form> </div> )} </Mutation> ); }); }} </Query> );Copy the code

Note: This time, the mutate function (named updateTodo) is not called, nor is the update function passed to the Mutation component, but the UI will be updated immediately. That’s the beauty of a stylized cache.

Formalized Cache — InMemoryCache formalizes data before saving it to storage by splitting the result into individual objects, creating unique identifiers for each object, and storing these objects in flattened data structures (the unique identifiers created are the keys of these objects, known as cache keys). By default, InMemoryCache will attempt to use the common primary keys of ID and _ID as unique identifiers (if they exist with the __typename field on the object).

If id and _id are not specified, or __typename is not specified, InMemoryCache will revert back to the root query according to the hierarchy of objects queried.

For example, root_query.allpeople. 0 will be stored as the cache key of the allPeople[0] object in the data under the root query of the cache. (In a flattened data structure, all objects are under ROOT_QUERY) :

Even if we don’t intend to use the mutation results in our UI, we still need to return the updated ID and properties for our UI to be automatically updated.

In the above code, we do not need to specify the update function because the TODOS query will automatically rebuild the query results using the updated TODO data in the cache.

Combined with the update function described in the previous section — which is not required for every mutation — the reason is that the UI is automatically updated with minimal manual manipulation of the data, based on the generic data structure of the Apollo Cache. With the current backend normalizing the data (especially the unification of the unique identifier ID, the definition of the __typename field), and after query or mutation, we can implement automatic UI updates with little manual processing of the data.

For example, if you only need to update a single piece of data in the cache, you only need to return the ID of the data and the attribute to be updated. In this case, you usually do not need to use the update function.

If you want to customize a unique identifier, that is, to generate a cache key without the default ID, you can use the dataIdFromObject function of the InMemoryCache constructor:

const cache = new InMemoryCache({
  dataIdFromObject: object= > object.key || null
});
Copy the code

Apollo Client does not add the typename to the cache key when specifying a custom dataIdFromObject, so if your ID is not unique among all objects, you may need to include __typename in the dataIdFromObject.

Install the Apollo DevTools extension in Google Chrome (requires scientific Internet access) and you can clearly see the storage status of this canonical cache.

The break

usereduxManaging state, focusing on how to get the data; whileapolloFocus on what data you need. This is very important to understand!

Remember that sentence? We introduced that at the beginning of this document. Does that make sense now? Now, let’s go back and sort out what we’ve learned:

  • After learning how to fetch data (Query), update data and change data (mutation), Apollo and React were combined, and the components could interact with data so easily!
  • After learning Apollo cache, our understanding of Apollo data storage went up another step. We split all the queried objects one by one and flattened a deep-level object in the form of unique identifiers, which were directly displayed in the root query of the cache.
  • After learning about Apollo’s stylized caching, we realized how elegant automatic UI updates can be! We don’t even need to manage the data, just pass it along according to the specification!

Local State Managementdetails

In the first half we touched on remote data interaction between the local and server side, and next we will move into local state management.

Apollo Client has built-in local state handling in version 2.5, allowing local data to be stored in the Apollo cache along with remote data. To access the local data, just use a GraphQL query.

Prior to version 2.5, if you wanted to use local state management, you had to introduce a deprecated package apollo-link-State (API), which was deprecated in version 2.5 because it was integrated into Apollo’s core and no longer maintained as a separate package. In Apollo’s 1.x release, redux was still introduced if local state management was to be implemented.

The Apollo Client has two main ways to perform local state mutations:

  • The first method is through invocationcache.writeDataWrite directly to the cache. inUpdate the cacheWe’ve gone over that in detailcache.writeDataAnd the restThe update functionWith the use of.
  • The second approach is to create a mutation component with GraphQL mutation that calls the local client parser (resolvers). If mutation depends on an existing value in the cache, we recommend using a parser (resolvers, described in the next two sections, for now just knowing it exists, it andapollo-serverThe resolver on the end is exactly the same.

Direct write cache

import React from 'react';
import { ApolloConsumer } from 'react-apollo';

import Link from './Link';

const FilterLink = ({ filter, children }) => (
  <ApolloConsumer>
    {client => (
      <Link
        onClick={() => client.writeData({ data: { visibilityFilter: filter } })}
      >
        {children}
      </Link>
    )}
  </ApolloConsumer>
);
Copy the code

Apollo injects the Apollo Client instance into the Render Prop of the ApolloConsumer component (API) or Query component, so when using these components, we can get the Client instance directly from the component’s props.

Writing directly to the cache does not require the GraphQLmutate or resolvers functions. So instead of using them in the above code, we call client.writeData directly from the onClick event function to write to the cache.

However, it is recommended to use the direct write cache only for simple writes, such as writing strings or one-time writes. It is important to note that direct writes are not implemented as GraphQL mutations, so they should not be included in complex development patterns. It also does not verify that the data you write to the cache is a valid GraphQL data structure. If any of the above is important to you, you should choose to use local resolvers.

@ client instructions

As mentioned in the previous section, the Render Prop for the Query component also contains a Client instance. So with the @client directive, we can easily get local state from the cache or resolvers in the Query component. The @client directive makes it easy to fetch local state from the cache in the Query component, or from the cache through resolvers.

import React from 'react';
import { Query } from 'react-apollo';
import gql from 'graphql-tag';

import Link from './Link';

const GET_VISIBILITY_FILTER = gql`
  {
    visibilityFilter @client
  }
`;

const FilterLink = ({ filter, children }) => (
  <Query query={GET_VISIBILITY_FILTER}>
    {({ data, client }) => (
      <Link
        onClick={() => client.writeData({ data: { visibilityFilter: filter } })}
        active={data.visibilityFilter === filter}
      >
        {children}
      </Link>
    )}
  </Query>
);
Copy the code

Code interpretation:

The @Client directive tells Apollo Client to fetch the data locally (cache or resolvers) instead of sending it to the GraphQL server. The query results on the Render Prop function are automatically updated after a call to client.writeData. All cache writes and reads are synchronized, so there is no need to worry about loading.

Local parser –resolvers

Finally meet you — Resolvers! The previous sections covered resolvers, but now let’s see what they can do.

To mutate GraphQL depending on the local state, all we need to do is specify a corresponding function in the local resolvers object. In the Apollo Client instantiation, the Resolvers are mapped to an object that holds each of the Resolver functions used for local mutations. When the @Client directive is found on the GraphQL field, Apollo Client looks for the corresponding Resolver function in the Resolvers object, which is associated with the resolvers key.

That is, when the query or mutation without @Client instruction is executed, the fields in the GraphQL document are already predefined on the server side, so we only need to write the GraphQL document according to the fields defined by the server side during the query or mutation. When the @Client directive is added, the Apollo Client does not send a request to the server, but instead looks internally for the fields specified in the GraphQL document. But how do we access these fields locally? Maybe they don’t even exist. (For details on how @Client works, please refer to the section on local Data query process in the official documentation, which is not covered in detail in this document due to space reasons.) At this point, we need to define our own way to access these fields — resolver. Define a resolver function in the resolvers object, which can be called by the GraphQL query or mutation when the @client instruction is used. This establishes the connection between the GraphQL query or mutation and the Apollo Client. This function can resolve the query or mutation controlled by the @Client instruction. Therefore, this function is named the parse function, meaning to find the value of the specified field in the GraphQL document from the local parse function.

So where is the analytic function definition?

It is actually in the constructor of ApolloClient, which means that when we instantiate the ApolloClient, we need to pass the resolvers to it.

Resolver, which is exactly the same as the client-server resolver function:

  fieldName: (obj, args, context, info) = > result;
Copy the code

Obj {object}: an object that contains the result returned by the resolver function on the parent field, or a ROOT_QUERY object that is the topmost query or mutation of the DOM tree. Args {object}: an object that contains all the parameters passed into the GraphQL document. For example, if updateNetworkStatus (isConnected: true) is used to trigger a query or mutation, args is {isConnected: true}. Context {object}: Object of context information shared between the React component and the Apollo Client network stack. In addition to any custom context attributes that may exist, the local resolvers always receive the following:

  • context.client: Indicates the Apollo Client instance
  • context.cacheReadQuery,.writequery,.readFragment,.writeFragment, and.writeData: a series of instances used to manipulate the CacheAPI
  • context.getCacheKeyUse:__typenameandidThe value is obtained from the cachekey.info {object}: Information about the execution status of a query. In practice, you may never use this parameter.
import { ApolloClient } from 'apollo-client'; import { InMemoryCache } from 'apollo-cache-inmemory'; const client = new ApolloClient({ cache: new InMemoryCache(), resolvers: { Mutation: { toggleTodo: (_root, variables, { cache, getCacheKey }) => { const id = getCacheKey({ __typename: 'TodoItem', id: variables.id }) const fragment = gql` fragment completeTodo on TodoItem { completed } `; const todo = cache.readFragment({ fragment, id }); const data = { ... todo, completed: ! todo.completed }; cache.writeData({ id, data }); return null; }},}});Copy the code

Code parsing:

To switch todo’s state, you first need to query the cache to find the contents of the toDO’s current state, and then do this by reading fragments from the cache using cache.readFragment. This function takes the fragment and ID, which correspond to the cache key of the item. We get the cache key by calling getCacheKey in the context and passing in the project’s __typename and ID.

Once the Fragment is read, you can toggle the completed state of todo and write the updated data back to the cache. Since we do not intend to use mutation returns in the UI, we return NULL because all GraphQL types can be null by default.

Let’s see how to call the toggleTodo parsing function (trigger toggleTodo mutation) :

import React from 'react'; import { Mutation } from 'react-apollo'; import gql from 'graphql-tag'; const TOGGLE_TODO = gql` mutation ToggleTodo($id: Int!) { toggleTodo(id: $id) @client } `; Const Todo = ({id, completed, text}) => (<Mutation Mutation ={TOGGLE_TODO} variables={{id}}> ToggleTodo inside the non-parser, toggleTodo is the mutate function we introduced earlier, renamed toggleTodo, ToggleTodo => (<li onClick={toggleTodo} style={{textDecoration: completed? 'line-through' : 'none', }} > {text} </li> )} </Mutation> );Copy the code

Code parsing:

First, we create a GraphQL mutation document that takes the ID of the item we want to switch as the only argument. We indicate that this is a local mutation by marking GraphQL’s toggleTodo field with the @Client directive. This tells the Apollo Client to call the toggleTodo parsing function in our local mutation parser (Resolvers) to parse the field. We then create a Mutation component just as we would with remote mutations. Finally, you pass the GraphQL mutation to the component and trigger it in the UI of the Render Prop function.

Querying local Status

Querying local data is very similar to querying a GraphQL server. The only difference is that local queries add @Client directives to the fields to indicate that they should be resolved from the Apollo Client cache or resolvers.

Let’s look at an example:

import React from 'react'; import { Query } from 'react-apollo'; import gql from 'graphql-tag'; import Todo from './Todo'; const GET_TODOS = gql` { todos @client { id completed text } visibilityFilter @client } `; const TodoList = () => ( <Query query={GET_TODOS}> { ({ data: { todos, visibilityFilter } }) => ( <ul> { getVisibleTodos(todos, visibilityFilter).map(todo => ( <Todo key={todo.id} {... todo} /> )) } </ul> ) } </Query> );Copy the code

Code parsing:

Create the GraphQL query and add the @Client directive to the Todos and visibilityFilter fields of the GraphQL document. We then pass the Query to the Query component. The @Client directive lets the Query component know to extract todos and VisibilityFilters from the Apollo Client cache, or to use predefined local resolver parsing.

Since the above query runs immediately after the component is installed, what if there are no items in the cache or no local resolvers defined?

We need to write the initial state to the cache before running the query to prevent incorrect output.

Initialize the local state

Typically, we need to write the initial state to the cache so that none of the components that query the data before mutation is triggered will fail. To do this, prepare a cache for the initial value using cache.writeData.

import { ApolloClient } from 'apollo-client'; import { InMemoryCache } from 'apollo-cache-inmemory'; const cache = new InMemoryCache(); const client = new ApolloClient({ cache, resolvers: { /* ... * /}}); cache.writeData({ data: { todos: [], visibilityFilter: 'SHOW_ALL', networkStatus: { __typename: 'NetworkStatus', isConnected: false, }, }, });Copy the code

Note: Apollo V2.4 and V2.5 write initial local state differently, refer to the official API for details.

Reset local state/cache

import { ApolloClient } from 'apollo-client'; import { InMemoryCache } from 'apollo-cache-inmemory'; const cache = new InMemoryCache(); const client = new ApolloClient({ cache, resolvers: { /* ... * /}}); const data = { todos: [], visibilityFilter: 'SHOW_ALL', networkStatus: { __typename: 'NetworkStatus', isConnected: false, }, }; cache.writeData({ data }); client.onResetStore(() => cache.writeData({ data }));Copy the code

The cache can be reset using the client.onresetStore method.

Request both local state and remote data

mutation ToggleTodo($id: Int!). { toggleTodo(id:$id) @client
  getData(id: $id) {
    id,
    name
  }
}
Copy the code

Simply add the @Client directive after the fields that you want to query locally.

Use the @Client field as a variable

Within the same GraphQL statement, you can also use the state retrieved locally for the next query with the @export directive

import { ApolloClient } from 'apollo-client'; import { InMemoryCache } from 'apollo-cache-inmemory'; import { HttpLink } from 'apollo-link-http'; import gql from 'graphql-tag'; const query = gql` query currentAuthorPostCount($authorId: Int!) { currentAuthorId @client @export(as: "authorId") postCount(authorId: $authorId) } `; const cache = new InMemoryCache(); const client = new ApolloClient({ link: new HttpLink({ uri: 'http://localhost:4000/graphql' }), cache, resolvers: {}}); cache.writeData({ data: { currentAuthorId: 12345, }, }); / /... run the query using client.query, the <Query /> component, etc.Copy the code

In the above example, currentAuthorId is first loaded from the cache and then passed as the authorId variable (specified by the @export (as: “authorId”) directive) into the subsequent postCount field. The @export directive can also be used to select specific fields in a set, such as:

The @export directive can also be used to select specific fields in a set

import { ApolloClient } from 'apollo-client'; import { InMemoryCache } from 'apollo-cache-inmemory'; import { HttpLink } from 'apollo-link-http'; import gql from 'graphql-tag'; const query = gql` query currentAuthorPostCount($authorId: Int!) { currentAuthor @client { name authorId @export(as: "authorId") } postCount(authorId: $authorId) } `; const cache = new InMemoryCache(); const client = new ApolloClient({ link: new HttpLink({ uri: 'http://localhost:4000/graphql' }), cache, resolvers: {}}); cache.writeData({ data: { currentAuthor: { __typename: 'Author', name: 'John Smith', authorId: 12345, }, }, }); / /... run the query using client.query, the <Query /> component, etc.Copy the code

The @export directive is not limited to remote queries; It can also be used to define variables for other @client fields or selection sets :(note the @client directives after the currentAuthorId and postCount fields of the GraphQL document in the following code)

import { ApolloClient } from 'apollo-client'; import { InMemoryCache } from 'apollo-cache-inmemory'; import gql from 'graphql-tag'; const query = gql` query currentAuthorPostCount($authorId: Int!) { currentAuthorId @client @export(as: "authorId") postCount(authorId: $authorId) @client } `; const cache = new InMemoryCache(); const client = new ApolloClient({ cache, resolvers: { Query: { postCount(_, { authorId }) { return authorId === 12345 ? 100:0; }},}}); cache.writeData({ data: { currentAuthorId: 12345, }, }); / /... run the query using client.query, the <Query /> component, etc.Copy the code

Dynamically injected Resolver

Sometimes, when we use code splitting in an APP, such as react-loadable, we do not want all resolvers to be written together when initializing the Apollo client. Instead, we want to split the resolvers into separate modules so that after the APP is compiled, Each module’s individual resolver will be included in its own package, which also helps reduce the size of the entry file. This can be done using addResolvers and setResolvers (APIS), such as the following code:

import Loadable from 'react-loadable';

import Loading from './components/Loading';

export const Stats = Loadable({
  loader: () => import('./components/stats/Stats'),
  loading: Loading,
});
Copy the code
import React from 'react'; import { ApolloConsumer, Query } from 'react-apollo'; import gql from 'graphql-tag'; const GET_MESSAGE_COUNT = gql` { messageCount @client { total } } `; const resolvers = { Query: { messageCount: (_, args, { cache }) => { // ... calculate and return the number of messages in // the cache ... return { total: 123, __typename: 'MessageCount', }; ,}}}; const MessageCount = () => { return ( <ApolloConsumer> {(client) => { client.addResolvers(resolvers); return ( <Query query={GET_MESSAGE_COUNT}> {({ loading, data: { messageCount } }) => { if (loading) return 'Loading ... '; return ( <p> Total number of messages: {messageCount.total} </p> ); }} </Query> ); }} </ApolloConsumer> ); }; export default MessageCount;Copy the code

Get rid of the React tag

Due to different programming habits, some people (like me) don’t like (or get used to) mixing logic code with the React tag, as we’ve seen with all the sample code from the start of this document! Personally, it would be a bad idea to have Query tags, Mutation tags, and various other tags nested in a large project, with all the logic implementation code crammed into the React component. While officials favor this approach (most of the code above is an example of official documentation), they say it’s easier and easier to write this way!

Vary from person to person!

I personally prefer to separate the logic of GraphQL and Apollo from the React component. We can do this using the GraphQL and compose methods provided by the React-Apollo library. Of course, these methods are not the only ones in the React-Apollo package. There are other methods as well. See the React Apollo API

Here is an example of separated code:

import { Avatar, Card, Icon } from 'antd' import gql from 'graphql-tag' import * as React from 'react' import { useEffect, useRef } from 'react' import { compose, graphql } from 'react-apollo' import { QueryState, Typename } from 'src/config/clientState' import { GET_COMPONENTS_LIST, GET_QUERY_STATE } from 'src/graphql' import { getCorrectQueryState } from 'src/util' import Pagination from '.. /pagination' import './index.scss' const { Meta } = Card type Type = { id: number, name: string } type Author = { username: string, email: string, avatar: string } type Component = { id: number, name: string, version: string, chineseName: string, description: string, type: Type, url: string, author: Author, previewUrl: string, isOwn: boolean, isStored: boolean } type ListComponent = { data: { components: Component[], compCount: number, }, componentsCollect: ([id, isCollect]: [number, boolean]) => void, queryState: QueryState } const ListComponent = (props: ListComponent) => { const cardList: React.MutableRefObject<HTMLDivElement | null> = useRef(null) const { data, componentsCollect, QueryState} = props // animation of the component card when loading, With the React ref and use the key attribute useEffect (() = > {if (cardList. Current) {cardList. Current..childnodes. ForEach ((element: HTMLElement, index: number) => { setInterval(() => { element.classList.add('card-load-anim') }, index * 40) }) } }) return ( <div className='m-list'> <div className='m-cards' ref={cardList} key={Math.random()}> { ! data || ! data.components ? null : data.components.map((o: Components, i: number) => { return ( <Card key={`m-list-btn-${i}`} hoverable={true} cover={<img alt='example' src='https://gw.alipayobjects.com/zos/rmsportal/JiqGstEfoWAOHiTxclqi.png' />} actions={ [ <a className={`${o.isOwn ? 'm-list-btn-text-disabled' : 'm-list-btn-text'}`} key={`m-list-btn-${i}-1`} href='javascript:void(0)' onClick={! Bind (null, [O. ID,! O. ISstored]) : null} > <Icon type='copy' /> {O. ISstored? } </a>, <a className='m-list-btn-text' key='m-list-btn-2' href='javascript:void(0)' > <Icon type='file-search' /> document </a>]} > <Meta avatar={<Avatar src='https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png' />} title={o.name} description={o.description} /> </Card> ) }) } </div> <Pagination totalPages={Math.ceil(data.compCount / queryState.pagination.size)} total={data.compCount} /> </div> ) } export default compose( graphql( gql(GET_QUERY_STATE),  { props: ({ data: { queryState } }: any) => ({ queryState }) } ), graphql( gql(GET_COMPONENTS_LIST), { options: ({ queryState }: any) => ({ variables: { ... getCorrectQueryState(queryState) } }) } ), graphql(gql` mutation ($id: Int! , $isCollect: Boolean!) { storeComponent(id: $id, isStore: $isCollect) } `, { props: ({ mutate }: any) => ({ componentsCollect: ([id, isCollect]: [number, boolean]) => { debugger mutate({ variables: { id, isCollect }, optimisticResponse: { __typename: Typename.Mutation, storeComponent: { __typename: Typename.Component, id, isStored: isCollect } } }) } }) }) )(ListComponent)Copy the code

Write at the end:

If you have studied this document carefully, I believe you have already started to develop React application on Apollo stack. If you have any problems in the future, you will learn it quickly by reading the official document.

Due to the limited level, this article is my own translation side to join their own understanding and written, which must not be some improper or wrong place, welcome everyone to correct!