This error occurs when React Query's useQuery hook is called outside the body of a function component or custom hook, violating React's Rules of Hooks. The hook must be called at the top level of a functional component, not inside callbacks, loops, or conditionals.
This error indicates that you're attempting to use the useQuery hook from React Query (or similar query hooks from Apollo Client) in a location where React hooks are not allowed. React enforces strict rules about where hooks can be called - they must be invoked at the top level of function components or custom hooks, never inside regular functions, class components, event handlers, or effect callbacks. React hooks rely on a consistent calling order to maintain state between renders. When you call useQuery outside a component, React cannot track the hook's internal state or lifecycle, causing the hook system to fail. This is a fundamental restriction of React's hooks architecture, not specific to React Query. The error typically manifests as "Invalid hook call" or messages stating that hooks can only be called inside the body of a function component.
Ensure your function name starts with an uppercase letter and is used as a component:
// ✗ Wrong - lowercase function name
function myComponent() {
const { data } = useQuery({ queryKey: ['data'], queryFn: fetchData });
return <div>{data}</div>;
}
// ✓ Correct - uppercase component name
function MyComponent() {
const { data } = useQuery({ queryKey: ['data'], queryFn: fetchData });
return <div>{data}</div>;
}React treats functions starting with uppercase letters as components, which allows hooks to work properly.
Call useQuery at the top level of your function component, not inside callbacks or effects:
// ✗ Wrong - useQuery inside useEffect
function MyComponent() {
const [data, setData] = useState(null);
useEffect(() => {
const { data } = useQuery({ queryKey: ['data'], queryFn: fetchData });
setData(data);
}, []);
return <div>{data}</div>;
}
// ✓ Correct - useQuery at top level
function MyComponent() {
const { data } = useQuery({
queryKey: ['data'],
queryFn: fetchData
});
return <div>{data}</div>;
}React Query automatically handles refetching and caching - you don't need useEffect.
If you need reusable query logic, create a custom hook (function starting with "use"):
// Custom hook - can use other hooks
function useProductData(productId) {
return useQuery({
queryKey: ['product', productId],
queryFn: () => fetchProduct(productId),
enabled: !!productId // Only run when productId exists
});
}
// Use in component
function ProductDetails({ productId }) {
const { data, isLoading, error } = useProductData(productId);
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return <div>{data.name}</div>;
}Custom hooks can call other hooks and follow the same Rules of Hooks.
If you need to fetch data imperatively (in event handlers or effects), use the queryClient methods instead of useQuery:
import { useQueryClient } from '@tanstack/react-query';
function MyComponent() {
const queryClient = useQueryClient();
const handleClick = async () => {
// Use fetchQuery for imperative fetching
const data = await queryClient.fetchQuery({
queryKey: ['data'],
queryFn: fetchData
});
console.log(data);
};
return <button onClick={handleClick}>Fetch Data</button>;
}Alternatively, use the refetch function from useQuery:
function MyComponent() {
const { data, refetch } = useQuery({
queryKey: ['data'],
queryFn: fetchData,
enabled: false // Don't fetch on mount
});
const handleClick = () => {
refetch(); // Trigger fetch imperatively
};
return <button onClick={handleClick}>Fetch Data</button>;
}Wrap your application with QueryClientProvider at the root level:
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
const queryClient = new QueryClient();
function App() {
return (
<QueryClientProvider client={queryClient}>
<YourComponents />
</QueryClientProvider>
);
}
export default App;For Next.js App Router, create a client component provider:
// app/providers.tsx
'use client';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { useState } from 'react';
export function Providers({ children }) {
const [queryClient] = useState(() => new QueryClient());
return (
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
);
}Verify you don't have multiple React versions causing hook conflicts:
npm ls react
# or
yarn why reactIf you see multiple versions, resolve them:
# Remove node_modules and lock files
rm -rf node_modules package-lock.json
# or
rm -rf node_modules yarn.lock
# Reinstall dependencies
npm install
# or
yarn installFor monorepos or linked packages, use resolutions in package.json:
{
"resolutions": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
}
}Understanding the Rules of Hooks:
React's Rules of Hooks are enforced by the hook dispatcher, which tracks the order of hook calls to maintain consistent state between renders. Each hook call is assigned an index based on the order it's called, and React uses this index to retrieve the correct state on subsequent renders. This is why hooks must always be called in the same order.
Query Enabling Patterns:
When you need conditional fetching based on user interaction or data availability, use the enabled option instead of conditional hook calls:
// ✗ Wrong - conditional hook call
if (shouldFetch) {
const { data } = useQuery({ queryKey: ['data'], queryFn: fetchData });
}
// ✓ Correct - hook always called, conditionally enabled
const { data } = useQuery({
queryKey: ['data'],
queryFn: fetchData,
enabled: shouldFetch
});Dynamic Query Keys:
For queries that depend on props or state, include dependencies in the query key. React Query automatically refetches when the key changes:
function UserProfile({ userId }) {
const { data } = useQuery({
queryKey: ['user', userId], // Changes when userId changes
queryFn: () => fetchUser(userId),
enabled: !!userId // Only fetch when userId exists
});
}ESLint Configuration:
Install eslint-plugin-react-hooks to catch Rules of Hooks violations during development:
npm install eslint-plugin-react-hooks --save-devConfigure in .eslintrc:
{
"plugins": ["react-hooks"],
"rules": {
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn"
}
}Apollo Client vs TanStack Query:
The same Rules of Hooks apply to Apollo Client's useQuery and other GraphQL hooks. The error messages and solutions are identical - hooks must be called at the top level of function components or custom hooks.
Prop spreading could cause security issues
Prop spreading could cause security issues
Error: error:0308010C:digital envelope routines::unsupported
Error: error:0308010C:digital envelope routines::unsupported
React Hook "useEffect" is called conditionally. React Hooks must be called in the exact same order in every component render.
React Hook useEffect placed inside a condition
Hook can only be called inside the body of a function component
Hook can only be called inside the body of a function component
Rollup failed to resolve import during build
How to fix "Rollup failed to resolve import" in React