How to fix useMutation only works inside a component in React
This error occurs when you try to call React Query's useMutation hook outside of a functional component or custom hook. Like all React hooks, useMutation must follow the Rules of Hooks and can only be called at the top level of components.
Quick fix
Fixes useMutation only works inside a component
// ❌ WRONG - calling in utility function
// utils/api.ts
export function createUser(data: UserData) {
const mutation = useMutation({ // Error: can't call hook here
mutationFn: (userData: UserData) => fetch('/api/users', {
method: 'POST',
body: JSON.stringify(userData)
})
});
return mutation.mutate(data);
}
// ✅ CORRECT - calling in component
// components/UserForm.tsx
import { useMutation } from '@tanstack/react-query';
function UserForm() {
const mutation = useMutation({
mutationFn: (userData: UserData) => fetch('/api/users', {
method: 'POST',
body: JSON.stringify(userData)
})
});
const handleSubmit = (data: UserData) => {
mutation.mutate(data);
};
return (
<form onSubmit={(e) => {
e.preventDefault();
handleSubmit(formData);
}}>
{/* form fields */}
</form>
);
}- Move useMutation call into a functional component
- Create a custom hook for reusable mutations
- Use regular fetch/axios if you truly need outside-component execution
- Fix conditional or nested useMutation calls
// ❌ WRONG - calling in utility function// utils/api.tsexport function createUser(data: UserData) {const mutation = useMutation({ // Error: can't call hook heremutationFn: (userData: UserData) => fetch('/api/users', {method: 'POST',body: JSON.stringify(userData)})});return mutation.mutate(data);}// ✅ CORRECT - calling in component// components/UserForm.tsximport { useMutation } from '@tanstack/react-query';function UserForm() {const mutation = useMutation({mutationFn: (userData: UserData) => fetch('/api/users', {method: 'POST',body: JSON.stringify(userData)})});const handleSubmit = (data: UserData) => {mutation.mutate(data);};return (<form onSubmit={(e) => {e.preventDefault();handleSubmit(formData);}}>{/* form fields */}</form>);}useMutation only works inside a component
What this error means
This error appears when you attempt to use React Query's `useMutation` hook in an invalid context—typically outside of a React functional component or custom hook. React Query's `useMutation` is a React hook, which means it must follow React's Rules of Hooks. The Rules of Hooks state that hooks can only be called: 1. At the top level of a functional component 2. At the top level of a custom hook 3. Never inside loops, conditions, nested functions, or regular JavaScript functions When you try to call `useMutation` in a utility function, event handler outside of a component, or a class component, React Query will throw this error because it cannot access the necessary React context and lifecycle management.
Typical symptoms
- Error message: "useMutation only works inside a component" or similar hook error
- Application crashes when attempting to call useMutation
- TypeScript errors about invalid hook usage context
- Mutations fail to execute when called from utility functions
- React DevTools shows hooks count mismatch warnings
Common causes
- Attempting to call useMutation directly in a utility function or service module
- Calling useMutation inside a regular JavaScript function instead of a component
- Trying to use useMutation in a class component
- Calling useMutation conditionally or inside a loop
- Attempting to instantiate useMutation in an event handler defined outside the component
- Calling useMutation in middleware or API route handlers
How to fix "useMutation only works inside a component"
The simplest fix is to call useMutation at the top level of a functional component:
// ❌ WRONG - calling in utility function
// utils/api.ts
export function createUser(data: UserData) {
const mutation = useMutation({ // Error: can't call hook here
mutationFn: (userData: UserData) => fetch('/api/users', {
method: 'POST',
body: JSON.stringify(userData)
})
});
return mutation.mutate(data);
}
// ✅ CORRECT - calling in component
// components/UserForm.tsx
import { useMutation } from '@tanstack/react-query';
function UserForm() {
const mutation = useMutation({
mutationFn: (userData: UserData) => fetch('/api/users', {
method: 'POST',
body: JSON.stringify(userData)
})
});
const handleSubmit = (data: UserData) => {
mutation.mutate(data);
};
return (
<form onSubmit={(e) => {
e.preventDefault();
handleSubmit(formData);
}}>
{/* form fields */}
</form>
);
}If you need to share mutation logic across multiple components, wrap useMutation in a custom hook:
// hooks/useCreateUser.ts
import { useMutation, useQueryClient } from '@tanstack/react-query';
interface UserData {
name: string;
email: string;
}
export function useCreateUser() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (userData: UserData) => {
const response = await fetch('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(userData)
});
if (!response.ok) {
throw new Error('Failed to create user');
}
return response.json();
},
onSuccess: () => {
// Invalidate and refetch queries after successful mutation
queryClient.invalidateQueries({ queryKey: ['users'] });
}
});
}
// Usage in any component
function UserForm() {
const createUser = useCreateUser();
const handleSubmit = (data: UserData) => {
createUser.mutate(data, {
onSuccess: () => {
console.log('User created!');
}
});
};
return (/* form JSX */);
}If you genuinely need to execute a mutation outside of React's component tree (e.g., in a service worker, middleware, or Node.js script), you should use regular fetch or axios instead:
// utils/api.ts
// For non-React contexts, don't use React Query
export async function createUser(data: UserData) {
const response = await fetch('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
if (!response.ok) {
throw new Error('Failed to create user');
}
return response.json();
}
// Then in your component, you can wrap this if needed:
function UserForm() {
const queryClient = useQueryClient();
const mutation = useMutation({
mutationFn: createUser,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['users'] });
}
});
return (/* form JSX */);
}React Query is designed for React component lifecycle management. There isn't much benefit to calling mutations outside of React since you lose automatic lifecycle handling, caching, and retry logic.
Ensure useMutation is called unconditionally at the top level:
// ❌ WRONG - conditional hook call
function MyComponent({ enableMutation }: { enableMutation: boolean }) {
if (enableMutation) {
const mutation = useMutation({ /* ... */ }); // Breaks Rules of Hooks
}
return <div>...</div>;
}
// ✅ CORRECT - always call hook, conditionally use it
function MyComponent({ enableMutation }: { enableMutation: boolean }) {
const mutation = useMutation({ /* ... */ });
const handleAction = () => {
if (enableMutation) {
mutation.mutate(data);
}
};
return <button onClick={handleAction}>Submit</button>;
}Also avoid calling hooks inside loops or nested functions:
// ❌ WRONG - hook in loop
function BatchProcessor({ items }: { items: Item[] }) {
items.forEach(item => {
const mutation = useMutation({ /* ... */ }); // Error!
});
}
// ✅ CORRECT - single hook, loop in the mutation logic
function BatchProcessor({ items }: { items: Item[] }) {
const mutation = useMutation({
mutationFn: async (items: Item[]) => {
return Promise.all(
items.map(item => processItem(item))
);
}
});
const handleProcess = () => {
mutation.mutate(items);
};
return <button onClick={handleProcess}>Process All</button>;
}Advanced notes
Why React Query requires component context:
React Query's hooks rely on React's context system to access the QueryClient instance and React's component lifecycle to manage subscriptions, cleanup, and re-renders. When you call useMutation outside of a component, these systems are unavailable.
Sharing mutation state across components (v5+):
In React Query v5+, you can use useMutationState to observe mutation state across different components:
import { useMutationState } from '@tanstack/react-query';
function GlobalMutationStatus() {
const mutations = useMutationState({
filters: { mutationKey: ['createUser'], status: 'pending' }
});
if (mutations.length > 0) {
return <div>Creating user...</div>;
}
return null;
}Advanced pattern for optimistic updates:
When using mutations with optimistic updates, keep query-related logic (like cache updates) in useMutation callbacks, and UI-related logic (toasts, redirects) in .mutate callbacks:
const mutation = useMutation({
mutationFn: updateUser,
// Query-related: runs even if component unmounts
onMutate: async (newUser) => {
await queryClient.cancelQueries({ queryKey: ['users', newUser.id] });
const previousUser = queryClient.getQueryData(['users', newUser.id]);
queryClient.setQueryData(['users', newUser.id], newUser);
return { previousUser };
},
onError: (err, newUser, context) => {
queryClient.setQueryData(['users', newUser.id], context?.previousUser);
}
});
// In component:
mutation.mutate(userData, {
// UI-related: only runs if component is mounted
onSuccess: () => {
toast.success('User updated!');
navigate('/users');
}
});Using QueryClient directly (advanced):
In rare cases where you absolutely must execute a mutation outside React (like in a background worker), you can use QueryClient directly without hooks:
import { QueryClient } from '@tanstack/react-query';
const queryClient = new QueryClient();
// This bypasses React entirely
await queryClient.executeMutation({
mutationFn: () => fetch('/api/users', { method: 'POST', body: data })
});However, this loses most of React Query's benefits and should only be used in non-React contexts.
Frequently asked questions
- What does "useMutation only works inside a component" mean?
- This error appears when you attempt to use React Query's useMutation hook in an invalid context—typically outside of a React functional component or custom hook. React Query's useMutation is a React hook, which means it must follow React's Rules of Hooks. The Rules of Hooks state that hooks can only be called: 1.
- How do I fix "useMutation only works inside a component"?
- This error occurs when you try to call React Query's useMutation hook outside of a functional component or custom hook. Like all React hooks, useMutation must follow the Rules of Hooks and can only be called at the top level of components.
- What causes "useMutation only works inside a component"?
- Attempting to call useMutation directly in a utility function or service module Calling useMutation inside a regular JavaScript function instead of a component Trying to use useMutation in a class component
- Is the "useMutation only works inside a component" error serious?
- It is a medium-severity error — more of a blocker than a risk. The steps above resolve it.