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:

  1. Initialize the query with enabled: false to prevent automatic execution during component mount.
  2. Destructure refetch from the hook return value alongside loading and error states.
  3. Attach refetch to a button's onClick handler to manually trigger the request.
  4. Optionally reset cache using queryClient.removeQueries or invalidateQueries before 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:

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: false to create a React Query that starts in an idle state and skips the initial fetch.
  • Call refetch() inside a button's onClick handler to manually trigger the network request.
  • Leverage queryClient.removeQueries when you need to clear stale data before fetching again.
  • Maintain hook rules by keeping useQuery at 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:

Share the following with your agent to get started:
curl -s https://instagit.com/install.md

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client