-
Notifications
You must be signed in to change notification settings - Fork 2.7k
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
Query doesn't return empty entries from cached array (offset pagination) any more #6628
Comments
This is an intentional consequence of #6454, but you can opt out of this filtering behavior by replacing the empty items with Depending on what exactly you mean by "empty" (dangling references? empty objects? new InMemoryCache({
typePolicies: {
Person: {
fields: {
friends(existing, { args, canRead }) {
// ... logic to produce a slice of existing based on args...
// Using a null placeholder will ensure the slice retains its original length,
// effectively disabling the default filtering of unreadable items:
return slice.map(friend => canRead(friend) ? friend : null);
// By default, the cache behaves as if you returned the following array:
// return slice.filter(canRead);
},
},
},
},
}) |
Thanks for the quick response! const arr = [];
arr[1] = "Foo";
We used this to merge arbitrary pages in the cache. The type policy for reference: function makeOffsetPaginationPolicy(): TypePolicy {
return {
fields: {
rows: {
keyArgs: false,
merge(existing: any[], incoming: any[], params) {
const offset = params.variables.offset;
const merged = existing ? existing.slice(0) : [];
// Insert the incoming elements in the right places, according to args.
const end = offset + incoming.length;
for (let i = offset; i < end; ++i) {
merged[i] = incoming[i - offset];
}
return merged;
},
},
},
};
} |
I fixed this in our code by writing /**
* Create TypePolicy for offset/limit based pagination.
*/
function makeOffsetPaginationPolicy(): TypePolicy {
return {
fields: {
rows: {
keyArgs: false,
merge(existing: any[], incoming: any[], params) {
const offset = params.variables.offset;
const merged = existing ? existing.slice(0) : [];
// Insert the incoming elements in the right places, according to args.
const newEnd = offset + incoming.length;
const oldEnd = existing?.length ?? 0;
for (let i = Math.min(offset, oldEnd); i < newEnd; ++i) {
if (i >= offset) {
merged[i] = incoming[i - offset];
} else if (i >= oldEnd) {
// add placeholder
merged[i] = null;
}
}
return merged;
},
read(existing) {
return existing ?? [];
},
},
},
};
} |
The provided offsetLimitPagination policy no longer works properly as it creates sparse arrays that are now automatically filtered: |
Exact same problem here. I was relying on sparse arrays to work with offset based pagination and it was working great. I really don't want to pad the array with potentially thousands of nulls as a workaround. And as @transcranial points out above, based on the fact that the provided This issue is preventing me from moving past the RC and onto the official release of 3. Is there a sane way we can reintroduce support for sparse arrays for the purposes of offset pagination? |
Further notes...the fix as suggested by @benjamn will not work in the case of sparse arrays (including the officially provided @cbergmiller workaround does work, but does suffer from the padding problem I mentioned in my previous comment, so does still feel to be like a workaround rather than an acceptable solution. |
Let us know if this is still a concern with |
Hi @hwillson it is still a problem with latest. It would require an intentional update to reintroduce support for storing sparse arrays in Apollo cache. The current implementation uses a The downside to no longer supporting sparse arrays, as previously noted, is that it forces clients to pad their arrays with potentially millions of nulls before storing (imagine the case where only the first and thousandth page of data had been fetched, for example). |
Basically this currently blocks us from implementing server-side pagination. But without our UI just doesn't load anymore as requests are running into timeouts. Any other solutions besides filling everything with |
This may not help your specific case but i moved to cursor-based pagination. Less effort in the frontend and better perfoming DB queries (large offsets tended to be slow). |
@cbergmiller Also already thought about it but we have server-side sorting and I've heard that this is getting tricky with cursor-based pagination. |
@benjamn @hwillson I think this should be clarified in the docs, you still have to patch the slice.map(friend => canRead(friend) ? friend : null);
for (let i = 0; i < slice.length; ++i) {
slice[i] = canRead(slice[i]) ? slice[i] : null;
} |
@adamyonk thanks for the suggestion! Would you be up for suggesting a change to the docs via a Pull Request? And thanks to all in this thread for sharing your concerns with this approach. We won't be able to prioritize revisiting this aspect of the code in the near future but will keep this issue open for any documentation updates that might ship as a result 🙏🏻 |
I just ran into this issue again while attempting to reconfigure pagination in my app. Respectfully, the provided |
The following should work as expected for a limit-offset type of pagination. It leverages the fact that the cache preserves sparse arrays. import {offsetLimitPagination as offsetLimitPaginationOriginal} from "@apollo/client/utilities";
const offsetLimitPagination: typeof offsetLimitPaginationOriginal = (keyArgs = false) => {
return {
...offsetLimitPaginationOriginal(),
read(existing, {args, canRead}) {
// console.debug(`Reading from cache offset=${args?.offset} limit=${args?.limit} existing=`, existing);
if (!Array.isArray(existing) || !Number.isFinite(args?.limit)) return undefined; // pass-through non-paginated queries
const {offset = 0, limit} = args;
const slice = existing?.slice(offset, offset + limit).filter(Object); // Filter out empty values from the sparse array
return !slice.length ? undefined : slice;
}
}
}
cache: new InMemoryCache({
typePolicies: {
Query: {
fields: {
users: {
...offsetLimitPagination(),
},
}
}
}
}), React component: const [pageInfo, setPageInfo] = useState<{offset: number; limit: number;}>({offset: 0, limit: 10});
const {data: usersDTO} = useQuery(UsersPaginate, { variables: {
...pageInfo
}})
const loadPage = (page: number) => {
const offset = pageInfo.limit * (page-1);
setPageInfo({...pageInfo, offset}); // triggers refetch and field policy's read (see client.ts)
}
.... |
We noticed that our implementation of an offset/limit bases pagination broke. This used to work with earlier Apollo Client 3.0 versions (beta/rc) but doesn't work in 3.0.2.
Intended outcome:
Custom type policies should be able to merge pagination results .
useQuery
should return the array with the empty entries so that the component can find the data it needs to display a page based on an offset and limit.Actual outcome:
The cache shows an array with empty entries.
useQuery
returns the array with the empty items removed.Apollo Client 3.0.2
React 16.13.1
The text was updated successfully, but these errors were encountered: