This error occurs when startTransition is called directly in the render phase instead of in event handlers or effects. React requires startTransition to be called during non-render phases to properly manage concurrent updates and maintain rendering stability.
The "startTransition cannot be called during render" error is a React 18+ safeguard that prevents improper use of the Concurrent Features API. startTransition is designed to mark state updates as non-urgent transitions, allowing React to interrupt them for more urgent updates like user input. When you call startTransition during the render phase (directly in the component body), React cannot properly schedule and manage these transitions. The render phase must be pure and free of side effects - calling startTransition here violates this principle and can lead to unpredictable behavior, infinite loops, or rendering inconsistencies. This error typically appears in development mode as React actively checks for improper API usage. In production, it may manifest as unexpected rendering behavior or runtime errors.
The most common fix is to move startTransition calls into event handlers rather than the render phase:
// ❌ WRONG - calling during render
function SearchResults({ query }) {
startTransition(() => {
setSearchQuery(query);
});
return <div>...</div>;
}
// ✅ CORRECT - calling in event handler
function SearchResults() {
const [searchQuery, setSearchQuery] = useState('');
const handleSearch = (query: string) => {
startTransition(() => {
setSearchQuery(query);
});
};
return (
<input
onChange={(e) => handleSearch(e.target.value)}
/>
);
}This ensures startTransition only runs in response to user interactions, not during the render phase.
If you need to trigger transitions based on prop changes, use useEffect:
// ❌ WRONG - calling during render
function FilteredList({ filter }) {
startTransition(() => {
setFilteredItems(items.filter(item => item.matches(filter)));
});
return <List items={filteredItems} />;
}
// ✅ CORRECT - using useEffect
function FilteredList({ filter }) {
const [filteredItems, setFilteredItems] = useState([]);
useEffect(() => {
startTransition(() => {
setFilteredItems(items.filter(item => item.matches(filter)));
});
}, [filter]);
return <List items={filteredItems} />;
}This defers the transition until after the initial render completes.
Replace standalone startTransition with the useTransition hook for better control:
import { useTransition } from 'react';
function SearchComponent() {
const [isPending, startTransition] = useTransition();
const [searchResults, setSearchResults] = useState([]);
const handleSearch = (query: string) => {
startTransition(() => {
// This state update is now marked as a transition
setSearchResults(performExpensiveSearch(query));
});
};
return (
<div>
<input onChange={(e) => handleSearch(e.target.value)} />
{isPending && <Spinner />}
<Results data={searchResults} />
</div>
);
}The useTransition hook provides the startTransition function along with a pending state indicator.
Ensure you're using React 18 or higher with proper imports:
# Check your React version
npm list react react-dom
# Upgrade if needed
npm install react@latest react-dom@latestThen verify your imports:
import { startTransition } from 'react';
// or
import { useTransition } from 'react';If you're on React 17 or earlier, startTransition is not available and you'll need to upgrade.
In React 18, startTransition callbacks must be synchronous. For async operations:
// ❌ WRONG - async function in React 18
const handleSubmit = () => {
startTransition(async () => {
const result = await fetchData();
setData(result);
});
};
// ✅ CORRECT - wrap only the state update
const handleSubmit = async () => {
const result = await fetchData();
startTransition(() => {
setData(result);
});
};Note: React 19 supports async callbacks in startTransition, but React 18 requires synchronous functions.
Concurrent Rendering Context: startTransition is part of React's Concurrent Features that enable interruptible rendering. When you mark updates as transitions, React can pause work on them to handle more urgent updates (like user input), then resume the transition. This requires strict control over when transitions start - allowing them during render would break React's ability to manage the update queue.
Server-Side Rendering: On the server, startTransition has no effect since SSR is always synchronous. If you see this error during SSR, it indicates code that should only run on the client is executing on the server. Use typeof window !== 'undefined' checks or ensure the code only runs in client components with the 'use client' directive.
useMemo and useCallback Gotcha: Never call startTransition inside useMemo or useCallback dependency arrays or during their execution on render:
// ❌ WRONG
const memoizedValue = useMemo(() => {
startTransition(() => setCount(count + 1));
return count * 2;
}, [count]);
// ✅ CORRECT
const handleIncrement = useCallback(() => {
startTransition(() => setCount(count + 1));
}, [count]);React 19 Changes: React 19 introduces async support for startTransition callbacks, allowing you to use await inside the transition function. This is a breaking change from React 18's synchronous-only requirement. If upgrading, review all startTransition usage to take advantage of this improvement.
Debugging Tips: Enable React DevTools Profiler with "Highlight updates when components render" to visualize when transitions occur. This helps identify if transitions are firing during render vs. in response to events.
React Hook useCallback has a missing dependency: 'variable'. Either include it or remove the dependency array react-hooks/exhaustive-deps
React Hook useCallback has a missing dependency
Cannot use private fields in class components without TS support
Cannot use private fields in class components without TS support
Cannot destructure property 'xxx' of 'undefined'
Cannot destructure property of undefined when accessing props
useNavigate() may be used only in the context of a <Router> component.
useNavigate() may be used only in the context of a Router component
Cannot find module or its corresponding type declarations
How to fix "Cannot find module or type declarations" in Vite