Error: optimistic state update outside Transition
If you get the error 'An optimistic state update occurred outside a Transition or Action. To fix, move the update to an Action, or wrap with startTransition.', it means you called the optimistic setter outside of startTransition or an Action prop. The setter must be called inside startTransition or inside an Action prop. When you call the setter outside an Action, the optimistic state will briefly appear and then immediately revert back to the original value because there's no Transition to hold the optimistic state while your Action runs.
Error: cannot update optimistic state while rendering
If you get the error 'Cannot update optimistic state while rendering', it means you called the optimistic setter during the render phase of a component. You can only call the setter from event handlers, effects, or other callbacks, never during render. The setter must be called from within startTransition in event handlers or from within Action functions.
useOptimistic showing stale values
If optimistic state shows based on old data, especially when state changes during an Action, use an updater function or reducer to calculate the optimistic state relative to the current state. For example, instead of setOptimistic(5) which always sets to 5 even if count changed, use an updater like adjust(1) which adds 1 to whatever the current count is. This ensures relative updates handle state changes correctly and prevents optimistic updates from being based on stale data.
useRef initialValue parameter
The `initialValue` parameter in useRef can be a value of any type. It sets the initial value of the ref object's `current` property. This argument is ignored after the initial render.
useRef hook signature and return value
useRef is a React Hook that returns an object with a single property named `current`. The signature is `const ref = useRef(initialValue)`. The returned ref object persists across renders and allows you to store a mutable value that does not trigger a re-render when changed.
useRef does not trigger re-renders
Changing a ref's `current` property does not trigger a component re-render. Refs are perfect for storing information that does not affect the visual output of the component. Use state instead of refs for information that needs to display on the screen.
useRef.current property behavior
The `current` property of a ref object is mutable and can be changed at any time. Unlike state, changing `ref.current` does not trigger a re-render. If the ref is passed as a `ref` attribute to a JSX node, React will set its `current` property to that DOM node. React sets `current` back to `null` when the node is removed from the screen.
useRef values persist between renders
On the next renders, useRef will return the same ref object that was created on the initial render. This allows you to store information between re-renders without losing it, unlike regular variables which reset on every render.
Do not read or write ref.current during rendering
Reading or writing `ref.current` during the render phase breaks the expectation that a component function should be pure. You must not write or read `ref.current` during rendering, except for initialization. Reading or writing refs should only happen in event handlers or effects.
useRef initialization exception for reading ref.current
Although reading or writing `ref.current` during render is not allowed, it is acceptable to initialize `ref.current` with a lazy initialization pattern during rendering. For example, checking `if (playerRef.current === null)` and then assigning a new object is allowed because the condition only executes during initialization and produces the same result every time.
useRef Strict Mode behavior
In Strict Mode during development, React calls your component function twice to help find accidental impurities. This means each ref object will be created twice, but one version will be discarded. If your component function is pure, this should not affect the behavior. This is development-only and does not affect production.
useRef with mutable objects and rendering
You can mutate the `ref.current` property. However, if it holds an object that is used for rendering (such as a piece of state), you should not mutate that object.
useRef for storing interval IDs
A common use case for useRef is storing an interval ID returned by setInterval. The interval ID is not used for rendering, so it is appropriate to keep it in a ref and manually update it by setting `ref.current`.
useRef for DOM manipulation
useRef is commonly used to manipulate the DOM. Declare a ref object with an initial value of `null`, pass it as the `ref` attribute to a JSX DOM node, and then access the DOM node via `ref.current` to call methods like `focus()`, or use DOM APIs like `querySelectorAll()` or `scrollIntoView()`.
useRef click counter example
This example shows a click counter using useRef: `import { useRef } from 'react'; export default function Counter() { let ref = useRef(0); function handleClick() { ref.current = ref.current + 1; alert('You clicked ' + ref.current + ' times!'); } return <button onClick={handleClick}>Click me!</button>; }`. Note that displaying `{ref.current}` in JSX will not update on click because setting `ref.current` does not trigger a re-render.
useRef stopwatch example with state and refs
This example combines useState and useRef: `import { useState, useRef } from 'react'; export default function Stopwatch() { const [startTime, setStartTime] = useState(null); const [now, setNow] = useState(null); const intervalRef = useRef(null); function handleStart() { setStartTime(Date.now()); setNow(Date.now()); clearInterval(intervalRef.current); intervalRef.current = setInterval(() => { setNow(Date.now()); }, 10); } function handleStop() { clearInterval(intervalRef.current); } let secondsPassed = 0; if (startTime != null && now != null) { secondsPassed = (now - startTime) / 1000; } return <><h1>Time passed: {secondsPassed.toFixed(3)}</h1><button onClick={handleStart}>Start</button><button onClick={handleStop}>Stop</button></>; }`. State variables track rendered time, while the ref stores the interval ID.
useRef scroll image into view example
This example uses useRef with DOM APIs to scroll images: `import { useRef } from 'react'; export default function CatFriends() { const listRef = useRef(null); function scrollToIndex(index) { const listNode = listRef.current; const imgNode = listNode.querySelectorAll('li > img')[index]; imgNode.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center' }); } return <><nav><button onClick={() => scrollToIndex(0)}>Neo</button><button onClick={() => scrollToIndex(1)}>Millie</button><button onClick={() => scrollToIndex(2)}>Bella</button></nav><div><ul ref={listRef}><li><img src="https://placecats.com/neo/300/200" alt="Neo" /></li><li><img src="https://placecats.com/millie/200/200" alt="Millie" /></li><li><img src="https://placecats.com/bella/199/200" alt="Bella" /></li></ul></div></>; }`. The ref stores the list container and querySelector finds images to scroll into view.
useRef video player control example
This example uses useRef to call play() and pause() on a video element: `import { useState, useRef } from 'react'; export default function VideoPlayer() { const [isPlaying, setIsPlaying] = useState(false); const ref = useRef(null); function handleClick() { const nextIsPlaying = !isPlaying; setIsPlaying(nextIsPlaying); if (nextIsPlaying) { ref.current.play(); } else { ref.current.pause(); } } return <><button onClick={handleClick}>{isPlaying ? 'Pause' : 'Play'}</button><video width="250" ref={ref} onPlay={() => setIsPlaying(true)} onPause={() => setIsPlaying(false)}><source src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4" type="video/mp4" /></video></>; }`. The ref controls video playback through DOM methods.
useRef exposing ref to parent component
To let a parent component manipulate the DOM inside a child component, you can accept `ref` as a prop in the child and pass it to the child element: `function MyInput({ ref }) { return <input ref={ref} />; } export default function Form() { const inputRef = useRef(null); function handleClick() { inputRef.current.focus(); } return <><MyInput ref={inputRef} /><button onClick={handleClick}>Focus the input</button></>; }`. This allows parent components to access and manipulate child DOM nodes.
useRef lazy initialization pattern
To avoid recreating expensive objects on every render, initialize a ref with a lazy pattern: `function Video() { const playerRef = useRef(null); if (playerRef.current === null) { playerRef.current = new VideoPlayer(); } }`. Although this reads `ref.current` during render, it is acceptable because the condition only executes once during initialization.
useRef lazy initialization with getter function
To avoid null checks when initializing useRef later, use a getter function pattern: `function Video() { const playerRef = useRef(null); function getPlayer() { if (playerRef.current !== null) { return playerRef.current; } const player = new VideoPlayer(); playerRef.current = player; return player; } }`. The ref itself remains nullable, but the getter function guarantees a non-null return value.
useRef cannot access custom component refs by default
By default, custom components do not expose refs to the DOM nodes inside them. If you try to pass a ref to a custom component like `<MyInput ref={inputRef} />`, you will get an error. The custom component must accept `ref` as a prop and explicitly pass it to a built-in component.
useRef custom component solution
To fix the issue of not being able to get a ref to a custom component, modify the custom component to accept `ref` as a prop and pass it to a built-in element: `function MyInput({ value, onChange, ref }) { return <input value={value} onChange={onChange} ref={ref} />; } export default MyInput;`. Then the parent component can pass a ref to the custom component and access the underlying input element.
useRef info is local to each component copy
Information stored in a ref is local to each copy of your component. Unlike variables defined outside a component (which are shared across instances), each component instance has its own independent ref object.
useRef differences from useState
The key differences between useRef and useState are: (1) useRef does not trigger a re-render when changed, while useState does; (2) useRef persists its value across renders without resetting; (3) useRef returns the same object on every render, while useState may create new state; (4) useRef is mutable, while useState updates are immutable; (5) useState should be used for information displayed on screen, useRef for information that does not affect rendering.
useSyncExternalStore hook signature
useSyncExternalStore is a React Hook that lets you subscribe to an external store. The signature is: const snapshot = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot?). It returns the snapshot of the data in the store.
useSyncExternalStore subscribe parameter
The subscribe parameter is a function that takes a single callback argument and subscribes it to the store. When the store changes, it should invoke the provided callback, which will cause React to re-call getSnapshot and (if needed) re-render the component. The subscribe function should return a function that cleans up the subscription.
useSyncExternalStore getSnapshot parameter
The getSnapshot parameter is a function that returns a snapshot of the data in the store that's needed by the component. While the store has not changed, repeated calls to getSnapshot must return the same value. If the store changes and the returned value is different (as compared by Object.is), React re-renders the component.
useSyncExternalStore getServerSnapshot parameter
The getServerSnapshot parameter is an optional function that returns the initial snapshot of the data in the store. It will be used only during server rendering and during hydration of server-rendered content on the client. The server snapshot must be the same between the client and the server, and is usually serialized and passed from the server to the client. If you omit this argument, rendering the component on the server will throw an error.
useSyncExternalStore snapshot must be immutable
The store snapshot returned by getSnapshot must be immutable. If the underlying store has mutable data, return a new immutable snapshot if the data has changed. Otherwise, return a cached last snapshot.
useSyncExternalStore subscribe function changes cause resubscription
If a different subscribe function is passed during a re-render, React will re-subscribe to the store using the newly passed subscribe function. You can prevent this by declaring subscribe outside the component.
useSyncExternalStore and non-blocking transitions
If the store is mutated during a non-blocking Transition update, React will fall back to performing that update as blocking. For every Transition update, React will call getSnapshot a second time just before applying changes to the DOM. If it returns a different value than when it was called originally, React will restart the update from scratch, this time applying it as a blocking update, to ensure that every component on screen is reflecting the same version of the store.
useSyncExternalStore should not suspend based on store value
It is not recommended to suspend a render based on a store value returned by useSyncExternalStore. The reason is that mutations to the external store cannot be marked as non-blocking Transition updates, so they will trigger the nearest Suspense fallback, replacing already-rendered content on screen with a loading spinner, which typically makes a poor UX.
useSyncExternalStore basic example with external store
This example shows how to use useSyncExternalStore to read from an external todos store:
import { useSyncExternalStore } from 'react';
import { todosStore } from './todoStore.js';
function TodosApp() {
const todos = useSyncExternalStore(todosStore.subscribe, todosStore.getSnapshot);
return (
<>
<button onClick={() => todosStore.addTodo()}>Add todo</button>
<hr />
<ul>
{todos.map(todo => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
</>
);
}
The external store implementation:
let nextId = 0;
let todos = [{ id: nextId++, text: 'Todo #1' }];
let listeners = [];
export const todosStore = {
addTodo() {
todos = [...todos, { id: nextId++, text: 'Todo #' + nextId }]
emitChange();
},
subscribe(listener) {
listeners = [...listeners, listener];
return () => {
listeners = listeners.filter(l => l !== listener);
};
},
getSnapshot() {
return todos;
}
};
function emitChange() {
for (let listener of listeners) {
listener();
}
}
useSyncExternalStore with browser API example
This example shows how to subscribe to the navigator.onLine browser API:
import { useSyncExternalStore } from 'react';
export default function ChatIndicator() {
const isOnline = useSyncExternalStore(subscribe, getSnapshot);
return <h1>{isOnline ? '✅ Online' : '❌ Disconnected'}</h1>;
}
function getSnapshot() {
return navigator.onLine;
}
function subscribe(callback) {
window.addEventListener('online', callback);
window.addEventListener('offline', callback);
return () => {
window.removeEventListener('online', callback);
window.removeEventListener('offline', callback);
};
}
useSyncExternalStore custom hook pattern
It is recommended to extract useSyncExternalStore logic into a custom Hook rather than using it directly in components. This example shows a custom useOnlineStatus hook:
import { useSyncExternalStore } from 'react';
export function useOnlineStatus() {
const isOnline = useSyncExternalStore(subscribe, getSnapshot);
return isOnline;
}
function getSnapshot() {
return navigator.onLine;
}
function subscribe(callback) {
window.addEventListener('online', callback);
window.addEventListener('offline', callback);
return () => {
window.removeEventListener('online', callback);
window.removeEventListener('offline', callback);
};
}
Now different components can call useOnlineStatus without repeating the underlying implementation.
useSyncExternalStore with server rendering example
This example shows how to add support for server rendering with the getServerSnapshot function:
import { useSyncExternalStore } from 'react';
export function useOnlineStatus() {
const isOnline = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
return isOnline;
}
function getSnapshot() {
return navigator.onLine;
}
function getServerSnapshot() {
return true; // Always show "Online" for server-generated HTML
}
function subscribe(callback) {
window.addEventListener('online', callback);
window.addEventListener('offline', callback);
return () => {
window.removeEventListener('online', callback);
window.removeEventListener('offline', callback);
};
}
The getServerSnapshot function runs on the server when generating the HTML and on the client during hydration.
useSyncExternalStore getSnapshot caching requirement
The getSnapshot function must return cached snapshots and only return a different object if something in the store has actually changed. If getSnapshot returns a new object every time it is called (such as creating a new object literal each time), React will enter an infinite loop and throw an error: 'The result of getSnapshot should be cached'.
useSyncExternalStore define subscribe outside component
To avoid unnecessary resubscription, define the subscribe function outside the component. If subscribe is defined inside the component, it will be a different function on every re-render, causing React to resubscribe on every re-render. Alternatively, wrap subscribe with useCallback to only resubscribe when specified dependencies change.
useSyncExternalStore server snapshot must match client
The getServerSnapshot function must return the same exact data on the initial client render as it returned on the server. If getServerSnapshot returned prepopulated store content on the server, that content must be transferred to the client. One way to do this is to emit a script tag during server rendering that sets a global variable like window.MY_STORE_DATA, and read from that global on the client in getServerSnapshot.
useState pitfall - 'Too many re-renders' error
You might get an error that says 'Too many re-renders. React limits the number of renders to prevent an infinite loop.' This typically means that you're unconditionally setting state during render, so your component enters a loop. Very often, this is caused by a mistake in specifying an event handler - for example, calling the handler during render instead of passing it down.
useState hook signature and return value
useState is a React Hook that adds a state variable to a component. The signature is `const [state, setState] = useState(initialState)`. It returns an array with exactly two values: the current state (initially set to initialState), and a set function that lets you update the state to a different value and trigger a re-render.
useState initialState parameter behavior
The initialState parameter can be a value of any type, or a function. If you pass a function as initialState, it will be treated as an initializer function. The initializer function should be pure, should take no arguments, and should return a value of any type. React will call your initializer function when initializing the component and store its return value as the initial state. The initialState argument is ignored after the initial render.
useState hook rules - must call at top level
useState is a Hook, so you can only call it at the top level of your component or your own Hooks. You cannot call it inside loops or conditions. If you need that, extract a new component and move the state into it.
useState initializer function called twice in Strict Mode
In Strict Mode, React will call your initializer function twice in order to help you find accidental impurities. This is development-only behavior and does not affect production. If your initializer function is pure (as it should be), this should not affect the behavior. The result from one of the calls will be ignored.
Set function signature and parameters
The set function returned by useState lets you update the state to a different value and trigger a re-render. You can pass the next state directly, or a function that calculates it from the previous state. If you pass a function as nextState, it will be treated as an updater function. The updater function must be pure, should take the pending state as its only argument, and should return the next state. React will put your updater function in a queue and re-render your component. During the next render, React will calculate the next state by applying all of the queued updaters to the previous state.
Set function does not have a return value
The set function returned by useState does not have a return value.
Set function updates state for next render only
The set function only updates the state variable for the next render. If you read the state variable after calling the set function, you will still get the old value that was on the screen before your call.
State update optimization with Object.is comparison
If the new value you provide is identical to the current state, as determined by an Object.is comparison, React will skip re-rendering the component and its children. This is an optimization. Although in some cases React may still need to call your component before skipping the children, it should not affect your code.
React batches state updates
React batches state updates. It updates the screen after all the event handlers have run and have called their set functions. This prevents multiple re-renders during a single event. In the rare case that you need to force React to update the screen earlier, for example to access the DOM, you can use flushSync.
Set function has stable identity
The set function has a stable identity, so you will often see it omitted from Effect dependencies, but including it will not cause the Effect to fire. If the linter lets you omit a dependency without errors, it is safe to do.
Set function during rendering behavior
Calling the set function during rendering is only allowed from within the currently rendering component. React will discard its output and immediately attempt to render it again with the new state. This pattern is rarely needed, but you can use it to store information from the previous renders.
Set function updater runs twice in Strict Mode
In Strict Mode, React will call your updater function twice in order to help you find accidental impurities. This is development-only behavior and does not affect production. If your updater function is pure (as it should be), this should not affect the behavior. The result from one of the calls will be ignored.
useState naming convention
The convention is to name state variables like [something, setSomething] using array destructuring.
useState example - counter with number state
Example showing useState with a number state variable. The count state variable holds a number. Clicking the button increments it:
```js
import { useState } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
function handleClick() {
setCount(count + 1);
}
return (
<button onClick={handleClick}>
You pressed me {count} times
</button>
);
}
```
useState example - text input with string state
Example showing useState with a string state variable. The text state variable holds a string. When you type, the handler reads the latest input value from the browser input DOM element and updates the state:
```js
import { useState } from 'react';
export default function MyInput() {
const [text, setText] = useState('hello');
function handleChange(e) {
setText(e.target.value);
}
return (
<>
<input value={text} onChange={handleChange} />
<p>You typed: {text}</p>
<button onClick={() => setText('hello')}>
Reset
</button>
</>
);
}
```
useState example - checkbox with boolean state
Example showing useState with a boolean state variable. The liked state variable holds a boolean. When you click the input, the set function updates the state with whether the checkbox is checked:
```js
import { useState } from 'react';
export default function MyCheckbox() {
const [liked, setLiked] = useState(true);
function handleChange(e) {
setLiked(e.target.checked);
}
return (
<>
<label>
<input
type="checkbox"
checked={liked}
onChange={handleChange}
/>
I liked this
</label>
<p>You {liked ? 'liked' : 'did not like'} this.</p>
</>
);
}
```
useState example - multiple state variables in same component
Example showing multiple useState calls in the same component. You can declare more than one state variable in the same component. Each state variable is completely independent:
```js
import { useState } from 'react';
export default function Form() {
const [name, setName] = useState('Taylor');
const [age, setAge] = useState(42);
return (
<>
<input
value={name}
onChange={e => setName(e.target.value)}
/>
<button onClick={() => setAge(age + 1)}>
Increment age
</button>
<p>Hello, {name}. You are {age}.</p>
</>
);
}
```
useState updater function pattern for multiple updates
When you need to make multiple updates based on the previous state in a single handler, use an updater function instead of passing the next state directly. This ensures that each update uses the result of the previous update. For example, if age is 42, calling setAge(age + 1) three times will only increment to 43, but calling setAge(a => a + 1) three times will increment to 45.
useState updater function example
Example showing the updater function pattern:
```js
function handleClick() {
setAge(a => a + 1); // setAge(42 => 43)
setAge(a => a + 1); // setAge(43 => 44)
setAge(a => a + 1); // setAge(44 => 45)
}
```
Here, `a => a + 1` is your updater function. It takes the pending state and calculates the next state from it. React puts your updater functions in a queue. Then, during the next render, it will call them in the same order.