Deeper Server Component Promise resolution
// Server Component
export default function App() {
const messagePromise = fetchMessage();
return (
<Suspense fallback={<p>⌛Downloading message...</p>}>
<Message messagePromise={messagePromise} />
</Suspense>
);
}
// Server Component
async function Message({ messagePromise }) {
const messageContent = await messagePromise;
return <p>{messageContent}</p>;
}
Pitfall: use cannot be wrapped in try-catch
`use` throws internally to integrate with Suspense, so it cannot be wrapped in try-catch. Instead, wrap the component that calls `use` in an Error Boundary to handle errors.
Error: Suspense Exception uncached promise warning
If you receive a warning 'A component was suspended by an uncached promise', the Promise passed to `use` is not cached and React cannot reuse it across re-renders. This commonly happens when calling `fetch` or an `async` function directly in render. To fix this, cache the Promise so the same instance is reused, typically using a function like `fetchData` that returns a cached Promise.
When to use use vs await for Promises
Use `await` in Server Components to resolve Promises during render. Use `use` in Client Components to read Promise values since Client Components cannot `await` during render. Both suspend where the Promise is read and unblock the UI above. A common case for Client Component `use` is interactive content like popovers and tooltips where data is only needed after a hover or click.
Server Component Promise resolution example
// Server Component
export default async function App() {
const messageContent = await fetchMessage();
return <Message messageContent={messageContent} />;
}
Error handling with Error Boundary and use
If the Promise passed to `use` is rejected, the error propagates to the nearest Error Boundary. Wrap the component that calls `use` in an Error Boundary to display a fallback when the Promise is rejected. `use` cannot be caught in try-catch blocks because it throws internally to integrate with Suspense.
use API signature and overview
The `use` React API lets you read the value of a Promise or context. The signature is `const value = use(resource)`. Unlike the `useContext` hook, `use` can be called within loops and conditional statements like `if`. Despite its name, `use` is not a Hook when used with Promises.
use(context) - reading context values
When called with a context created by `createContext`, `use(context)` returns the context value determined by the closest context provider above the calling component. If no provider exists, it returns the `defaultValue` passed to `createContext`. Unlike `useContext`, `use` can be called inside conditionals and loops.
use(context) caveats
`use` must be called inside a Component or a Hook. Reading context with `use` is not supported in Server Components.
use(promise) - reading Promise resolved values
When called with a Promise, `use(promise)` returns the resolved value of the Promise. The component calling `use` suspends while the Promise is pending. The component must be wrapped in a Suspense boundary to display a fallback while the Promise is pending. If the Promise is rejected, the fallback of the nearest Error Boundary will be displayed.
use(promise) parameter requirements
The Promise passed to `use` must be cached so that the same Promise instance is reused across re-renders. If a new Promise is created on every render, React will repeatedly show the Suspense fallback and prevent content from appearing.
use(promise) caveats
`use` must be called inside a Component or a Hook. `use` cannot be called inside a try-catch block; instead wrap the component in an Error Boundary to catch errors. Promises passed to `use` must be cached for reuse across re-renders. When passing a Promise from a Server Component to a Client Component, its resolved value must be serializable.
use with context example
import { use } from 'react';
function Button() {
const theme = use(ThemeContext);
// ...
}
function HorizontalRule({ show }) {
if (show) {
const theme = use(ThemeContext);
return <hr className={theme} />;
}
return false;
}
use with Promise example
import { use } from 'react';
function Albums({ albumsPromise }) {
const albums = use(albumsPromise);
return (
<ul>
{albums.map(album => (
<li key={album.id}>
{album.title} ({album.year})
</li>
))}
</ul>
);
}
Reading Promise from context with use
To share asynchronous data without prop drilling, set a Promise as a context value, then read it with `use(context)` and resolve it with `use(promise)`. This requires two `use` calls: the first retrieves the Promise from context, the second unwraps the Promise's resolved value. Components that read the Promise should be wrapped in a Suspense boundary.
Reading Promise from context example
import { use } from 'react';
import { UserContext } from './UserContext';
function Profile() {
const userPromise = use(UserContext);
const user = use(userPromise);
return <h1>{user.name}</h1>;
}
Promise caching pattern for Client Components
A basic Promise cache stores the Promise keyed by URL so the same instance is reused across renders. When implementing a promise cache, set `status` and `value` (or `reason`) fields on the Promise. React checks these fields when `use` is called: if `status` is 'fulfilled', it reads `value` synchronously without suspending. If `status` is 'rejected', it throws `reason`. If the field is missing or 'pending', it suspends. Example cache implementation:
let cache = new Map();
export function fetchData(url) {
if (!cache.has(url)) {
cache.set(url, getData(url));
}
return cache.get(url);
}
Promise cache with status tracking
let cache = new Map();
function fetchData(url) {
if (!cache.has(url)) {
const promise = getData(url);
promise.status = 'pending';
promise.then(
value => {
promise.status = 'fulfilled';
promise.value = value;
},
reason => {
promise.status = 'rejected';
promise.reason = reason;
},
);
cache.set(url, promise);
}
return cache.get(url);
}
Why Promises are recreated on every render
React does not preserve state for renders that suspended before mounting. After each suspension, React retries rendering from scratch, so any Promise created during render is recreated. This means calling `fetch` directly in render or creating an `async` function inside render will create a new Promise on every render, causing React to repeatedly show the Suspense fallback.
Pitfall: Don't bypass use by reading promise.status directly
Never read `promise.status` or `promise.value` directly to bypass `use`. Always pass the Promise to `use` and let React handle it. Bypassing `use` this way breaks React Suspense optimizations and Suspense features for React DevTools. While `use(promise)` can be called conditionally, never conditionally skip calling `use` based on the promise itself.
Pitfall: Context search direction
Like `useContext`, `use(context)` always looks for the closest context provider above the component that calls it. It searches upwards and does not consider context providers in the component from which you're calling `use(context)`.
useActionState with useOptimistic example
You can combine useActionState with useOptimistic to show immediate UI feedback. Call useOptimistic with the current state from useActionState, and in the same startTransition call, update the optimistic state and then call dispatchAction. This provides immediate visual feedback while the Action is still pending.
useActionState hook signature
useActionState is called at the top level of a component and returns an array with three values: const [state, dispatchAction, isPending] = useActionState(reducerAction, initialState, permalink?). The reducerAction is a function that receives the previous state and actionPayload and returns the new state. initialState is the initial value for state. The optional permalink parameter is a string containing the unique page URL that this form modifies, used for Server Components with progressive enhancement.
useActionState returns three values
useActionState returns an array with exactly three values: (1) The current state, initially matching initialState, then matching the value returned by reducerAction after dispatchAction is invoked. (2) A dispatchAction function used to trigger the reducerAction, called inside Actions or wrapped in startTransition. (3) The isPending flag indicating whether any dispatched Actions for this Hook are pending.
useActionState call requirements
useActionState 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.
useActionState queuing behavior
React queues and executes multiple calls to dispatchAction sequentially. Each call to reducerAction receives the result of the previous call as its previousState argument. If dispatchAction is called multiple times, React queues them and waits for the previous Action to finish before calling the next Action.
dispatchAction function identity and dependencies
The dispatchAction 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.
permalink option with Server Components
When using the permalink option, ensure the same form component is rendered on the destination page including the same reducerAction and permalink so React knows how to pass the state through. Once the page becomes interactive, this parameter has no effect. For Server Functions, if the form is submitted before the JavaScript bundle loads, the browser will navigate to the specified permalink URL rather than the current page's URL.
useActionState with serializable values
When using Server Functions, initialState needs to be serializable (values like plain objects, arrays, strings, and numbers). Similarly, actionPayload needs to be serializable.
useActionState error handling with Error Boundary
If dispatchAction throws an error, React cancels all queued actions and shows the nearest Error Boundary by rethrowing the error from the useActionState hook.
useActionState batching multiple actions
If there are multiple ongoing Actions, React batches them together. This is a limitation that may be removed in a future release.
dispatchAction must be called from an Action
dispatchAction must be called from an Action. You can wrap it in startTransition, or pass it to an Action prop. Calls outside that scope will not be treated as part of the Transition and will log an error in development mode.
reducerAction function can be async and perform side effects
Unlike reducers in useReducer, the reducerAction function passed to useActionState can be async and perform side effects. It receives the previous state as its first argument and actionPayload as its second argument, and returns the new state. Each time dispatchAction is called, React calls reducerAction with the actionPayload.
reducerAction parameters
reducerAction receives two parameters: (1) previousState, which initially equals initialState and after the first call to dispatchAction equals the last state returned. (2) optional actionPayload, the argument passed to dispatchAction, which can be any type but is typically an object with a type property identifying it.
reducerAction return value and triggers transition
reducerAction returns the new state and triggers a Transition to re-render with that state. The return type must match the type of initialState.
reducerAction is not invoked twice in StrictMode
reducerAction is not invoked twice in StrictMode since reducerAction is designed to allow side effects.
reducerAction state update after await requires startTransition
If you set state after await in the reducerAction you currently need to wrap the state update in an additional startTransition.
Why called reducerAction
The function passed to useActionState is called a reducer action because: (1) It reduces the previous state into a new state, like useReducer. (2) It is an Action because it is called inside a Transition and can perform side effects. Conceptually, useActionState is like useReducer, but you can do side effects in the reducer.
useActionState vs useReducer
useReducer is for managing state of your UI where the reducer must be pure. useActionState is for managing state of your Actions where the reducer can perform side effects. useActionState has to order calls sequentially because it computes the next Action to take based on the previous Action. If you want to perform Actions in parallel, use useState and useTransition directly.
useActionState with Action props
When you pass dispatchAction to a component that exposes an Action prop, you do not need to call startTransition or useOptimistic yourself. The component will handle the Transition and optimistic updates.
useActionState with AbortController for cancellation
You can use an AbortController to cancel pending Actions. Create an AbortController, pass its signal in the actionPayload to dispatchAction, and check the signal in the reducerAction. When a new Action is dispatched, abort the previous controller and create a new one. Aborting an Action is only safe when you know the side effect can be safely ignored or retried.
Pitfall: Aborting Actions may not be safe
Aborting an Action is not always safe. For example, if the Action performs a mutation like writing to a database, aborting the network request does not undo the server-side change. This is why useActionState does not abort by default. It is only safe when you know the side effect can be safely ignored or retried.
useActionState with form action prop
You can pass dispatchAction as the action prop to a form. When used this way, React automatically wraps the submission in a Transition, so you do not need to call startTransition yourself. The reducerAction receives the previous state and the submitted FormData as its second argument.
Form with useActionState example
When using useActionState with a form action prop, the reducerAction receives FormData as its second parameter. You can call formData.get('name') to access form field values. React automatically wraps the form submission in a Transition.
useActionState Server Component progressive enhancement
When used with Server Functions, useActionState allows the server's response to be shown before hydration completes. You can also use the optional permalink parameter for progressive enhancement, allowing the form to work before JavaScript loads on pages with dynamic content. This is typically handled by your framework.
useActionState error handling approaches
There are two ways to handle errors with useActionState: (1) For known errors like validation errors, return it as part of your reducerAction state and display it in the UI. (2) For unknown errors, you can throw an error, and React will cancel all queued Actions and show the nearest Error Boundary by rethrowing the error.
isPending flag not updating troubleshooting
If isPending flag is not updating when calling dispatchAction manually, make sure you wrap the call in startTransition. When dispatchAction is passed to an Action prop, React automatically wraps it in a Transition.
useActionState Action cannot read form data
When you use useActionState, the reducerAction receives an extra argument as its first argument: the previous or initial state. The submitted form data is its second argument. So the function signature is function action(prevState, formData) not function action(formData).
useActionState actions being skipped
If you call dispatchAction multiple times and some do not run, it may be because an earlier dispatchAction call threw an error. When reducerAction throws, React skips all subsequently queued dispatchAction calls. To handle this, catch errors within reducerAction and return an error state instead of throwing.
useActionState does not provide built-in reset
useActionState does not provide a built-in reset function. To reset the state, you can design your reducerAction to handle a reset signal, such as checking if payload is null. Alternatively, you can add a key prop to the component using useActionState to force it to remount with fresh state.
useActionState error: async function outside transition
The error 'An async function with useActionState was called outside of a transition' happens when dispatchAction must run inside a Transition. To fix, either wrap the call in startTransition or pass dispatchAction to an Action prop where React automatically wraps it in a Transition.
useActionState error: Cannot update during render
You cannot call dispatchAction during render as this causes an infinite loop. Only call dispatchAction in response to user events like form submissions or button clicks.
useCallback to prevent Effects from firing too often
useCallback can be used to prevent an Effect from firing too often. When you call a function inside an Effect, that function must be declared as a dependency. If the function is created inline in the component body, it will be a different function every render, causing the Effect to re-run constantly. By wrapping the function in useCallback with stable dependencies, the function remains the same between renders, and the Effect only re-runs when necessary.
useCallback signature and basic usage
useCallback is called at the top level of a component with two parameters: a function to cache and a dependencies array. It returns either the same function from the previous render (if dependencies are unchanged) or the new function passed in the current render. Signature: const cachedFn = useCallback(fn, dependencies)
useCallback with memo for skipping re-renders
useCallback is useful when you pass a function as a prop to a component wrapped in memo. Without useCallback, a new function is created on every render, and since JavaScript creates a different function each time (even with identical code), the memoized child component will receive different props and re-render unnecessarily. By wrapping the function in useCallback, you ensure it's the same function between re-renders (until dependencies change), allowing the memoized child to skip re-rendering.
useCallback is only a performance optimization
You should only rely on useCallback as a performance optimization. If your code doesn't work without it, find the underlying problem and fix it first. Then you may add useCallback back. In many cases, a state variable or a ref may be more appropriate.
useCallback creates a new function, not prevents it
useCallback does not prevent creating the function. You always create a function (and that's fine), but React ignores it and gives you back a cached function if nothing changed.
useCallback cache invalidation during development
React does not throw away the cached function unless there is a specific reason. In development, React throws away the cache when you edit the file of your component. Both in development and in production, React will throw away the cache if your component suspends during the initial mount. In the future, React may add more features that take advantage of cache invalidation.
useCallback must be called at component top level
useCallback 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.
useCallback returns the cached or new function
On the initial render, useCallback returns the fn function you passed. During subsequent renders, it returns either an already stored fn function from the last render (if dependencies haven't changed), or the fn function you passed during the current render.