T
TanStack15mo ago
adverse-sapphire

optimistic update not updating dom

Im trying to optimistically update an item in an array but it doesnt trigger an update in the dom. My response of the query is something like
const data = {
data: [{ id: '1', name: 'test' }],
meta: { pages: 1, ... }
}
const data = {
data: [{ id: '1', name: 'test' }],
meta: { pages: 1, ... }
}
and is fetched via the api, lets simply for now
const query = useQuery({
queryKey: ['users'],
queryFn: () => return data
})
const query = useQuery({
queryKey: ['users'],
queryFn: () => return data
})
now i have an update function where i want to update that user optimistically
const toggleMutation = useMutation({
mutationFn: (uuid: string) => ...updateUser
onMutate: async (uuid) => {
const previousUsers = queryClient.getQueryData(['users'])

if (previousUsers) {
for (const user of previousUsers.data) {
if (user.uuid === uuid) {
user.name = 'test1'
}
}
queryClient.setQueryData(['users'], previousUsers)
}

// Return a context with the previous and new user
return { previousUser, previousUsers, queryKey }
}
})
const toggleMutation = useMutation({
mutationFn: (uuid: string) => ...updateUser
onMutate: async (uuid) => {
const previousUsers = queryClient.getQueryData(['users'])

if (previousUsers) {
for (const user of previousUsers.data) {
if (user.uuid === uuid) {
user.name = 'test1'
}
}
queryClient.setQueryData(['users'], previousUsers)
}

// Return a context with the previous and new user
return { previousUser, previousUsers, queryKey }
}
})
Doing this will update the data that i see in the devtools, but wont trigger an update in the dom, Updating a value in the root object will trigger an update
queryClient.setQueryData(['users'], { data: [], ...previousData })
queryClient.setQueryData(['users'], { data: [], ...previousData })
for example works
5 Replies
adverse-sapphire
adverse-sapphireOP15mo ago
it seems like the values arnt tracked deeply
queryClient.setQueryData(['users'], { data: [], ...previousData })
queryClient.setQueryData(['users'], previousUsers)
queryClient.setQueryData(['users'], { data: [], ...previousData })
queryClient.setQueryData(['users'], previousUsers)
resetting the value first and than set the expected value works, but this should not be the way right?
conscious-sapphire
conscious-sapphire15mo ago
You're correct: data in query isn't deeply tracked. You need to replace the whole object in order for Vue to detect this change and re-render. I believe data is a shallowRef under the hood.
conscious-sapphire
conscious-sapphire15mo ago
I personally use immer library to simplify that. Basically you treat your data as immutable and use immer to produce next immutable state. When optimistic update performance isn't a concern it looks clean. https://github.com/immerjs/immer
GitHub
GitHub - immerjs/immer: Create the next immutable state by mutating...
Create the next immutable state by mutating the current one - immerjs/immer
conscious-sapphire
conscious-sapphire15mo ago
With immer your update can look like this:
import { produce } from 'immer';

queryClient.setQueryData(['users'], current => {
if (!current) return;

return produce(current, state => {
const user = state.find(u => u.uuid === uuid);
if (!user) return;
user.name = 'test1';
});
});
import { produce } from 'immer';

queryClient.setQueryData(['users'], current => {
if (!current) return;

return produce(current, state => {
const user = state.find(u => u.uuid === uuid);
if (!user) return;
user.name = 'test1';
});
});
One gotcha when using immer in Vue: need to disable object auto freeze. Otherwise Vue will produce warnings telling it's unable to setup its own proxies.
// In your app's entry point
import { setAutoFreeze } from "immer";

// disable immer Auto freeze to avoid conflicts with proxies created by Vue itself
setAutoFreeze(false)
// In your app's entry point
import { setAutoFreeze } from "immer";

// disable immer Auto freeze to avoid conflicts with proxies created by Vue itself
setAutoFreeze(false)
adverse-sapphire
adverse-sapphireOP15mo ago
Thanks! got it working by doing this instead
queryClient.setQueryData(
queryKey,
(oldData) =>
oldData
? {
data: oldData.data.map((user) => ({ ...user, is_active: user.uuid === uuid ? !user.is_active : user.is_active })),
meta: oldData.meta
}
: oldData,
)
queryClient.setQueryData(
queryKey,
(oldData) =>
oldData
? {
data: oldData.data.map((user) => ({ ...user, is_active: user.uuid === uuid ? !user.is_active : user.is_active })),
meta: oldData.meta
}
: oldData,
)

Did you find this page helpful?