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

feat: query cache provider #476

Merged
merged 6 commits into from May 15, 2020

Conversation

jackmellis
Copy link
Contributor

@jackmellis jackmellis commented May 10, 2020

the query cache now comes from a context, meaning users can easily override and control the cache being used inside their application

see #460

this is a basic use case of creating an isolated cache for testing

in component:

const MyComponent = () => {
  const queryCache = useQueryCache();

  const [ mutate ] = useMutation(someFn, {
    onSettled() {
      queryCache.refetchQueries('basket');
    }
  });
  return ( ... );
};

in unit test:

import { makeQueryCache, QueryCacheProvider } from 'react-query';

const cache = makeQueryCache();
sinon.spy(cache, 'refetchQueries');


mount(
  <QueryCacheProvider queryCache={cache}>
    <MyComponent/>
  </QueryCacheProvider>
);

// ...

await invokeSomeMutation(); // after mutating, we should refetch the basket

expect(cache.refetchQueries).to.be.calledWith('basket');

@tannerlinsley
Copy link
Collaborator

Nice! I would love to see the tests converted to use this. Thoughts?

@jackmellis
Copy link
Contributor Author

Sure I can update them if you like

@tannerlinsley
Copy link
Collaborator

If it’s not too much trouble! 👍

@jackmellis
Copy link
Contributor Author

I've updated the tests. I sometimes get a warning about overlapping act() calls. I can't figure out why and I'm not sure if it was happening before or if I've uncovered something...

@@ -9,7 +9,7 @@ const onWindowFocus = () => {
const { refetchAllOnWindowFocus } = defaultConfigRef.current

if (isDocumentVisible() && isOnline()) {
queryCache
defaultQueryCacheRef.current
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm. Does this feel funny to you? What if a user had multiple query caches in their app? How would they hook up to this? Should multiple caches be allowed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes it definitely felt funny! But as you can set up the focus handler outside of any sort of react context, it's very hard to determine which cache to use.

I used the same pattern as the config where there is a defaultConfigRef - that the provider writes over. The focus handler actually uses the config ref as well so it's already a bit of a grey area...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this specific config value is "global", it kind of do make sense to use the latest one. For caches, maybe we should keep track of all active caches and update them all? I think that should make it possible to get rid of defaultQueryCacheRef?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've removed the defaultQueryCacheRef in favour of a list of active caches

@Ephem
Copy link
Collaborator

Ephem commented May 11, 2020

Nice work! This also relates to server rendering as per #461, if you want I can take a closer look and test it from that perspective tomorrow. 😄

@tannerlinsley
Copy link
Collaborator

@Ephem Awesome, since this is clearly cross-cutting, I'll let you test before proceeding.

Also, just to confirm, this will all still be backwards compatible right? With the main goals being:

  • Truly isolated tests
  • SSR isolation

Confirm?

Copy link
Collaborator

@Ephem Ephem left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First of all, very nice work on this, I like the approach you are taking!

Just so my view on this PR is clear I think the goal should be to support explicit caches for testing, using different caches for different parts of the application/separate React roots and laying the foundation to build improved SSR support on top, but not change SSR behaviour in this PR.

Tests

  • All internal tests are now updated to the new approach, which is great, but this also means the old approach is currently untested. Maybe add a few key tests targeting using the global cache, without a Provider?
  • Maybe add a few tests using multiple caches/providers?

Query cleanup

I think an existing major issue right now is cache cleanup. I think the issues #432 and #460 are both related to this and also the act warnings in this PR. I think this could and should be fixed in this PR since the problem might actually become worse otherwise.

EDIT: This could just as well be done in a separate PR before this is merged.

Let me expand:

I'm pretty sure these problems stem from query timeouts not being cleared.

cache.clear = () => {
  cache.queries = {}
  notifyGlobalListeners()
}

The old cache clear function does not in any way remove the pending query timeouts for staleness etc. To get rid of these problems, I think calling cache.clear() should also synchronously loop through all queries and remove all timeouts related to that query.

  • If a user creates a cache manually with makeQueryCache, they own the cache and are responsible for calling cache.clear().
  • If react-query creates a cache, the library owns that cache and needs to clean it up if it is no longer used (for example, in ReactQueryCacheProvider).

Serverside rendering

This approach looks great from a SSR-perspective and this PR implements a key part of what is needed to improve that story. There is more work that is needed on top, but that should be done in a follow up and I see nothing that wouldn't be compatible!

}

export function ReactQueryCacheProvider({ queryCache, children }) {
defaultQueryCacheRef.current = queryCache || makeQueryCache()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If queryCache is not provided, this will create a new queryCache on every render. Maybe this is also something to add a test for to avoid regression later?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should only create it once. That looks like it will be created on every render (if the queryCache isn't passed)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've cleaned this logic up now so it tracks queryCache instances between renders

src/useBaseQuery.js Show resolved Hide resolved
@@ -9,7 +9,7 @@ const onWindowFocus = () => {
const { refetchAllOnWindowFocus } = defaultConfigRef.current

if (isDocumentVisible() && isOnline()) {
queryCache
defaultQueryCacheRef.current
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this specific config value is "global", it kind of do make sense to use the latest one. For caches, maybe we should keep track of all active caches and update them all? I think that should make it possible to get rid of defaultQueryCacheRef?

src/queryCache.js Show resolved Hide resolved
Jack Ellis added 3 commits May 14, 2020 21:07
the query cache now comes from a context, meaning users can easily override and control the cache being used inside their application
this commit does the follwing:
track all active query caches added via the provider, handles mounting/unmounting
if creating a cache, ensure we only do it once
when unmounting an internally-created cache, make sure to clear it first
setFocusHandler now loops through all of the currently-active caches
@jackmellis
Copy link
Contributor Author

First of all, very nice work on this, I like the approach you are taking!

Just so my view on this PR is clear I think the goal should be to support explicit caches for testing, using different caches for different parts of the application/separate React roots and laying the foundation to build improved SSR support on top, but not change SSR behaviour in this PR.

Tests

  • All internal tests are now updated to the new approach, which is great, but this also means the old approach is currently untested. Maybe add a few key tests targeting using the global cache, without a Provider?
  • Maybe add a few tests using multiple caches/providers?

Query cleanup

I think an existing major issue right now is cache cleanup. I think the issues #432 and #460 are both related to this and also the act warnings in this PR. I think this could and should be fixed in this PR since the problem might actually become worse otherwise.

EDIT: This could just as well be done in a separate PR before this is merged.

Let me expand:

I'm pretty sure these problems stem from query timeouts not being cleared.

cache.clear = () => {
  cache.queries = {}
  notifyGlobalListeners()
}

The old cache clear function does not in any way remove the pending query timeouts for staleness etc. To get rid of these problems, I think calling cache.clear() should also synchronously loop through all queries and remove all timeouts related to that query.

  • If a user creates a cache manually with makeQueryCache, they own the cache and are responsible for calling cache.clear().
  • If react-query creates a cache, the library owns that cache and needs to clean it up if it is no longer used (for example, in ReactQueryCacheProvider).

Serverside rendering

This approach looks great from a SSR-perspective and this PR implements a key part of what is needed to improve that story. There is more work that is needed on top, but that should be done in a follow up and I see nothing that wouldn't be compatible!

I've added tests to show the global/default cache still works and a few tests for different caching scenarios

The provider now tracks its queryCache value and if it's a self-created one, it cleans it up and removes it on unmount. If the cache is manually created, it's up to the creator to manage cleanup.

I haven't touched on the in-depth cache cleanup (cancelling timeouts etc.) as I don't want to lose the focus of this PR. Happy to take this on separately though if nobody is working on it...

@Ephem
Copy link
Collaborator

Ephem commented May 15, 2020

Changes and tests looks great to me, really great work!

I totally agree with addressing cache cleanup in a separate PR. Since it's possible that using a lot of Providers in tests could worsen the existing problems (more timeouts) I think it should probably be addressed before a release.

README.md Outdated Show resolved Hide resolved
Co-authored-by: Tanner Linsley <tannerlinsley@gmail.com>
@tannerlinsley tannerlinsley merged commit 51375fd into TanStack:master May 15, 2020
@tannerlinsley
Copy link
Collaborator

🎉 This PR is included in version 1.5.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants