React-apollo: fetchMore doesn't trigger re-rendering component after executed updateQuery

Created on 12 Sep 2019  路  5Comments  路  Source: apollographql/react-apollo

Intended outcome:
The component will be triggered to re-render with data is the result of ### updateQuery function

Actual outcome:
The component is only triggered if the network status has changed, before executing the updateQuery function. The data which is composed by updateQuery is not "passing" to the data field of the query result.
The app only render the latest query data if any other state in the component changed.

here are some main parts of this process:

const MyComponent = (props) => {
  const hotelQueryResult = useQuery(
    SEARCH_HOTEL_QUERY,
    {
      variables: <some-variables-here>,
      notifyOnNetworkStatusChange: true
    }
  );

  ...

  function functionUseToFetchMore() {
    const currentTotalFetched = (_get(hotelQueryResult.data, "trip.searchProducts.nodes") || []).length;
    if (!hotelQueryResult.loading && currentTotalFetched < totalCount) {
      const nextPage = Math.floor(currentTotalFetched / TOTAL_ITEM_PER_PAGE);
      hotelQueryResult.fetchMore({
        variables: {
          pageNumber: nextPage
        },
        updateQuery: (previousResult: any, { fetchMoreResult }: any) => {
          const currentNodes = _get(previousResult, "trip.searchProducts.nodes");
          const incomingNodes = _get(fetchMoreResult, "trip.searchProducts.nodes");
          return _set(previousResult, "trip.searchProducts.nodes", [...currentNodes, ...incomingNodes]);
        }
      });
    }
  }

  return <map-to-render-data>
}

Scenario:

  • First run:
    data length = 10, component re-rendered
  • Trigger fetch more
    component re-rendered after network request completed.
    after executing updateQuery, component is not triggered, hotelQueryResult.data is not updated.

Version
"@apollo/react-hooks": "^3.0.0",

Most helpful comment

Or, when you use fetchPolicy= no-cache 馃し

All 5 comments

This happens because you are modifying previousResult in your updateQuery function. You should never modify previousResult. Instead, create a new object with the data you need and return that.

In order to help avoid making errors like this, a new option called freezeResults was recently added to apollo inmemory cache. If you use this option, then results will be made immutable when your application is running in development mode.

A corresponding option was added to Apollo Client called assumeImmutableResults, which helps to improve performance. assumeImmutableResults should only be set to true if freezeResults is also true.

Thank @dylanwulf for helping me out.
Yea, the problem above is actually like as yours. Also figured out that thelodash.set is an deep-set, it actually modifies the previousResult. Instead, use fp/set, it's OK then.
One of the ways to do to fix the above problem is to modify and return the fetchMoreResult instead and it's working, due to it's a new object which is basically new reference which leads to the expected behavior.
Anyway, thank you so much. This is first time I've created an issue, forgive me if any.

Or, when you use fetchPolicy= no-cache 馃し

Or, when you use fetchPolicy= no-cache 馃し

Thank you! Had this set and couldn't work out why I wasn't receiving a new update.

I read that this bug was fixed in apollo-client 3 (I did not try it)

If it is need to use fetchPolicy: no-cache you can create new state variable and collect items in it as workaround for older versions of apollo-client:

const [list, setList] = useState([])
const [nextToken, setNextToken] = useState(undefined)

const { data: { list } = {}, fetchMore } = useQuery(gql(GET_LIST), {
    fetchPolicy: "no-cache",
    skip: !!list,
    onCompleted: (data) => {
      setNextToken(data.list.nextToken)
      setUsersList(data.list.items)
    },
  })

const loadMore = async () => {
    if (!nextToken) {
      return null
    }
    return await fetchMore({
      updateQuery: (previousResult, { fetchMoreResult, variables }) => {
        setNextToken(fetchMoreResult.list.nextToken)
        setUsersList([...usersList, ...fetchMoreResult.list.items])
        return null
      },
    })
  }
Was this page helpful?
0 / 5 - 0 ratings