I’m using react-query
in my React app to fetch and mutate data. When I click a button to mutate the data, I need the button to be disabled and spinner to appear until the new mutated data is returned.
To achieve this, I’m using the isLoading
and isMutating
flags provided by useMutation
and useQuery
hooks.
const queryClient = useQueryClient();
const {
isLoading,
error,
data: todos,
} = useQuery<Todo[]>("todos", fetchTodos);
const { mutate, isLoading: isMutating } = useMutation(addTodo, {
onSuccess: () => {
queryClient.invalidateQueries("todos");
},
});
const handleAddTodo = () => {
if (!todos) return;
mutate({ id: todos.length + 1, title: "New Todo", completed: false });
};
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error fetching todos.</div>;
if (!todos) return <div>Unexpected missing data</div>;
return (
<>
<button disabled={isMutating} onClick={handleAddTodo}>
Add Todo
</button>
<ul>
{todos.map((todo) => (
<li key={todo.id}>
{todo.completed ? "☑" : "☐"} - {todo.title}
</li>
))}
</ul>
</>
);
When I click on the button to save data, for a brief moment, "Loading…" appears, then "loading" disappears, but old data are still displayed. Then again "loading" appears, and new data appear after that.
- clicked button
- a mutation request is sent
isMutating
is true, button is disabled- a mutation request is completed, cache is invalidated
isMutating
is false, button is enabled- a query request is sent,
isLoading
is still false, old data is displayed - a query request finishes, new data appears
How do I disable the button until the new data comes in?
2
Answers
TL;DR
isLoading
ofuseQuery
is useless for showing spinners, useisFetching
if you don't need background loading and need proper spinnersI should've read documentation on
useQuery
more carefully.The
isLoading
property of theuseQuery
hook only indicates that the query is loading if there is no cached data available. If there is cached data available, the hook will return that data immediately andisLoading
will be set to false. This results in old data being displayed briefly before the new data is fetched.What I need is
isFetching
property. It indicates whether the query is currently fetching new data, regardless of whether there is cached data available. So using isFetching solves the issue.Probably, I should use an overlay based on
isFetching
and the data shown based onisLoading
. The button should be disabled based onisMutating
andisFetching
.Also, watch a demo from
react-query
creator where he mentions that he doesn't like multiple spinners going everywhere and prefers optimistic background updates.react-query v4 (a new version published in organization) describes the need for two separate statuses,
status
andfetchStatus
: https://tanstack.com/query/v4/docs/react/guides/queries#why-two-different-statesThe main reason is ability to do loading in the background while displaying the cached (stale) version.
if you return the result from
invalidateQueries
(which is a Promise) from theonSuccess
callback, your mutation will stay inloading
state until that Promise has resolved as well:with that, you only need to disable the button while the mutation is in-flight (in loading state)