Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fetchMore with variables doesn't update field policy read args #7496

Closed
jmmendivil opened this issue Dec 18, 2020 · 6 comments
Closed

fetchMore with variables doesn't update field policy read args #7496

jmmendivil opened this issue Dec 18, 2020 · 6 comments

Comments

@jmmendivil
Copy link

Hi guys.
I'm trying to get used to using typePolicies, it's awesome to have the logic in a single point instead of every updateQuery, but when I do a fetchMore() it seems like the args from the read() aren't updated like the merge() and I need them to do a Paginated view as in Paginated read functions.

Intended outcome:
Get the same args in read() as in merge() on every update with fetchMore() .

Actual outcome:
args in read() doesn't update with fetchMore().

How to reproduce the issue:

TypePolicy:

    {
      ProductsType: {
        fields: {
          getPackProducts: {
            keyArgs: false,
            read(exists, { args }) {
              // args.skip and args.first are the same
              // as the original query, always. Not ok
              return exists && exists.slice(args.skip, args.first + args.skip);
            },
            merge(existing, incoming, { args }) {
              // args.skip and args.first are the original values (first)
              // and updates (skip) on each fetchMore. OK
              const merged = existing ? existing.slice(0) : [];
              const start = args ? args.skip : merged.length;
              const end = start + incoming.length;
              for (let i = start; i < end; ++i) {
                merged[i] = incoming[i - start];
              }
              return merged;
            }
          }
        }
      }
    }

Query:

const GET_PACKS = gql`
  query($first: Int, $skip: Int) {
    myProducts {
      getPackProducts(first: $first, skip: $skip) {
        id
      }
    }
  }
`;

useQuery hook:

const { loading, error, data, fetchMore } = useQuery(
  GET_PACKS,
  {
    variables: {
      skip: 0,
      first: 10
    }
  }
);

fetchMore

<button onClick={() => {
  fetchMore({
    variables: {
      skip: 20
    }
  })
}}>Next</button>

Versions

System:
  OS: macOS 10.15.7
Binaries:
  Node: 10.15.1 - ~/.nvm/versions/node/v10.15.1/bin/node
  Yarn: 1.22.10 - /usr/local/bin/yarn
  npm: 6.14.9 - ~/.nvm/versions/node/v10.15.1/bin/npm
Browsers:
  Chrome: 87.0.4280.88
  Firefox: 84.0
  Safari: 14.0
npmPackages:
  @apollo/client: ^3.3.6 => 3.3.6
@benjamn
Copy link
Member

benjamn commented Dec 21, 2020

@jmmendivil The fetchMore query only writes new data to the cache. If you want the variables of the original useQuery(GET_PACKS, ...) query to be updated, you have to update them yourself, as explained in the setLimit example in the pagination docs (scroll down a bit until you see setLimit used). Please note that the variables you pass to fetchMore are not always the variables you would want for the original query, which is why it does not make sense to use the fetchMore variables to update the original query automatically.

@stevenmusumeche
Copy link

This is really surprising behavior. Why wouldn't you want the updated query variables in your read function?

@sergiidiak
Copy link

Given above, you don't really need fetchMore then. But..
read examples in pagination docs might be confusing for newcomers who follow the given examples. For me, I couldn't understand why Apollo doesn't make any requests when I update query variables (as suggested above) only (without fetchMore).
Turned out you also need to check if existing.slice(...) has some data in it, because if there is no data then it means you haven't loaded this page yet so you need to return undefined to trigger network request. It's only a problem for cache-first network policy.

@hwillson
Copy link
Member

It doesn't sound like there is an outstanding issue here, so closing. Thanks!

@FezVrasta
Copy link

How should I go about implementing a cursor based pagination where I can also preserve the index of the items like in this example (https://www.apollographql.com/docs/react/pagination/cursor-based/#using-list-element-ids-as-cursors) if the fetchMore call doesn't provide the cursor used to fetch the additional items? I can't update the cursor on the useQuery variables, it doesn't make any sense to me.

@rossm6
Copy link

rossm6 commented Oct 9, 2021

Ah...

I've been using apollo for months and can't believe I've only just discovered this.

In fact two recent discoveries which struck me as odd together now make sense.

The first discovery was the following query will execute again when the variables passed to the hook change. Hitherto I'd always assumed that fetchMore was the only way of getting further data for the original query set up in the hook. In fact I questioned whether this was intended behaviour here because I couldn't understand why this would be - #8847

function SomeComponent () {
    const { data, loading, fetchMore } = useQuery(GET_YOUR_LISTINGS, {
        fetchPolicy: 'network-only',
        nextFetchPolicy: 'cache-first',
        variables: {
            filters: {
                ...args.filters
            },
            orderBy: getOrderingForServer(args.orderBy),
            first: PAGE_SIZE,
            after: ''
        }
    });
}

Today I stumbled upon this issue because I too noticed that args passed to read did not reflect variables passed to fetchMore.

So my first discovery is the solution for ensuring the correct args are passed to read.

Together I now have something like this -

function SomeComponent () {
    const [args, setArgs] = useState();
    const [fetchInitial, { data, loading, fetchMore, called }] = useLazyQuery(GET_YOUR_LISTINGS, {
        fetchPolicy: 'network-only',
        nextFetchPolicy: 'cache-first',
        variables: {
            filters: {
                ...args.filters
            },
            orderBy: getOrderingForServer(args.orderBy),
            first: PAGE_SIZE,
            after: ''
        }
    });

   const fetchCount = useRef(0);
   if(called) fetchCount.current = 1;

    useEffect(() => {
       if(!args) return
       if(!fetchCount.current){
         fetchInitial();
       }
    }, [args, fetchInitial]);

    // fetchMore is then passed to a child infinite scroll component

    return (
     null
    )
}

I'm sure last time I read the docs for the Query section where fetchMore is first introduced this is not discussed. I'd strongly recommend changing the docs so that users new to apollo understand from the beginning that fetchMore really means fetchMore of the original query. I'm sure many like me just assumed it was the ONLY WAY of getting anything more from the original query.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Feb 15, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants