-
-
Notifications
You must be signed in to change notification settings - Fork 5.3k
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
Add opt-in client-side caching layer to save on network requests #4386
Conversation
As there is a |
The removes the need for a refresh() once the optimistic mode finishes, and allows better usage of the cache even in optimistic mode
Fixed the problem with the forced refresh after an undoable call, reverts #4058 |
This changes the way the list is displayed when updating params (sort, pargination, filter): the list empties first, to show that the request is loading, then displayes the updated results.
Now we can share cache between getList and getMatching!
Now we cache the list of ids per request, that means that we can use the cache (and optimistic rendering) when going back to a list (paginatinon, sort, and filters included) that was seen before. |
The requests made by the We need to migrate the |
switch (type) { | ||
case 'getList': | ||
return ( | ||
resourceState && |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could use lodash/get here:
get(resourceState, ['list', 'validity', JSON.stringify(payload as GetListParams)]);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Due to prettier, the code isn't super shorter, and due to lodash, it isn't more readable:
// with lodash
return (
get(resourceState, [
'list',
'validity',
JSON.stringify(payload as GetListParams),
]) > now
);
// without lodash
return (
resourceState &&
resourceState.list &&
resourceState.list.validity &&
resourceState.list.validity[
JSON.stringify(payload as GetListParams)
] > now
);
In this case, I prefer a bit more verbosity with a bit less indirection.
case 'getList': { | ||
const data = resourceState.data; | ||
const requestSignature = JSON.stringify(payload); | ||
const ids = resourceState.list.idsForQuery[requestSignature]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Strange, are we sure resourceState.list.idsForQuery is always set ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes: if the query key was set in validity
, it means it was done in idsForQuery
, too.
|
||
const initialState = {}; | ||
|
||
const validityReducer: Reducer<ValidityRegistry> = ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this would be better grouped with idsForQuery and totalForQuery to avoid having the same serialized query repeated three times though the state.
oldValidityRegistry: ValidityRegistry | ||
): ValidityRegistry => { | ||
const validityRegistry = { ...oldValidityRegistry }; | ||
ids.forEach(id => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return ids.reduce((validityRegistry, id) => ({
...validityRegistry,
[id]: validUntil
}), oldValidityRegistry);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reduce version isn't shorter, and clones the registry once for every id. The original version clones the registry only once. I think the reduce version is slower and takes more memory. As it doesn't have the benefit of brevity, let's keep some imperative programming here.
// without reduce
const addIds = (
ids: Identifier[] = [],
validUntil: Date,
oldValidityRegistry: ValidityRegistry
): ValidityRegistry =>
{
const validityRegistry = { ...oldValidityRegistry };
ids.forEach(id => {
validityRegistry[id] = validUntil;
});
return validityRegistry;
};
// with reduce
const addIds = (
ids: Identifier[] = [],
validUntil: Date,
oldValidityRegistry: ValidityRegistry
): ValidityRegistry =>
ids.reduce(
(validityRegistry, id) => ({
...validityRegistry,
[id]: validUntil,
}),
oldValidityRegistry
);
switching to RFR |
ids: ids || [], | ||
hideFilter: queryModifiers.hideFilter, | ||
ids: typeof ids === 'undefined' ? defaultIds : ids, | ||
loaded: loaded || defaultIds.length > 0, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not using defaultTotal here ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's wrong with defaultIds?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nothing wrong. It's just that defaultTotal > 0
seems lighter.
Co-Authored-By: JulienM <[email protected]>
What's the status on this? Is this being tracked and/or worked on? I hit a serious issue this week with our app using a new form that can load in with 20+ child line inputs (via ArrayInput/SimpleFormIterator). I implemented the new caching features but it's not a network issue -- it's a scripting issue and it looks like it's due to the old fetch saga. It really sinks the app and makes a borderline unusable user experience. I think the above issue might address what I'm dealing with. If I can find some free time, I plan to try to look into updating the useReferenceInputController and try my best. |
This problem is still present, not being worked on, and tracked by issue #4531 that I just created thanks to your comment. If you feel like giving it a try, we'd appreciate the help! |
look like requestSignature used in useGetList for cachedRequests is different than one sent in action in requestPayload
in requestSignature filter variable do not have navigation and sort data, but in requestPayload its there. |
@gomcodoctor I don't know what you're talking about. The request signatures computed in useGetList and in the cachedRequests reducer are the same for me. If you've found a bug, please open a new issue with a way to reproduce it. |
I will create a new issue.. I think it's bug |
@fancyaction |
See rationale in #4385
What started as a simple enhancement resulted into a pretty large refactoring and optimization.
useGetList
now stores data in thelist
reducer (no more incustomQueries
)useGetList
now has one cache for each set of parameter. This allows optimistic rendering (and cache) of lists when changing pagination, filters, and sortinguseListController
now usesuseGetList
useListController
usesresources.[resourceName].list.ids
whenresources.[resourceName].list.cachedRequest[requestSignature]
isn't defined (avoids showing an empty list when changing params)validUntil
key, the relevant cache in the Redux store is maked with this validity dateuseDataProvider
call has a valid cached response, the call todataProvider
is skipped altogetherThe current implementation is opt-in (and backwards compatible): the
dataProvider
responses must include avalidUntil
key to enable the cache.