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!
use
redux
Managing state, focusing on how to get the data; whileapollo
Focus 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
- You can use
create-react-app
Create 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
- You can also set up React projects online on CodesandBox. Easy and fast!
Build GraphQL service
- You can fork on GithubgraphpackProject and then use
Making account
The logincodesandbox
And import this project, you can build an online GraphQL service with zero configuration. This document is available at the time of writingcodesandbox
Set up a service for your reference:kdvmr.sse.codesandbox.io/ - 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 packageapollo-cache-inmemory
: Indicates the officially recommended cache packageapollo-link-http
: package used to get remote dataapollo-link-error
: package used to handle errorsapollo-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-boost
Exported Apollo Client object (detailsAPI) : A large collection object that integrates official core functionalityapollo-client
Exported Apollo Client object (detailsAPI) : the default is where the App isThe same host
On theGraphQL endpoint
, to customizeuri
Still need to introduceapollo-link-http
The package. If you’re usingapollo v1.x
, can be directly fromapollo-client
Bags exportcreateNetworkInterface
For 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
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 for
mutation
The GraphQL (mutation), mutation requires a parameter of type stringtype
. It wraps the GraphQL statement for mutation ingql
Method, and pass it toMutation component
theprops
. Mutation component
I need oneAnonymous functions
As aA subroutine
Also called the Render Prop function, same as the Query component, but with different parameters.Render prop function
The first argument to isMutation component
Internally 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 Client
Mutation 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 function
The 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:
cache
I can talk about this parameter in detail in several lectures, so I’m just going to go into it in detailAPI
- The cache is usually
InMemoryCache
Constructor provided to the Apollo Client when creating the Apollo Client. Please return toCreate an Apollo clientReview)InMemoryCache
From a single packageapollo-cache-inmemory
. If you useapollo-boost
The package is already included in the package, so there is no need to install it again.- Cache has several utility functions, for example
cache.readQuery
andcache.writeQuery
, which allow you to read and write to the cache using GraphQL.- There are other ways, for example
cache.readFragment
.cache.writeFragment
andcache.writeData
In detailAPI.mutationResult
An object whose data property stores the results of mutation (the data obtained after the POST request)API
- If you specifyPositive response, will be updated twice
update
Function: One is an optimistic result and the other is an actual result.- You can use your variation results to do so
cache.writeQuery
Update 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
useredux
Managing state, focusing on how to get the data; whileapollo
Focus 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 invocation
cache.writeData
Write directly to the cache. inUpdate the cacheWe’ve gone over that in detailcache.writeData
And the restThe update function
With 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 and
apollo-server
The 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 instancecontext.cache
ReadQuery,.writequery,.readFragment,.writeFragment, and.writeData: a series of instances used to manipulate the CacheAPIcontext.getCacheKey
Use:__typename
andid
The 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!