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

useStableQueryArgs chokes on BigInt #4279

Closed
rkofman opened this issue Mar 19, 2024 · 8 comments · Fixed by #4315 · May be fixed by #4297
Closed

useStableQueryArgs chokes on BigInt #4279

rkofman opened this issue Mar 19, 2024 · 8 comments · Fixed by #4315 · May be fixed by #4297
Labels
bug Something isn't working rtk-query

Comments

@rkofman
Copy link
Contributor

rkofman commented Mar 19, 2024

When serializing a query object that includes a BigInt, rktQuery blows up with:

TypeError: Do not know how to serialize a BigInt

This seems to be due to this code in buildQueryHooks:

useStableQueryArgs(skip ? skipToken : arg, defaultSerializeQueryArgs, context.endpointDefinitions[name], name);

Which uses defaultSerializeQueryArgs instead of user-supplied overrides (i.e. serializeQueryArgs) -- where users could override the behavior and make bigints safe to serialize.

@markerikson markerikson added bug Something isn't working rtk-query labels Mar 19, 2024
@riqts
Copy link
Contributor

riqts commented Mar 20, 2024

Worth noting that currently the buildQueryHooks code intentionally overrides the endpoint definitions serializeQueryArgs so that refetching and cache lifecycles proceed as intended when a queryArg changes.
so the hook recognises a change and triggers a refetch even when the user provided key remains stable.

My thoughts on how to approach it:

  1. Add handling for BigInt inside defaultSerializeQueryArgs

    • This might be the obvious answer I am not sure if there's a clear reason why BigInt was not handled in the first place or not
    • Alternatively there might be an obvious reason why its not lol
  2. Add an option to bypass defaultSerializeQueryArgs

    • Kind of a risk as users may not understand they are taking on the responsibility of managing refetch and unique keys themselves
    • really simple to implement :D

@EskiMojo14
Copy link
Collaborator

EskiMojo14 commented Mar 20, 2024

This might be the obvious answer I am not sure if there's a clear reason why BigInt was not handled in the first place or not
Alternatively there might be an obvious reason why its not lol

because JSON.stringify doesn't handle it and we haven't added any special behaviour to it
image

the only thing defaultSerializeQueryArgs does differently with JSON.stringify is ensuring keys are in a predictable order, so { foo: 1, bar: 2 } serializes to the same string as { bar: 2, foo: 1 }

@rkofman
Copy link
Contributor Author

rkofman commented Mar 22, 2024

serializeQueryArgs is documented as:

Accepts a custom function if you have a need to change the creation of cache keys for any reason.

To me, it is surprising that changing how cache keys are created doesn't affect refetching and cache lifecycles. The example documenting the per-endpoint version reinforces this:

// Example: an endpoint with an API client passed in as an argument,
// but only the item ID should be used as the cache key

The API client isn't likely to change and as a user, I don't expect it to trigger new requests if it does.

If I understand correctly (and I very well might not), the reason to not just use the user-provided serializeQueryArgs is to allow e.g. infinite-loading paginated endpoints. The storage cache-key remains the same, but when the page param changes, the query still needs to fire and fetch new data (whose merging is then perhaps managed somewhat manually with merge).

In my mind, the ideal API would be to use the user-provided serializeQueryArgs override for both storage and lifecycle. Users would still be able to use forceRefetch to force fetching in cases where the serialized query args are not triggering a refetch (e.g. the page parameter changed).

Alternately, if two separate cache key implementations really are useful; it might be best to allow users to override either one. Conceptually, I would think of them as: serializeQueryArgsForFetching and serializeQueryArgsForStorage.

@riqts
Copy link
Contributor

riqts commented Mar 23, 2024

Worth noting that currently the buildQueryHooks code intentionally overrides the endpoint definitions serializeQueryArgs so that refetching and cache lifecycles proceed as intended when a queryArg changes.

My apologies, I may have misrepresented the intention of the change here. I believe the reason defaultSerializeQueryArgs is used instead of the user provided serializeQueryArgs is to ensure the hooks dependancy retriggers when the queryArgs change.

Regardless, I do agree mostly with what you have said, it does seem inherently at odds to allow a user supplied serializeQueryArgs and then run the queryArgs through the defaultSerializeQueryArgs. It does seem the reason it exists is to support infiniteQuery scenarios, as you suggested. #2816

I am exploring further whether I can make any reasonable trigger from the useStableQueryArgs using a stable cache key in this area:

export function useStableQueryArgs<T>(
  queryArgs: T,
  serialize: SerializeQueryArgs<any>,
  endpointDefinition: EndpointDefinition<any, any, any, any>,
  endpointName: string,
) {
  const incoming = useMemo(
    () => ({
      queryArgs,
      serialized:
        typeof queryArgs == 'object'
          ? serialize({ queryArgs, endpointDefinition, endpointName })
          : queryArgs,
    }),
    [queryArgs, serialize, endpointDefinition, endpointName],
  )
  const cache = useRef(incoming)
  useEffect(() => {
    if (cache.current.serialized !== incoming.serialized) {
      cache.current = incoming
    }
  }, [incoming])

  return cache.current.serialized === incoming.serialized
    ? cache.current.queryArgs
    : queryArgs
}

But I do think that potentially the alternative is to provide an additional option for the user to handle the refetching or arg changes

@rkofman
Copy link
Contributor Author

rkofman commented Mar 26, 2024

Since the serialization for StableQueryArgs doesn't need to be reversible, would it be useful to just implement a mapping of bigint values to some serialized form directly in StableQueryArgs (e.g. a string value directly, or an object with a __bigint__ key that maps to a string)? That way it just.. silently has the correct behavior w/o any user impact.

@rkofman
Copy link
Contributor Author

rkofman commented Apr 5, 2024

Anything I can do to facilitate a code review / merging of my PR above?

@phryneas
Copy link
Member

phryneas commented Apr 5, 2024

Unfortunately, right now we're just all pretty preoccupied - we'll get around to the PR eventually, but I'm sorry I can't guarantee a time frame on it.
That said, the PR contains a PR build in the CodeSandbox preview that you can add to your package.json in the meantime - I hope that makes it already usable for you.

@rkofman
Copy link
Contributor Author

rkofman commented Apr 7, 2024

I appreciate the pointer to the CodeSandbox preview. It's definitely a better workaround than I had landed on -- and something I totally overlooked as a possibility. Will follow-up on other comments in the PR!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working rtk-query
Projects
None yet
5 participants