React Query + MobX — can I (should I?) mutate query cache data directly?
We recently introduced react query to a semi-old codebase that uses MobX observables for almost everything. In MobX, state updates are done in a mutable manner.
Queries call service methods which return MobX observables and as a result the query data across our app is mostly MobX observables. I was wondering if it would be bad practice to mutate the query cache directly (since MobX observables are supposed to be mutated directly for fine grained reactivity to work), instead of doing it immutably by supplying it with completely new objects — for example, a to-do item gets checked off and we mutate its observable Todo model directly inside the "todos" query's cached array, instead of setting the query's data with a new array containing the updated item.
I am also interested in hearing about your experiences working with this library combination, or anything similar.
Thanks!
6 Replies
correct-apricot•9mo ago
Why is mobx and react query so intertwined here? mobx has it's own react bindings.
This sounds wierd. Code reproduction would be highly appreidated.
correct-apricot•9mo ago
This might be helpful to read https://tanstack.com/query/latest/docs/framework/react/guides/does-this-replace-client-state
Basically you'd use query to sync with your server via fetch (you can do some other async stuff as well) and mobx for fast client only state
Does TanStack Query replace Redux, MobX or other global state manag...
Well, let's start with a few important items: TanStack Query is a server-state library, responsible for managing asynchronous operations between your server and client Redux, MobX, Zustand, etc. are c...
harsh-harlequinOP•9mo ago
Hey @DogPawHat , thank you for the reply! I am already very aware of the "Does Tanstack Query replace Reduc, Mobx etc.." post, and it indeed does make sense.
But this is a huge legacy app we're talking about.
How "server state" was handled initially (and still is in a LOT of places):
- MobX observable stores (for example "UserListStore") call BE service methods, handle loading state, refetching etc.
- BE service methods call the BE through fetch, convert the responses to MobX observable classes (for example "User") — therefore almost every non-trivial BE API call returned an MobX observable in one way or another ("User", a list of "User" etc.)
These MobX observables ("User", "Organization", etc) are the fundamental abstractions used everywhere across the app.
How we implemented React Query:
For now we decided to leave the service methods untouched and feed them directly into react query, which means:
- queryFn (in useQuery) call our service methods, which return MobX observables
- query data in the cache is therefore MobX observables
My question rephrased:
Since we are using MobX, and MobX observables are supposed to be mutated directly, would it make sense to do so as well when mutating the query cache (instead of invalidating, when we exactly know which properties need to change)? I know it is not advised according to https://tanstack.com/query/latest/docs/framework/react/guides/updates-from-mutation-responses#immutability, but in most projects query data is just plain objects, not observables.
Updates from Mutation Responses | TanStack Query React Docs
When dealing with mutations that update objects on the server, it's common for the new object to be automatically returned in the response of the mutation. Instead of refetching any queries for that i...
harsh-harlequinOP•9mo ago
Here's an example:
Notice the observable class and observed wrapped Page component.
Please know that this works. When updating user.name to "Anna" the component actually re-renders.
I was just wondering if there's anyone who's actually done something similar! And if it was a valid way of mutating the cache, since its MobX, and not plain objects.
correct-apricot•9mo ago
Per https://tanstack.com/query/latest/docs/framework/react/guides/updates-from-mutation-responses#immutability
Updates via setQueryData must be performed in an immutable way. DO NOT attempt to write directly to the cache by mutating data (that you retrieved from the cache) in place. It might work at first but can lead to subtle bugs along the way.I must strongly recommend that you do not further develop this strategy for a Mobx -> Tanstack Query refactor. I'm gonna point out a few issues: - Mobx observables are not JSON serailzable I believe and you will lose structural sharing - You are creating 2 observers here in the same component to the same data: one that's observing the cache via
useQuery and the observable itself via observe. https://tkdodo.eu/blog/inside-react-query explains this a bit more
- the 2 observers could get out of sync real easily, expecailly if you have multiple observers to the same query.
- In your example, queryFn is a non-async fn returning just the observable, when queryFn really needs to return a Promise. You could do
And it would have the same effect I think.
Can you explain a bit more why you are refactoring this logic to query and what you are trying to do here? I can imagine that moving to query from mobx would clean up a lot of boilerplate, but going by the example, your just adding overhead for no benifit and a high chance of introducing bugs.
I'd recommend decoupling the fetch api calls from the mobx classes and methods first before making a move to query.Inside React Query
Taking a look under the hood of React Query
Updates from Mutation Responses | TanStack Query React Docs
When dealing with mutations that update objects on the server, it's common for the new object to be automatically returned in the response of the mutation. Instead of refetching any queries for that i...
Render Optimizations | TanStack Query React Docs
React Query applies a couple of optimizations automatically to ensure that your components only re-render when they actually need to. This is done by the following means: structural sharing React Quer...
harsh-harlequinOP•9mo ago
Like I said, its a huge legacy code base, and we wanted to take advantage of react query for its amazing server state management.
Thanks for the reply again. I wont be mutating the cache directly!