最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Apollo Client Write Query Not Updating UI - Stack Overflow

programmeradmin3浏览0评论

We are building an offline first React Native Application with Apollo Client. Currently I am trying to update the Apollo Cache directly when offline to update the UI optimistically. Since we offline we do not attempt to fire the mutation until connect is "Online" but would like the UI to reflect these changes prior to the mutation being fired while still offline. We are using the readQuery / writeQuery API functions from .html#writequery-and-writefragment. and are able to view the cache being updated via Reacotron, however, the UI does not update with the result of this cache update.

    const newItemQuantity = existingItemQty + 1;
    const data = this.props.client.readQuery({ query: getCart, variables: { referenceNumber: this.props.activeCartId } });
    data.cart.items[itemIndex].quantity = newItemQuantity;
    this.props.client.writeQuery({ query: getCart, data });

We are building an offline first React Native Application with Apollo Client. Currently I am trying to update the Apollo Cache directly when offline to update the UI optimistically. Since we offline we do not attempt to fire the mutation until connect is "Online" but would like the UI to reflect these changes prior to the mutation being fired while still offline. We are using the readQuery / writeQuery API functions from http://dev.apollodata.com/core/read-and-write.html#writequery-and-writefragment. and are able to view the cache being updated via Reacotron, however, the UI does not update with the result of this cache update.

    const newItemQuantity = existingItemQty + 1;
    const data = this.props.client.readQuery({ query: getCart, variables: { referenceNumber: this.props.activeCartId } });
    data.cart.items[itemIndex].quantity = newItemQuantity;
    this.props.client.writeQuery({ query: getCart, data });
Share Improve this question asked Aug 29, 2017 at 21:05 DadlesDadles 1191 gold badge1 silver badge10 bronze badges
Add a comment  | 

2 Answers 2

Reset to default 14

If you look at the documentation examples, you will see that they use the data in an immutable way. The data attribute passed to the write query is not the same object as the one that is read. Mutating this object is unlikely to be supported by Apollo because it would not be very efficient for it to detect which attributes you modified, without doing deep copies and comparisons of data before/after.

const query = gql`
  query MyTodoAppQuery {
    todos {
      id
      text
      completed
    }
  }
`;
const data = client.readQuery({ query });
const myNewTodo = {
  id: '6',
  text: 'Start using Apollo Client.',
  completed: false,
};
client.writeQuery({
  query,
  data: {
    todos: [...data.todos, myNewTodo],
  },
});

So you should try the same code without mutating the data. You can use for example set of lodash/fp to help you

const data = client.readQuery({...});
const newData = set("cart.items["+itemIndex+"].quantity",newItemQuantity,data);
this.props.client.writeQuery({ ..., data: newData });

It recommend ImmerJS for more complex mutations

Just to save someones time. Using the data in an immutable way was the solution. Agree totally with this answer, but for me I did something else wrong and will show it here. I followed this tutorial and updating the cache worked fine as I finished the tutorial. So I tried to apply the knowledge in my own app, but there the update didn’t work even I did everything similar as showed in the tutorial.

Here was my approach to update the data using the state to access it in the render method:

// ... imports

export const GET_POSTS = gql`
    query getPosts {
        posts {
            id
            title
        }
     }
 `

class PostList extends Component {

    constructor(props) {
        super(props)

        this.state = {
            posts: props.posts
        }
    }

    render() {    
        const postItems = this.state.posts.map(item => <PostItem key={item.id} post={item} />)

        return (
            <div className="post-list">
                {postItems}
            </div>
        )
    }

}

const PostListQuery = () => {
    return (
        <Query query={GET_POSTS}>
            {({ loading, error, data }) => {
                if (loading) {
                    return (<div>Loading...</div>)
                }
                if (error) {
                    console.error(error)
                }

                return (<PostList posts={data.posts} />)
            }}
        </Query>
    )
}

export default PostListQuery

The solution was just to access the date directly and not using the state at all. See here:

class PostList extends Component {

    render() {
        // use posts directly here in render to make `cache.writeQuery` work. Don't set it via state
        const { posts } = this.props

        const postItems = posts.map(item => <PostItem key={item.id} post={item} />)

        return (
            <div className="post-list">
                {postItems}
            </div>
        )
    }

}

Just for completeness here is the input I used to add a new post and update the cache:

import React, { useState, useRef } from 'react'
import gql from 'graphql-tag'
import { Mutation } from 'react-apollo'
import { GET_POSTS } from './PostList'

const ADD_POST = gql`
mutation ($post: String!) {
  insert_posts(objects:{title: $post}) {
    affected_rows 
    returning {
      id 
      title
    }
  }
}
`

const PostInput = () => {
  const input = useRef(null)

  const [postInput, setPostInput] = useState('')

  const updateCache = (cache, {data}) => {
    // Fetch the posts from the cache 
    const existingPosts = cache.readQuery({
      query: GET_POSTS
    })

    // Add the new post to the cache 
    const newPost = data.insert_posts.returning[0]

    // Use writeQuery to update the cache and update ui
    cache.writeQuery({
      query: GET_POSTS,
      data: {
        posts: [
          newPost, ...existingPosts.posts
        ]
      }
    })

  }

  const resetInput = () => {
    setPostInput('')
    input.current.focus()
  }

  return (
    <Mutation mutation={ADD_POST} update={updateCache} onCompleted={resetInput}>
      {(addPost, { loading, data }) => {
        return (
          <form onSubmit={(e) => {
            e.preventDefault()
            addPost({variables: { post: postInput }})
          }}>
            <input 
              value={postInput}
              placeholder="Enter a new post"              
              disabled={loading}
              ref={input}
              onChange={e => (setPostInput(e.target.value))}              
            />
          </form>
        )
      }}
    </Mutation>
  )
}

export default PostInput
发布评论

评论列表(0)

  1. 暂无评论