From 2293adb03f23429af75723cc441ce01916699a83 Mon Sep 17 00:00:00 2001 From: Thomas Waldecker Date: Sun, 5 May 2019 19:18:05 +0200 Subject: [PATCH 1/3] docs: change subscription check to prevent ts error (#601) I changed it like it is written in the apollo docs: https://www.apollographql.com/docs/react/advanced/subscriptions And it works. --- docs/guide/apollo/subscriptions.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/guide/apollo/subscriptions.md b/docs/guide/apollo/subscriptions.md index 214e404..09b5a77 100644 --- a/docs/guide/apollo/subscriptions.md +++ b/docs/guide/apollo/subscriptions.md @@ -40,9 +40,9 @@ const wsLink = new WebSocketLink({ const link = split( // split based on operation type ({ query }) => { - const { kind, operation } = getMainDefinition(query) - return kind === 'OperationDefinition' && - operation === 'subscription' + const definition = getMainDefinition(query) + return definition.kind === 'OperationDefinition' && + definition.operation === 'subscription' }, wsLink, httpLink From c466f9893fe34ef83503054cdd3e933778ca5f21 Mon Sep 17 00:00:00 2001 From: Natalia Tepluhina Date: Sun, 12 May 2019 09:54:35 -0500 Subject: [PATCH 2/3] docs: Local state management (#606) * docs: add local state intro * docs: add section about initializing Apollo cache * docs: described local mutations * docs: added extending local schema --- docs/guide/local-state.md | 301 ++++++++++++++++++++++++++++++++++---- 1 file changed, 270 insertions(+), 31 deletions(-) diff --git a/docs/guide/local-state.md b/docs/guide/local-state.md index 7af4431..b1df078 100644 --- a/docs/guide/local-state.md +++ b/docs/guide/local-state.md @@ -1,41 +1,280 @@ # Local state -If you need to manage local data, you can do so with [apollo-link-state](https://github.com/apollographql/apollo-link-state) and the `@client` directive: +## Why use Apollo local state management? + +When you perform GraphQL queries with Apollo, the results of API calls will be stored in **Apollo cache**. Now imagine you also need to store some kind of a local application state and make it available for different components. Usually, in Vue application we can achieve this with [Vuex](https://vuex.vuejs.org/). But having both Apollo and Vuex will mean you store your data in two different places so you have _two sources of truth_. + +Good thing is Apollo has a mechanism of storing local application data to cache. Previously, it used an [apollo-link-state](https://github.com/apollographql/apollo-link-state) library for this. Since Apollo 2.5 release this functionality was included to Apollo core. + +## Creating a local schema + +Just how creating a GraphQL 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. + +Let's create a local schema to describe an item that will serve as a single element of todo-items list. This item should have some text, some property to define if it's already done or not and also an ID to distinguish one todo-item from another. So, it should be an object with three properties: ```js -export default { - apollo: { - hello: gql` - query { - hello @client { - msg - } - } - ` - }, - mounted () { - // mutate the hello message - this.$apollo - .mutate({ - mutation: gql` - mutation($msg: String!) { - updateHello(message: $msg) @client - } - `, - variables: { - msg: 'hello from link-state!' - } - }) - } +{ + id: 'uniqueId', + text: 'some text', + done: false } ``` -This is an increasingly popular way of managing client-side state. Some projects even use it as a replacement of Vuex (or other Flux-inspired solutions). +Now we're ready to add an `Item` type to our local GraphQL schema. -## Examples +```js +//main.js -- [Example project](https://codesandbox.io/s/zqqj82396p) (by @chriswingler) -- [Todo App](https://codesandbox.io/s/x2jr96r8pp) (by @NikkitaFTW) -- [Todo App - Expanded](https://codesandbox.io/s/k3621oko23) (by @ScottMolinari fork of @NikkitaFTW's) +import gql from 'graphql-tag'; ---- +export const typeDefs = gql` + type Item { + id: ID! + text: String! + done: Boolean! + } +`; +``` + +`gql` here stands for the JavaScript template literal tag that parses GraphQL query strings. + +Now we need to add `typeDefs` to our Apollo client + +```js{4-5} +// main.js + +const apolloClient = new ApolloClient({ + typeDefs, + resolvers: {}, +}); +``` + +:::warning WARNING +As you can see, we've added also an empty `resolvers` object here: if we don't assign it to the Apollo client options, it won't recognize the queries to local state and will try to send a request to remote URL instead +::: + +## Extending a remote GraphQL schema locally + +You can not only create a local schema from scratch but also add a local **virtual fields** to your existing remote schema. These fields only exist on the client and are useful for decorating server data with local state. + +Imagine we have a type `User` in our remote schema: + +```js +type User { + name: String! + age: Int! +} +``` + +And we want to add a local-only property to `User`: + +```js +export const schema = gql` + extend type User { + twitter: String + } +`; +``` + +Now, when querying a user, we will need to specify `twitter` field is local: + +```js +const userQuery = gql` + user { + name + age + twitter @client + } +`; +``` + +## Initializing an Apollo cache + +To initialize an Apollo cache in your application, you will need to use an `InMemoryCache` constructor. First, let's import it to your main file: + +```js{4,6} +// main.js + +import ApolloClient from 'apollo-boost'; +import { InMemoryCache } from 'apollo-cache-inmemory'; + +const cache = new InMemoryCache(); +``` + +Now we need to add cache to our Apollo client options: + +```js{4} +//main.js + +const apolloClient = new ApolloClient({ + cache, + typeDefs, + resolvers: {}, +}); +``` + +Right now the cache is empty. To add some initial data to the cache, we need to use `writeData` method: + +```js{9-20} +// main.js + +const apolloClient = new ApolloClient({ + cache, + typeDefs, + resolvers: {}, +}); + +cache.writeData({ + data: { + todoItems: [ + { + __typename: 'Item', + id: 'dqdBHJGgjgjg', + text: 'test', + done: true, + }, + ], + }, +}); +``` + +We've just added an array of `todoItems` to our cache data and we defined that every item has a type name of `Item` (specified in our local schema). + +## Query local data + +Querying local cache is very similar to [sending GraphQL queries to remote server](apollo/queries.md). First, we need to create a query: + +```js +// App.vue + +import gql from 'graphql-tag'; + +const todoItemsQuery = gql` + { + todoItems @client { + id + text + done + } + } +`; +``` + +The main difference with queries to remote API is `@client` directive. This directive specifies that this query should not be executed against remote GraqhQL API. Instead, Apollo client should fetch results from the local cache. + +Now, we can use this query in our Vue component as a usual Apollo query: + +```js +// App.vue + +apollo: { + todoItems: { + query: todoItemsQuery + } +}, +``` + +## Change local data with mutations + +We have two different ways to change the local data: + +- direct write with `writeData` method as we did during [cache initialization](#initializing-an-apollo-cache); +- calling a GraphQL mutation. + +Let's add some mutations to our [local GraphQL schema](#creating-a-local-schema): + +```js{10-14} +// main.js + +export const typeDefs = gql` + type Item { + id: ID! + text: String! + done: Boolean! + } + + type Mutation { + changeItem(id: ID!): Boolean + addItem(text: String!): Item + } +`; +``` + +The `changeItem` mutation will set the Boolean `done` property of the certain item to the opposite. Let's create it using `gql`: + +```js +// App.vue + +const checkItemMutation = gql` + mutation($id: ID!) { + checkItem(id: $id) @client + } +`; +``` + +We defined a _local_ mutation (because we have a `@client` directive here) that will accept a unique identifier as a parameter. Now, we need a _resolver_: a function that resolves a value for a type or field in a schema. + +In our case, resolver will define what changes do we want to make to our local Apollo cache when we have a certain mutation. Local resolvers have the same function signature as remote resolvers (`(parent, args, context, info) => data`). In fact, we will need only args (arguments passed to the mutation) and context (we will need its cache property to read and write data). + +Let's add a resolver to our main file: + +```js +// main.js + +const resolvers = { + Mutation: { + checkItem: (_, { id }, { cache }) => { + const data = cache.readQuery({ query: todoItemsQuery }); + const currentItem = data.todoItems.find(item => item.id === id); + currentItem.done = !currentItem.done; + cache.writeQuery({ query: todoItemsQuery, data }); + return currentItem.done; + }, +}; +``` + +What are we doing here? + +1. read the `todoItemsQuery` from our cache to see what `todoItems` do we have now; +2. looking for an item with given id; +3. change found item `done` property to opposite; +4. write our changed `todoItems` back to cache; +5. return the `done` property as a mutation result. + +Now we need to replace an empty `resolvers` object in Apollo client options with newly created `resolvers`: + +```js{17} +// main.js + +const resolvers = { + Mutation: { + checkItem: (_, { id }, { cache }) => { + const data = cache.readQuery({ query: todoItemsQuery }); + const currentItem = data.todoItems.find(item => item.id === id); + currentItem.done = !currentItem.done; + cache.writeQuery({ query: todoItemsQuery, data }); + return currentItem.done; + }, +}; + +const apolloClient = new ApolloClient({ + cache, + typeDefs, + resolvers, +}); +``` + +After this, we can use the mutation in our Vue component like normal [mutations](apollo/mutations.md) + +```js +// App.vue + +methods: { + checkItem(id) { + this.$apollo.mutate({ + mutation: checkItemMutation, + variables: { id } + }); + }, +} +``` From a7680f6eef9455ff82094a9e9ff8d46ee7768aca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=93scar?= Date: Thu, 16 May 2019 19:54:32 +0200 Subject: [PATCH 3/3] docs: fix example mutation name (#614) On the local schema declared mutations are "changeItem" and "addItem", but the next steps refers to "checkItem". --- docs/guide/local-state.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/guide/local-state.md b/docs/guide/local-state.md index b1df078..0e46be2 100644 --- a/docs/guide/local-state.md +++ b/docs/guide/local-state.md @@ -194,13 +194,13 @@ export const typeDefs = gql` } type Mutation { - changeItem(id: ID!): Boolean + checkItem(id: ID!): Boolean addItem(text: String!): Item } `; ``` -The `changeItem` mutation will set the Boolean `done` property of the certain item to the opposite. Let's create it using `gql`: +The `checkItem` mutation will set the Boolean `done` property of the certain item to the opposite. Let's create it using `gql`: ```js // App.vue