nanostores/query

Feat: allow any atoms to be passed like keys

sintell opened this issue · 3 comments

Hi, I've been using your library for a while now, and it's doing great, except for a one use case:

I have the following store:

export const $getCurrentUser = createFetcherStore<{ me: UserDTO }, ApiError>(["/users/me", $authStore]);

And for my use case, I expect $getCurrentUser to be run on any $authStore change, but I actually don't want any part of $authStore to be included in the actual URL.
I got it, that you only allow strings or string atoms to simplify the caching mechanism, but it's really nice to have feature, and it's also a thing in the react-query

The other way to solve this, may be by allowing to specify additional dependencies through createFetcherStore second argument, and leave the caching mechanism as it is, but forcing the cache update on every dependency change

export const $getCurrentUser = createFetcherStore<{ me: UserDTO }, ApiError>(["/users/me"], {dependsOn: [$authStore]});

It'll be great to hear your thoughts, maybe you got another take onto this, and if you wish I'll be glad to implement any of the solutions myself and send you a PR

dkzlv commented

Thanks for your suggestions!

I'm gonna split the ideas you shared into two, if you don't mind.

Non-string keys

Generally, I like this idea. I did not add this initially, because it can actually be done rather easily in the userland like this:

declare const $user: WritableAtom<{ ... }>;

const $userSerialized = computed($user, user => JSON.stringify(user));

I could do the same in the lib itself, but I don't like the fact that it introduces opinions to serialization process. JSON.stringify will generally be bad for performance and can result in too much queries (e.g., if you somehow mutate data locally). Typically, if you evaluate your cases of non-string keys, the complete object serialization is rarely the thing you actually want. Much more often you actually do have some predictable heuristic on when the query must reevaluate.

Non-key-based dependencies

That's an interesting one. I get the idea of wanting to create reactive chains that do not actually add anything up to the keys. I think, that's something I may need to add to the lib, because it seems a very reasonable requirement. I'm still not sure about non-string based dependencies (the reasoning is the same).

But while it's not there, here's 2 ideas how you can do it right now with not-that-much-hassle.

First, you can do this by redefining the fetcher function on a store-basis. Not DRY, I understand, but that's something, right? 😄

export const $getCurrentUser = createFetcherStore<{ me: UserDTO }, ApiError>(
  ["/users/me", $authStore],
  { fetcher: () => fetch("/users/me") },
);

Personally I don't like this solution. What I do like is to understand what this "additional" dependency actually does. In reality it just tells us that the said key must be invalidated! When you use ["/users/me", $authStore] as your combined key, it actually results in a pile of old cache values that will never be reused! We will essentially store "/users/me${authStoreValue}" in the cache, even though it's not needed. So a better way would be to invalidate cache manually.

It's not covered in the documentation, but there are 2 other useful things we have:

  1. every fetcher store has a .key property that holds the current key. That's particularly useful for complex keys, but also reduces repetition for simple strings, like /users/me
  2. the root nanoquery function returns third item 😱 It also isn't documented yet, but it's stable, tested and you can use it:
const [fetcher, mutator, { invalidateKeys, mutateCache }] = nanoquery()

You can easily guess what those functions do. We're interested in invalidateKeys function here:

export const $getCurrentUser = createFetcherStore<{ me: UserDTO }, ApiError>("/users/me");
onSet($authStore, () => invalidateKeys($getCurrentUser.key))

It basically invalidates the $getCurrentUser store each time $authStore changes. If you are subscribed to $getCurrentUser currently, it will refetch immediately; if not, it will refetch next time you subscribe to it.

dkzlv commented

@sintell Updated the lib to contain a few helpers and, finally, a documentation for the third returned item of the context. Also, dropped in a few words about your specific case.

Feel free to reopen if you have any further ideas.

Thanks a lot, I'll take a look into this on holidays