React Concurrent Features
Full Reference: See advanced.md for advanced concurrent patterns including priority-based updates, optimistic updates with transitions, router integration, form actions, and debugging techniques.
Deep Knowledge: Use
mcp__documentation__fetch_docswith technology:react, topic:concurrentfor comprehensive documentation.
When NOT to Use This Skill
Skip this skill when:
- Using React 17 or earlier (not available)
- Building simple apps with fast renders
- All state updates are already fast (< 16ms)
- Working with non-React frameworks
- Server Components handle the work (Next.js App Router)
Overview
Concurrent React allows interrupting renders to keep the UI responsive:
Traditional Rendering:
[Update starts]────────────────────────[Render complete]
UI blocked!
Concurrent Rendering:
[Update starts]──[pause]──[higher priority]──[resume]──[complete]
UI remains responsive!
useTransition
Mark state updates as non-urgent (can be interrupted):
import { useState, useTransition } from 'react';
function TabContainer() {
const [tab, setTab] = useState('home');
const [isPending, startTransition] = useTransition();
function selectTab(nextTab: string) {
startTransition(() => {
setTab(nextTab);
});
}
return (
<div>
<nav>
{['home', 'about', 'contact'].map((t) => (
<button key={t} onClick={() => selectTab(t)}>
{t}
</button>
))}
</nav>
<div className={isPending ? 'opacity-50' : ''}>
{tab === 'home' && <Home />}
{tab === 'about' && <About />}
{tab === 'contact' && <Contact />}
</div>
</div>
);
}
useDeferredValue
Defer updating a value to keep UI responsive:
import { useState, useDeferredValue, memo } from 'react';
function SearchPage() {
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
const isStale = query !== deferredQuery;
return (
<div>
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search..."
/>
<div className={isStale ? 'opacity-50' : ''}>
<SearchResults query={deferredQuery} />
</div>
</div>
);
}
// Memoize to actually benefit from deferred value
const SearchResults = memo(function SearchResults({ query }: { query: string }) {
const results = useMemo(() => {
return items.filter(item =>
item.name.toLowerCase().includes(query.toLowerCase())
);
}, [query]);
return <ul>{results.map(item => <li key={item.id}>{item.name}</li>)}</ul>;
});
startTransition (without Hook)
For use outside components or when you don't need isPending:
import { startTransition } from 'react';
function handleClick() {
startTransition(() => {
setPage('/heavy-page');
});
}
Suspense with Transitions
function App() {
const [tab, setTab] = useState('home');
const [isPending, startTransition] = useTransition();
return (
<div>
<nav>
<button onClick={() => startTransition(() => setTab('posts'))}>
Posts
</button>
</nav>
<Suspense fallback={<TabSkeleton />}>
<div className={isPending ? 'opacity-50' : ''}>
{tab === 'posts' && <Posts />}
</div>
</Suspense>
</div>
);
}
useTransition vs useDeferredValue
| Feature | useTransition | useDeferredValue |
|---------|---------------|------------------|
| Purpose | Wrap state updates | Defer a value |
| Usage | When you control the update | When you receive a value |
| Returns | [isPending, startTransition] | Deferred value |
| Use case | Button clicks, form submits | Props from parent |
Anti-Patterns
| Anti-Pattern | Why It's Bad | Correct Approach |
|--------------|--------------|------------------|
| Wrapping every state update | Adds unnecessary complexity | Only for heavy updates |
| Not memoizing child components | No benefit from deferred value | Use memo() |
| Using for fast operations | Performance overhead | Reserve for slow renders (> 50ms) |
| Ignoring isPending state | User sees no feedback | Show loading indicator |
| Multiple transitions unnecessarily | Confusing behavior | Batch related updates |
Quick Troubleshooting
| Issue | Likely Cause | Solution |
|-------|--------------|----------|
| No performance improvement | Child not memoized | Add memo() to component |
| Still blocking UI | Synchronous heavy computation | Move to Web Worker |
| isPending always false | Update completes too fast | No transition needed |
| Stale UI shown too long | No loading indicator | Check and display isPending |
| Transition not working | Using in React < 18 | Upgrade to React 18+ |
Best Practices
- ✅ Use for user-initiated heavy updates
- ✅ Combine with
memo()for list components - ✅ Show visual feedback during pending state
- ✅ Use for route transitions
- ✅ Prefer
useDeferredValuefor received props - ❌ Don't wrap every state update
- ❌ Don't use for fast operations
- ❌ Don't forget to memoize child components
Reference Documentation
Deep Knowledge: Use
mcp__documentation__fetch_docswith technology:react, topic:concurrentfor comprehensive documentation.
微信扫一扫