- Spec files
npm-react
Describes: pkg:npm/react@19.1.x
- Description
- React is a JavaScript library for building user interfaces with a declarative, component-based approach.
- Author
- tessl
- Last updated
concurrency.md docs/
1# Concurrency and Transitions23Concurrent features for managing non-urgent updates and improving user experience. These features help React prioritize important updates while deferring less critical ones.45## Capabilities67### useTransition89Hook for marking updates as non-urgent transitions to keep the UI responsive.1011```typescript { .api }12/**13* Marks state updates as non-urgent transitions14* @returns Array with isPending boolean and startTransition function15*/16function useTransition(): [17isPending: boolean,18startTransition: (callback: () => void) => void19];20```2122**Usage Examples:**2324```typescript25import React, { useState, useTransition } from "react";2627function SearchResults() {28const [query, setQuery] = useState("");29const [results, setResults] = useState<string[]>([]);30const [isPending, startTransition] = useTransition();3132const handleSearch = (newQuery: string) => {33// Update input immediately (urgent)34setQuery(newQuery);3536// Update results as transition (non-urgent)37startTransition(() => {38// Simulate expensive search operation39const searchResults = performExpensiveSearch(newQuery);40setResults(searchResults);41});42};4344return (45<div>46<input47value={query}48onChange={(e) => handleSearch(e.target.value)}49placeholder="Search..."50/>5152{isPending && <div>Searching...</div>}5354<ul>55{results.map((result, index) => (56<li key={index}>{result}</li>57))}58</ul>59</div>60);61}6263function performExpensiveSearch(query: string): string[] {64// Simulate expensive operation65const items = Array.from({ length: 10000 }, (_, i) => `Item ${i}: ${query}`);66return items.filter(item => item.includes(query)).slice(0, 100);67}68```6970### startTransition7172Function for marking updates as non-urgent transitions without using a hook.7374```typescript { .api }75/**76* Marks state updates as non-urgent transitions77* @param callback - Function containing state updates to mark as transition78*/79function startTransition(callback: () => void): void;80```8182**Usage Examples:**8384```typescript85import React, { useState, startTransition } from "react";8687function TabContainer() {88const [activeTab, setActiveTab] = useState("home");89const [tabContent, setTabContent] = useState<React.ReactNode>("Home content");9091const switchTab = (tabId: string) => {92// Update active tab immediately (urgent)93setActiveTab(tabId);9495// Update content as transition (non-urgent)96startTransition(() => {97setTabContent(generateExpensiveContent(tabId));98});99};100101return (102<div>103<nav>104<button105onClick={() => switchTab("home")}106className={activeTab === "home" ? "active" : ""}107>108Home109</button>110<button111onClick={() => switchTab("profile")}112className={activeTab === "profile" ? "active" : ""}113>114Profile115</button>116<button117onClick={() => switchTab("settings")}118className={activeTab === "settings" ? "active" : ""}119>120Settings121</button>122</nav>123124<main>125{tabContent}126</main>127</div>128);129}130131function generateExpensiveContent(tabId: string): React.ReactNode {132// Simulate expensive content generation133const items = Array.from({ length: 1000 }, (_, i) => (134<div key={i}>{tabId} item {i}</div>135));136return <div>{items}</div>;137}138```139140### useDeferredValue141142Hook for deferring non-urgent updates to keep the UI responsive.143144```typescript { .api }145/**146* Defers a value update to prevent blocking urgent updates147* @param value - Value to defer148* @param initialValue - Optional initial value used during initial render149* @returns Deferred value that may lag behind the actual value150*/151function useDeferredValue<T>(value: T): T;152function useDeferredValue<T>(value: T, initialValue: T): T;153```154155**Usage Examples:**156157```typescript158import React, { useState, useDeferredValue, useMemo } from "react";159160function ProductList() {161const [searchTerm, setSearchTerm] = useState("");162const deferredSearchTerm = useDeferredValue(searchTerm);163164// Expensive filtering based on deferred value165const filteredProducts = useMemo(() => {166console.log("Filtering products...");167return PRODUCTS.filter(product =>168product.name.toLowerCase().includes(deferredSearchTerm.toLowerCase())169);170}, [deferredSearchTerm]);171172const isStale = searchTerm !== deferredSearchTerm;173174return (175<div>176<input177value={searchTerm}178onChange={(e) => setSearchTerm(e.target.value)}179placeholder="Search products..."180/>181182<div style={{ opacity: isStale ? 0.5 : 1 }}>183{isStale && <div>Updating results...</div>}184<ul>185{filteredProducts.map(product => (186<li key={product.id}>187{product.name} - ${product.price}188</li>189))}190</ul>191</div>192</div>193);194}195196const PRODUCTS = [197{ id: 1, name: "Laptop", price: 999 },198{ id: 2, name: "Phone", price: 599 },199// ... many more products200];201```202203### useOptimistic204205Hook for implementing optimistic updates that appear immediately but can be reverted.206207```typescript { .api }208/**209* Manages optimistic state updates210* @param state - Current state211* @param updateFn - Function to apply optimistic update212* @returns Array with optimistic state and function to add optimistic update213*/214function useOptimistic<S, A>(215state: S,216updateFn: (currentState: S, optimisticValue: A) => S217): [S, (optimisticValue: A) => void];218```219220**Usage Examples:**221222```typescript223import React, { useState, useOptimistic } from "react";224225interface Todo {226id: string;227text: string;228completed: boolean;229}230231function TodoApp() {232const [todos, setTodos] = useState<Todo[]>([]);233const [optimisticTodos, addOptimisticTodo] = useOptimistic(234todos,235(currentTodos, newTodo: Todo) => [...currentTodos, newTodo]236);237238const addTodo = async (text: string) => {239const newTodo: Todo = {240id: `temp-${Date.now()}`,241text,242completed: false243};244245// Show optimistic update immediately246addOptimisticTodo(newTodo);247248try {249// Send to server250const response = await fetch("/api/todos", {251method: "POST",252headers: { "Content-Type": "application/json" },253body: JSON.stringify({ text })254});255256if (response.ok) {257const savedTodo = await response.json();258// Replace optimistic todo with server response259setTodos(currentTodos => [...currentTodos, savedTodo]);260} else {261throw new Error("Failed to save todo");262}263} catch (error) {264// Optimistic update will be automatically reverted265console.error("Failed to add todo:", error);266}267};268269return (270<div>271<form onSubmit={(e) => {272e.preventDefault();273const formData = new FormData(e.currentTarget);274const text = formData.get("text") as string;275if (text.trim()) {276addTodo(text.trim());277e.currentTarget.reset();278}279}}>280<input name="text" placeholder="Add todo..." />281<button type="submit">Add</button>282</form>283284<ul>285{optimisticTodos.map(todo => (286<li287key={todo.id}288style={{289opacity: todo.id.startsWith("temp-") ? 0.7 : 1,290fontStyle: todo.id.startsWith("temp-") ? "italic" : "normal"291}}292>293{todo.text}294{todo.id.startsWith("temp-") && " (saving...)"}295</li>296))}297</ul>298</div>299);300}301```302303## Advanced Concurrency Patterns304305### Priority-Based Updates306307```typescript308import React, { useState, useTransition, useDeferredValue, startTransition } from "react";309310function MultiLevelPriorityApp() {311// Immediate: User input312const [inputValue, setInputValue] = useState("");313314// Deferred: Search results315const deferredInput = useDeferredValue(inputValue);316317// Transition: Heavy processing318const [processedData, setProcessedData] = useState<any[]>([]);319const [isPending, startTransition] = useTransition();320321React.useEffect(() => {322// Medium priority: Search results323const searchResults = performSearch(deferredInput);324325// Low priority: Heavy data processing326startTransition(() => {327const processed = performHeavyProcessing(searchResults);328setProcessedData(processed);329});330}, [deferredInput]);331332return (333<div>334{/* High priority: Always responsive */}335<input336value={inputValue}337onChange={(e) => setInputValue(e.target.value)}338placeholder="Type to search..."339/>340341{/* Medium priority: May lag slightly */}342<div>343Search term: {deferredInput}344</div>345346{/* Low priority: May be interrupted */}347<div>348{isPending ? "Processing..." : `Processed ${processedData.length} items`}349</div>350</div>351);352}353354function performSearch(query: string) {355// Simulate search356return query ? [`Result for ${query}`] : [];357}358359function performHeavyProcessing(data: any[]) {360// Simulate expensive processing361return data.map(item => ({ ...item, processed: true, timestamp: Date.now() }));362}363```364365### Coordinated Transitions366367```typescript368import React, { useState, useTransition } from "react";369370function CoordinatedUpdates() {371const [view, setView] = useState("list");372const [data, setData] = useState<any[]>([]);373const [filters, setFilters] = useState<any>({});374const [isPending, startTransition] = useTransition();375376const switchView = (newView: string) => {377startTransition(() => {378// Coordinate multiple state updates in single transition379setView(newView);380setData(loadDataForView(newView));381setFilters(getDefaultFiltersForView(newView));382});383};384385return (386<div>387<nav>388<button onClick={() => switchView("list")}>List View</button>389<button onClick={() => switchView("grid")}>Grid View</button>390<button onClick={() => switchView("chart")}>Chart View</button>391</nav>392393{isPending && <div>Switching views...</div>}394395<main>396<ViewRenderer view={view} data={data} filters={filters} />397</main>398</div>399);400}401402function loadDataForView(view: string) {403// Simulate loading data specific to view404return Array.from({ length: 100 }, (_, i) => ({ id: i, view, data: `${view} item ${i}` }));405}406407function getDefaultFiltersForView(view: string) {408// Return view-specific default filters409return { view, sortOrder: "asc", category: "all" };410}411412function ViewRenderer({ view, data, filters }: any) {413return <div>{view} view with {data.length} items</div>;414}415```416417### Interrupted Transitions418419```typescript420import React, { useState, useTransition, useEffect } from "react";421422function InterruptibleTransitions() {423const [query, setQuery] = useState("");424const [results, setResults] = useState<string[]>([]);425const [isPending, startTransition] = useTransition();426427useEffect(() => {428if (query) {429startTransition(() => {430// This transition can be interrupted by new queries431const searchResults = performSlowSearch(query);432setResults(searchResults);433});434}435}, [query]);436437return (438<div>439<input440value={query}441onChange={(e) => setQuery(e.target.value)}442placeholder="Search (try typing quickly)..."443/>444445<div>446{isPending ? "Searching..." : `Found ${results.length} results`}447</div>448449<ul>450{results.slice(0, 10).map((result, index) => (451<li key={index}>{result}</li>452))}453</ul>454</div>455);456}457458function performSlowSearch(query: string): string[] {459// Simulate slow search that can be interrupted460const start = Date.now();461const results = [];462463for (let i = 0; i < 10000; i++) {464if (Date.now() - start > 100) break; // Simulate slow operation465if (`item ${i}`.includes(query.toLowerCase())) {466results.push(`Search result ${i} for "${query}"`);467}468}469470return results;471}472```473474## Types475476### Concurrency-Related Types477478```typescript { .api }479type TransitionStartFunction = (callback: () => void) => void;480481interface TransitionOptions {482name?: string;483}484485type DeferredValueHook = {486<T>(value: T): T;487<T>(value: T, initialValue: T): T;488};489490type OptimisticStateHook = <S, A>(491state: S,492updateFn: (currentState: S, optimisticValue: A) => S493) => [S, (optimisticValue: A) => void];494```495496## Experimental/Unstable Concurrency Features497498### unstable_addTransitionType499500Adds a transition type for profiling and debugging purposes.501502```typescript { .api }503/**504* Adds a transition type for profiling505* @param type - String identifier for the transition type506*/507function unstable_addTransitionType(type: string): void;508```509510### unstable_getCacheForType511512Gets cache instance for a specific resource type.513514```typescript { .api }515/**516* Gets cache instance for resource type517* @param resourceType - Function that returns resource type518* @returns Cache instance for the resource type519*/520function unstable_getCacheForType<T>(resourceType: () => T): T;521```522523### unstable_useCacheRefresh524525Hook for refreshing cache entries.526527```typescript { .api }528/**529* Hook for refreshing cache530* @returns Function to refresh cache entries531*/532function unstable_useCacheRefresh(): <T>(?() => T, ?T) => void;533```534535### unstable_useSwipeTransition536537Hook for gesture-based transitions (experimental).538539```typescript { .api }540/**541* Hook for swipe gesture transitions542* @param gesture - Gesture configuration543* @returns Array with pending state and transition function544*/545function unstable_useSwipeTransition<T>(546gesture: StartGesture<T>547): [boolean, (callback: () => void, options?: { gesture: T }) => void];548549type StartGesture<T> = any; // Placeholder for gesture type550```