I'm just playing around with react-query
with typescript
I mean I do my first attempts
Is it the right way?
const useCreateTodo = () => {
const queryClient = useQueryClient();
return useMutation(
(todo: TodoDto) => axios.post(`${URL}/todos`, todo).then((res) => res.data),
{
onMutate: async (newTodo: TodoDto) => {
// Cancel any outgoing refetches (so they don't overwrite our optimistic update)
await queryClient.cancelQueries("todos");
// Snapshot the previous value
const previousTodos = queryClient.getQueryData("todos");
// Optimistically update to the new value
queryClient.setQueryData<TodoDto[] | undefined>("todos", (old) =>
old ? [...old, newTodo] : old
);
// Return a context object with the snapshotted value
return { previousTodos };
},
// If the mutation fails, use the context returned from onMutate to roll back
onError: (
err,
newTodo,
context:
| {
previousTodos: unknown;
}
| undefined
) => {
queryClient.setQueryData(
"todos",
context ? context.previousTodos : context
);
},
// Always refetch after error or success:
onSettled: () => {
queryClient.invalidateQueries("todos");
},
}
);
};
optimistic updates are a bit tricky for type inference. There is now an example for this exact case in the docs.
From that example:
const addTodoMutation = useMutation(
newTodo => axios.post('/api/data', { text: newTodo }),
{
// When mutate is called:
onMutate: async (newTodo: string) => {
setText('')
// Cancel any outgoing refetches (so they don't overwrite our optimistic update)
await queryClient.cancelQueries('todos')
// Snapshot the previous value
const previousTodos = queryClient.getQueryData<Todos>('todos')
// Optimistically update to the new value
if (previousTodos) {
queryClient.setQueryData<Todos>('todos', {
...previousTodos,
items: [
...previousTodos.items,
{ id: Math.random().toString(), text: newTodo },
],
})
}
return { previousTodos }
},
// If the mutation fails, use the context returned from onMutate to roll back
onError: (err, variables, context) => {
if (context?.previousTodos) {
queryClient.setQueryData<Todos>('todos', context.previousTodos)
}
},
// Always refetch after error or success:
onSettled: () => {
queryClient.invalidateQueries('todos')
},
}
)
A couple of explanations:
onMutate
, so that type inference will work for the mutateFn
(newTodo
is inferred) as well as for the context in onError
.getQueryData
so that previousTodos
is typed. You don't need to union with undefined
though - react-query will do that for you.setQueryData
is tricky, because it requires you to return an Array, but old
can be undefined. I prefer to use the previousTodos
returned by getQueryData