This React 18 error occurs when attempting to wrap text input state updates inside startTransition or useTransition. React prevents transition updates from controlling text inputs because user input must be treated as urgent, not deferrable.
This error appears when developers try to use React 18's `startTransition` or `useTransition` to wrap state updates that directly control a text input's value. React enforces this restriction because text inputs require immediate, synchronous updates to maintain a responsive user experience. In React 18's concurrent rendering model, transition updates are marked as non-urgent and can be interrupted. However, when users type into an input field, those updates must be processed immediately—any delay would make the input feel sluggish or unresponsive. If you update a component inside a transition but then start typing into an input, React will restart the rendering work after handling the input update, because input updates are always treated as urgent. This design decision ensures that user interactions with form fields remain crisp and responsive, even when other parts of your application are performing expensive computations in the background.
The correct pattern involves maintaining two state values: one for the input (urgent) and one for the derived result (non-urgent).
function SearchComponent() {
const [inputValue, setInputValue] = useState('');
const [query, setQuery] = useState('');
const [isPending, startTransition] = useTransition();
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
// Urgent update - keeps input responsive
setInputValue(e.target.value);
// Non-urgent update - can be deferred
startTransition(() => {
setQuery(e.target.value);
});
};
const filteredResults = expensiveFilter(items, query);
return (
<>
<input
type="text"
value={inputValue}
onChange={handleChange}
/>
{isPending && <LoadingSpinner />}
<ResultsList items={filteredResults} />
</>
);
}This ensures the input remains responsive while expensive filtering operations run in the background.
If you're getting this error, locate where you're wrapping input state updates in startTransition and remove it:
Incorrect:
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
startTransition(() => {
setValue(e.target.value); // ❌ Don't do this
});
};Correct:
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setValue(e.target.value); // ✅ Keep input updates urgent
};Input updates must always be synchronous to maintain responsiveness.
Use transitions for heavy operations triggered by input changes, not the input state itself:
function FilteredList() {
const [searchTerm, setSearchTerm] = useState('');
const [filteredData, setFilteredData] = useState(data);
const [isPending, startTransition] = useTransition();
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
setSearchTerm(value); // Urgent - updates input immediately
startTransition(() => {
// Non-urgent - can be interrupted
const filtered = data.filter(item =>
item.name.toLowerCase().includes(value.toLowerCase())
);
setFilteredData(filtered);
});
};
return (
<div>
<input
value={searchTerm}
onChange={handleSearch}
placeholder="Search..."
/>
{isPending && <p>Filtering...</p>}
<List items={filteredData} />
</div>
);
}For simpler cases, useDeferredValue can achieve similar results with less code:
function SearchResults() {
const [input, setInput] = useState('');
const deferredInput = useDeferredValue(input);
const results = useMemo(
() => filterItems(items, deferredInput),
[deferredInput]
);
return (
<>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
/>
<ResultsList items={results} />
</>
);
}This automatically defers the filtering computation while keeping the input responsive.
Why React enforces this restriction:
React's concurrent rendering allows it to work on multiple versions of the UI simultaneously and interrupt rendering work. Transition updates are explicitly marked as non-urgent, allowing React to pause them if more urgent updates arrive. Text inputs, however, must respond immediately to user keystrokes—any delay creates a poor user experience.
If React allowed transition updates to control inputs, the following scenario would break:
1. User types into an input
2. React marks the update as non-urgent (transition)
3. User types again before React processes the first keystroke
4. Input appears broken because characters don't appear immediately
Comparison with debouncing:
The two-state pattern with transitions is different from traditional debouncing:
- Debouncing: Delays function execution until typing pauses
- Transitions: Input updates immediately, but derived computations are deferred and interruptible
Transitions provide a better user experience because:
- Input feedback is instant (no typing lag)
- Expensive operations don't block the UI
- Users see pending states during long computations
Performance considerations:
For very large datasets (10,000+ items), combine transitions with:
- Virtualization libraries (react-window, react-virtuoso)
- Memoization of expensive computations
- Web Workers for heavy processing
- Server-side filtering for massive datasets
The transition API is designed for client-side UI improvements, not as a replacement for proper architectural solutions to performance problems.
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