T
TanStack10mo ago
adverse-sapphire

Input Focus Lost After Mutation with Optimistic Updates

Problem Description I'm experiencing an input focus issue with React Query mutations. Here's the exact behavior: 1. User types in Input A and triggers a mutation (e.g., updating a module name) 2. User immediately tries to focus Input B 3. Input B gets focused momentarily 4. Input B immediately loses focus 5. User needs to click Input B again to regain focus The issue occurs with both optimistic updates and regular mutations with invalidateQueries. Here's the interesting part: - When I comment out the optimistic update code, but keep the API request, everything works fine - When I add back optimistic updates or use invalidateQueries, the focus issue returns Relevant Code Here's a simplified version of my mutation:
export const useUpdateModule = (projectId: number) => {
const queryClient = useQueryClient();
const queryKey = [PROJECT_ESTIMATE_QUERY_KEY, { projectId }];

return useMutation({
mutationFn: ({ moduleId, data }: UpdateModuleParams) =>
api.estimates.updateModule(moduleId, data),
onMutate: async ({ moduleId, data }) => {
await queryClient.cancelQueries({ queryKey });
const previousData = queryClient.getQueryData<ProjectEstimateApiResponse>(queryKey);

queryClient.setQueryData<ProjectEstimateApiResponse>(queryKey, (old) => {
if (!old) return old;
return produce(old, (draft) => {
const module = draft.data.modules.find((m) => m.id === moduleId);
if (module) {
module.name = data.name;
module.is_exportable = data.is_exportable;
}
});
});

return { previousData };
},
onError: (_, __, context) => {
queryClient.setQueryData(queryKey, context?.previousData);
}
});
};
export const useUpdateModule = (projectId: number) => {
const queryClient = useQueryClient();
const queryKey = [PROJECT_ESTIMATE_QUERY_KEY, { projectId }];

return useMutation({
mutationFn: ({ moduleId, data }: UpdateModuleParams) =>
api.estimates.updateModule(moduleId, data),
onMutate: async ({ moduleId, data }) => {
await queryClient.cancelQueries({ queryKey });
const previousData = queryClient.getQueryData<ProjectEstimateApiResponse>(queryKey);

queryClient.setQueryData<ProjectEstimateApiResponse>(queryKey, (old) => {
if (!old) return old;
return produce(old, (draft) => {
const module = draft.data.modules.find((m) => m.id === moduleId);
if (module) {
module.name = data.name;
module.is_exportable = data.is_exportable;
}
});
});

return { previousData };
},
onError: (_, __, context) => {
queryClient.setQueryData(queryKey, context?.previousData);
}
});
};
What I've Tried Using Immer for immutable updates Memoizing components Adding stable keys to components Removing optimistic updates (this works but isn't the desired solution) and still doesn't work, i think this could be rerenders problem also
1 Reply
variable-lime
variable-lime10mo ago
you need to show a minimal reproduction please; there must be something in combination with how the fields are rendered

Did you find this page helpful?