简体   繁体   English

Apollo 客户端写入查询未更新 UI

[英]Apollo Client Write Query Not Updating UI

We are building an offline first React Native Application with Apollo Client.我们正在使用 Apollo Client 构建一个离线的第一个 React Native 应用程序。 Currently I am trying to update the Apollo Cache directly when offline to update the UI optimistically.目前我正在尝试在离线时直接更新 Apollo Cache 以乐观地更新 UI。 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.由于我们离线,我们不会尝试在连接为“在线”之前触发突变,但希望 UI 在仍处于离线状态时触发突变之前反映这些更改。 We are using the readQuery / writeQuery API functions from http://dev.apollodata.com/core/read-and-write.html#writequery-and-writefragment .我们正在使用来自http://dev.apollodata.com/core/read-and-write.html#writequery-and-writefragment的 readQuery / writeQuery API 函数。 and are able to view the cache being updated via Reacotron, however, the UI does not update with the result of this cache update.并且能够通过 Reacotron 查看正在更新的缓存,但是,UI 不会根据此缓存更新的结果进行更新。

    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 });

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. Apollo 不太可能支持改变这个对象,因为如果不进行深度复制和之前/之后的数据比较,它检测你修改了哪些属性不是很有效。

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您可以使用例如一set lodash/fp来帮助您

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它推荐ImmerJS进行更复杂的突变

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM