redux-toolkit icon indicating copy to clipboard operation
redux-toolkit copied to clipboard

RTKQ Architecture ideas: research TanStack Query, other libs, coordination approaches

Open markerikson opened this issue 1 month ago • 4 comments

Jotting down some notes in the back of my head:

  • We should probably review TanStack Query to see if there's any additional useful ideas we can glean in terms of architecture, feature set, implementation approaches, etc.
    • Probably a bunch of other libraries too (SWR, RESTHooks even though it has no real downloads, etc?)
  • The original design was entirely around individual hooks, but we've seen lots of users want to do queries in more coordinated ways: sequential, combined, etc. In other words, the next layer up from "run this one query". Currently this devolves into "just call the hooks and let your component coordinate things", but that's not always ergonomic. I know this is vague, but worth thinking through a bit.
  • Similarly, we've grown a half-dozen different aspects of internal query coordination: hooks, thunks, polling, invalidation, lifecycles, delayed invalidations, etc. It's all been very ad-hoc and it feels like we're stretching the bounds of what we can do here. Worth thinking through the pieces and figuring out if there's any consolidation or better design.
  • Obligatory "bundle sizes are too big and all the thunks and logic are complicated, can we redesign the internals to be nicer somehow?"
  • would still like to do something around normalization, but maybe a TSDB adapter sufficiently addresses that?

and then I'm sure there's plenty of related ideas in #3692 .

markerikson avatar Nov 29 '25 19:11 markerikson

One of the things I like about SWR is the ergonomics around mutate.

In the same realm of the discussion in #5073 of helpers around manual cache (mainly optimistic & pessimistic) updates, I think that having easy access to a bound update from a query hook would make a lot of use cases of simpler.

How this works for SWR, example adapted from their docs:

  const { data, mutate } = useSWR('/api/user', fetcher)
  const updateName = async () => {
      const newName = data.name.toUpperCase()
      await requestUpdateUsername(newName)
      mutate({ ...data, name: newName })
   }

JacobJaffe avatar Dec 03 '25 22:12 JacobJaffe

Hmm. So essentially what we do for cache lifeycles?

      updateCachedData: (isAnyQueryDefinition(endpointDefinition)
        ? (updateRecipe: Recipe<any>) =>
            mwApi.dispatch(
              api.util.updateQueryData(
                endpointName as never,
                originalArgs as never,
                updateRecipe,
              ),
            )
        : undefined) as any,

Seems pretty doable to throw that into the query hooks.

markerikson avatar Dec 03 '25 22:12 markerikson

Yeah-- it would be pretty sweet to be able to do:

// Optimistic patch example, within-consumer

const { data: profile, patch: patchProfile } = useGetProfileQuery(userId);
const [updateProfileMutation] = useUpdateProfileMutation();

const updateName = async (name: string) => {
  const patch = patchProfile({ name });
  try {
    await updateProfileMutation(userId, { name });
  catch (error) {
    patch.undo();
  }
}

I think one of the reasons I'm a fan of this approach is that in applications with lots of optimistic / pessimistic updates, it becomes nice for it be super obvious how the updates are occurring from just reading the consumer.

Especially when a variety of strategies are used for updating, and they all start feeling like lots of side effects firing off.

JacobJaffe avatar Dec 03 '25 22:12 JacobJaffe

Sure, filed:

  • https://github.com/reduxjs/redux-toolkit/issues/5151

markerikson avatar Dec 03 '25 22:12 markerikson