How to Trigger a React Query `useQuery` Hook on a Button Click
Disable automatic execution with enabled: false and invoke the refetch function returned by useQuery inside your button's onClick handler to run the query only when the user clicks.
React Query's useQuery hook is designed to fetch data when a component mounts, but you can defer execution until a specific user interaction occurs. According to the facebook/react source code, hooks must be called unconditionally at the top level of components during render, which means useQuery must be declared upfront even when you intend to trigger it manually via a button click later.
The enabled: false and refetch Pattern
React Query provides a manual execution mode that respects React's hooks architecture. By setting the enabled option to false, you create the query in an idle state that skips the initial network request. The hook returns a refetch function—a stable callback that dispatches a new query action to the Query Client when invoked.
This approach works because useQuery returns stable values (data, isLoading, refetch) on every render, as implemented in the core hook system in packages/react/src/ReactHooks.js. The refetch function triggers the data-fetching side-effect outside the render cycle, similar to how useEffect schedules effects after paint.
Step-by-Step Implementation
Follow these steps to execute a React Query fetch on button click:
- Initialize the query with
enabled: falseto prevent automatic execution during component mount. - Destructure
refetchfrom the hook return value alongside loading and error states. - Attach
refetchto a button'sonClickhandler to manually trigger the request. - Optionally reset cache using
queryClient.removeQueriesorinvalidateQueriesbefore refetching if you need fresh data.
Code Examples
Basic Button-Triggered Query
This example creates a disabled query that only fetches when the user clicks the "Load Users" button:
import { useQuery } from '@tanstack/react-query';
import axios from 'axios';
function UsersButton() {
// Query is defined but disabled initially
const { data, error, isLoading, refetch } = useQuery(
['users'],
() => axios.get('/api/users').then(res => res.data),
{ enabled: false } // Prevents auto-run
);
return (
<div>
<button onClick={() => refetch()}>Load Users</button>
{isLoading && <p>Loading…</p>}
{error && <p>Error: {error.message}</p>}
{data && (
<ul>
{data.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
)}
</div>
);
}
Resetting Cache Before Refetch
To guarantee fresh data on every click, clear the previous cache entry before calling refetch:
import { useQuery, useQueryClient } from '@tanstack/react-query';
function RefreshableWeather({ city }) {
const queryClient = useQueryClient();
const { data, isFetching, refetch } = useQuery(
['weather', city],
() => fetch(`https://api.weather.com/${city}`).then(r => r.json()),
{
enabled: false,
keepPreviousData: true
}
);
const handleClick = async () => {
// Clear previous result explicitly
await queryClient.removeQueries(['weather', city]);
// Start fresh request
refetch();
};
return (
<>
<button onClick={handleClick} disabled={isFetching}>
{isFetching ? 'Updating…' : 'Refresh Weather'}
</button>
{data && <pre>{JSON.stringify(data, null, 2)}</pre>}
</>
);
}
Triggering Multiple Queries with One Button
You can control several idle queries simultaneously using a single button click:
import { useQuery } from '@tanstack/react-query';
function Dashboard() {
const users = useQuery(
['users'],
() => fetch('/api/users').then(r => r.json()),
{ enabled: false }
);
const posts = useQuery(
['posts'],
() => fetch('/api/posts').then(r => r.json()),
{ enabled: false }
);
const loadAll = () => {
users.refetch();
posts.refetch();
};
return (
<>
<button onClick={loadAll}>Load Dashboard</button>
{/* Render users & posts once available */}
</>
);
}
React Architecture Considerations
This pattern complies with React's hooks rules because useQuery is called unconditionally at the top level of the component during every render. The internal state machine in React Query handles the actual network request scheduling.
Key files in the facebook/react repository demonstrate why this works:
packages/react/src/ReactHooks.js: Exports the built-in hooks that establish the patternuseQueryfollows—returning stable objects that external libraries can depend on across renders.packages/react-reconciler/src/ReactFiberHooks.js: Contains the low-level implementation of hook state management, showing how stable callbacks likerefetchpersist across renders without violating hook rules.packages/react/src/ReactCurrentDispatcher.js: Manages the dispatcher that supplies hook implementations during rendering, enabling third-party hooks likeuseQueryto integrate with React's render phase.
By keeping the hook call at the top level while disabling automatic execution, you avoid the "conditional hook" error while still achieving on-demand data fetching.
Summary
- Use
enabled: falseto create a React Query that starts in an idle state and skips the initial fetch. - Call
refetch()inside a button'sonClickhandler to manually trigger the network request. - Leverage
queryClient.removeQuerieswhen you need to clear stale data before fetching again. - Maintain hook rules by keeping
useQueryat the top level of your component, regardless of when the fetch actually executes.
Frequently Asked Questions
Can I call useQuery inside the button click handler instead?
No. Hooks must be called at the top level of your component during every render, not inside event handlers or conditional blocks. This rule is enforced by React's reconciler in packages/react-reconciler/src/ReactFiberHooks.js. Attempting to call useQuery inside an onClick will throw a hooks violation error.
What's the difference between refetch and invalidateQueries?
refetch is a function returned by useQuery that re-executes the specific query function immediately, while invalidateQueries marks queries as stale so they refetch the next time they are accessed or observed. Use refetch for immediate manual execution and invalidateQueries when you want to trigger updates across multiple components or after mutations.
Does enabled: false prevent the query from caching data?
No. The query still maintains its cache entry and state machine; it simply remains in idle status until triggered. Once you call refetch, the data is cached normally according to your staleTime and cacheTime settings, allowing subsequent renders or refetches to access cached values.
How do I handle loading states when using manual refetch?
The isLoading and isFetching flags returned by useQuery work identically whether the query runs automatically or manually. When enabled: false, isLoading starts as false and transitions to true when refetch() is called, allowing you to show loading indicators during the manual fetch.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s https://instagit.com/install.md