useCallback parameters: dependencies
The dependencies parameter is a list of all reactive values referenced inside the fn code. Reactive values include props, state, and variables/functions declared directly inside the component body. The list must have a constant number of items written inline like [dep1, dep2, dep3]. React compares each dependency with its previous value using Object.is comparison. If linting is configured for React, it will verify every reactive value is correctly specified as a dependency.
useCallback parameters: fn
The fn parameter is the function value you want to cache. It can take any arguments and return any values. React returns (not calls) your function during the initial render. On subsequent renders, React either returns the same function if dependencies haven't changed, or returns the new function you passed. The function is returned so you can decide when and whether to call it.
useCallback cannot be called in loops
You cannot call useCallback in a loop like inside items.map(). Hooks can only be called at the top level. If you need a cached callback for each item in a list, extract a separate component for each item and put useCallback at the top level of that component. Alternatively, wrap the child component in memo and remove useCallback.
Troubleshooting: useCallback returns different function every render
If useCallback returns a different function every time your component renders, the most common cause is forgetting to specify the dependency array as a second argument. Without the dependency array, useCallback will return a new function every time. Additionally, if at least one of your dependencies is different from the previous render, useCallback will return a new function. You can debug by logging dependencies to console and using Object.is() to check if they're the same between renders.
useCallback in custom Hooks
If you're writing a custom Hook, it's recommended to wrap any functions that it returns into useCallback. This ensures that the consumers of your Hook can optimize their own code when needed.
useCallback vs useMemo difference
useCallback and useMemo are both useful for optimizing child components. useMemo caches the result of calling your function. It calls the function you pass and caches the result. useCallback caches the function itself, not the result. It does not call the function you provide; it caches the function so the function itself doesn't change unless dependencies change. You can think of useCallback as: function useCallback(fn, dependencies) { return useMemo(() => fn, dependencies); }
When useCallback is valuable
useCallback is only valuable in a few cases: (1) You pass it as a prop to a component wrapped in memo and want to skip re-rendering if the value hasn't changed. Memoization lets your component re-render only if dependencies changed. (2) The function you're passing is later used as a dependency of some Hook, such as another function wrapped in useCallback or a useEffect dependency.
useCallback and updater functions to remove dependencies
When you need to update state based on previous state in a memoized callback, you can use an updater function to remove the dependency on the state variable. Instead of setTodos([...todos, newTodo]) with [todos] as a dependency, use setTodos(todos => [...todos, newTodo]) with an empty dependency array. This avoids making state a dependency by passing an instruction about how to update state to React.
useDebugValue without formatting function
If you don't specify a formatting function as the second parameter, the original value itself will be displayed in React DevTools.
useDebugValue hook signature
useDebugValue is a React Hook that adds a label to a custom Hook in React DevTools. The signature is useDebugValue(value, format?) where value is the value to display (any type) and format is an optional formatting function.
useDebugValue parameters
useDebugValue takes two parameters: (1) value (required) - the value you want to display in React DevTools, can have any type; (2) format (optional) - a formatting function that React DevTools will call with the value as argument, and will display the returned formatted value.
useDebugValue return value
useDebugValue does not return anything.
useDebugValue must be called at top level
useDebugValue must be called at the top level of a custom Hook to display a readable debug value.
useDebugValue formatting function defers computation
The formatting function passed to useDebugValue is only called when the component is inspected in React DevTools, allowing you to avoid running potentially expensive formatting logic on every render.
useDebugValue example with custom Hook
Example showing useDebugValue in a custom Hook: import { useSyncExternalStore, useDebugValue } from 'react'; export function useOnlineStatus() { const isOnline = useSyncExternalStore(subscribe, () => navigator.onLine, () => true); useDebugValue(isOnline ? 'Online' : 'Offline'); return isOnline; } function subscribe(callback) { window.addEventListener('online', callback); window.addEventListener('offline', callback); return () => { window.removeEventListener('online', callback); window.removeEventListener('offline', callback); }; }
useDebugValue example with formatting function
Example of useDebugValue with a formatting function: useDebugValue(date, date => date.toDateString()). The formatting function receives the debug value as a parameter and returns the formatted display value.
useDebugValue best practice
Don't add debug values to every custom Hook. It's most valuable for custom Hooks that are part of shared libraries and that have a complex internal data structure that's difficult to inspect.
useContext pitfall: missing provider in tree
If a component cannot see the value from its provider, check: (1) The provider is rendered above and outside the component calling useContext, not in the same component or below it. (2) The component is wrapped with the provider. (3) There are no build tooling issues causing the context objects to be different instances.
useContext pitfall: undefined from context without value prop
If you get undefined from context even though a default value is set, check if the provider has a value prop. Rendering <SomeContext> without a value prop is like passing value={undefined}, which overrides the default value. Always include value={theme} when providing context.
useContext pitfall: wrong prop name on provider
Make sure the context provider uses the prop name value, not theme or other custom names. For example, <ThemeContext value={theme}> is correct, but <ThemeContext theme={theme}> will not pass the value and you will get undefined.
useContext default value only used when no provider exists
The default value from createContext(defaultValue) is only used if there is no matching provider above the component at all. If there is a <SomeContext value={undefined}> provider in the parent tree, the component will receive undefined as the context value, not the default value.
useContext context identity must be exact
For useContext to work correctly, the context object used to provide context must be the exact same object as the context used to read it, as determined by === comparison. If build systems produce duplicate modules (such as with symlinks), this can break context.
useContext example: passing string value through context
import { createContext, useContext } from 'react';
const ThemeContext = createContext(null);
export default function MyApp() {
return (
<ThemeContext value="dark">
<Form />
</ThemeContext>
)
}
function Form() {
return (
<Panel title="Welcome">
<Button>Sign up</Button>
<Button>Log in</Button>
</Panel>
);
}
function Panel({ title, children }) {
const theme = useContext(ThemeContext);
const className = 'panel-' + theme;
return (
<section className={className}>
<h1>{title}</h1>
{children}
</section>
)
}
function Button({ children }) {
const theme = useContext(ThemeContext);
const className = 'button-' + theme;
return (
<button className={className}>
{children}
</button>
);
}
useContext with state to update context values
To update context over time, combine useContext with useState. Declare a state variable in the parent component and pass the current state as the context value to the provider. When state updates, all components reading that context will re-render with the new value.
useContext example: updating context with state
function MyPage() {
const [theme, setTheme] = useState('dark');
return (
<ThemeContext value={theme}>
<Form />
<Button onClick={() => {
setTheme('light');
}}>
Switch to light theme
</Button>
</ThemeContext>
);
}
useContext example: passing object with state setter
import { createContext, useContext, useState } from 'react';
const CurrentUserContext = createContext(null);
export default function MyApp() {
const [currentUser, setCurrentUser] = useState(null);
return (
<CurrentUserContext
value={{
currentUser,
setCurrentUser
}}
>
<Form />
</CurrentUserContext>
);
}
function LoginButton() {
const {
currentUser,
setCurrentUser
} = useContext(CurrentUserContext);
if (currentUser !== null) {
return <p>You logged in as {currentUser.name}.</p>;
}
return (
<Button onClick={() => {
setCurrentUser({ name: 'Advika' })
}}>Log in as Advika</Button>
);
}
useContext with multiple independent contexts
You can use multiple independent contexts in the same component tree. Each context is read separately with useContext. For example, one context can provide theme while another provides current user information.
useContext with extracted provider component
As apps grow, you can extract providers into a single component to hide nesting complexity. Create a component like MyProviders that wraps multiple context providers and renders children inside them. This allows cleaner component hierarchies while maintaining all context functionality.
useContext with reducer for complex state
Combine useContext with useReducer for larger apps. Create separate contexts for state values and dispatch functions. Export custom hooks that call useContext for each context. This pattern helps extract state logic out of components and makes it testable and reusable.
useContext default value fallback behavior
If React cannot find any providers of a particular context in the parent tree, useContext returns the defaultValue specified when the context was created with createContext. The default value never changes and provides a fallback value.
useContext meaningful default values
Instead of using null as the default value for context, consider using a more meaningful default value. For example, createContext('light') for a theme context. This helps components work without providers and prevents errors in test environments.
useContext overriding context for part of tree
You can override context for a part of the tree by wrapping that part in a provider with a different value. Nest and override providers as many times as needed. Inner providers override outer providers for their subtree.
useContext example: overriding theme for footer
function Form() {
return (
<Panel title="Welcome">
<Button>Sign up</Button>
<Button>Log in</Button>
<ThemeContext value="light">
<Footer />
</ThemeContext>
</Panel>
);
}
function Footer() {
return (
<footer>
<Button>Settings</Button>
</footer>
);
}
React automatically re-renders with context value changes
React automatically re-renders all children that use a particular context starting from the provider when the provider receives a different value. The previous and next values are compared with Object.is comparison. Using memo does not prevent children from receiving fresh context values.
useContext context must be above the calling component
A useContext() call in a component is not affected by providers returned from the same component. The corresponding Context provider needs to be above the component doing the useContext() call in the component tree.
useContext returns context value from closest provider
useContext returns the context value determined by the value prop passed to the closest SomeContext provider above the calling component in the tree. If no such provider exists, it returns the defaultValue passed to createContext for that context.
useContext example: automatically nested headings with context
import Heading from './Heading.js';
import Section from './Section.js';
export default function Page() {
return (
<Section>
<Heading>Title</Heading>
<Section>
<Heading>Heading</Heading>
<Heading>Heading</Heading>
<Heading>Heading</Heading>
<Section>
<Heading>Sub-heading</Heading>
<Heading>Sub-heading</Heading>
<Heading>Sub-heading</Heading>
<Section>
<Heading>Sub-sub-heading</Heading>
<Heading>Sub-sub-heading</Heading>
<Heading>Sub-sub-heading</Heading>
</Section>
</Section>
</Section>
</Section>
);
}
// In Section.js
import { useContext } from 'react';
import { LevelContext } from './LevelContext.js';
export default function Section({ children }) {
const level = useContext(LevelContext);
return (
<section className="section">
<LevelContext value={level + 1}>
{children}
</LevelContext>
</section>
);
}
// In Heading.js
import { useContext } from 'react';
import { LevelContext } from './LevelContext.js';
export default function Heading({ children }) {
const level = useContext(LevelContext);
switch (level) {
case 0:
throw Error('Heading must be inside a Section!');
case 1:
return <h1>{children}</h1>;
case 2:
return <h2>{children}</h2>;
case 3:
return <h3>{children}</h3>;
case 4:
return <h4>{children}</h4>;
case 5:
return <h5>{children}</h5>;
case 6:
return <h6>{children}</h6>;
default:
throw Error('Unknown level: ' + level);
}
}
// In LevelContext.js
import { createContext } from 'react';
export const LevelContext = createContext(0);
useContext optimize re-renders with useMemo for objects
When passing objects or functions via context, optimize re-renders by wrapping the object creation in useMemo. This prevents unnecessary re-renders of child components when the context value hasn't actually changed, only the parent component re-rendered.
useContext optimize functions with useCallback
When passing functions via context, wrap them with useCallback to prevent unnecessary re-renders of child components. useCallback memoizes the function so it only changes when its dependencies change.
useContext optimization example with useMemo and useCallback
import { useCallback, useMemo } from 'react';
function MyApp() {
const [currentUser, setCurrentUser] = useState(null);
const login = useCallback((response) => {
storeCredentials(response.credentials);
setCurrentUser(response.user);
}, []);
const contextValue = useMemo(() => ({
currentUser,
login
}), [currentUser, login]);
return (
<AuthContext value={contextValue}>
<Page />
</AuthContext>
);
}
useContext signature and basic usage
useContext is called with a single parameter SomeContext, which is a context created with createContext. It returns the context value for the calling component. Call useContext at the top level of your component. Example: const theme = useContext(ThemeContext);
Fixing useEffect re-running with changing dependencies
When a dependency changes on every re-render, fix it by: updating state based on previous state from an Effect, removing unnecessary object dependencies, removing unnecessary function dependencies, reading the latest props and state from an Effect, or as a last resort wrapping creation with useMemo or useCallback (for functions).
useEffect runs after every re-render when no dependency array
If useEffect is called without a dependency array, the effect re-runs after every commit. This is indicated by omitting the third argument entirely to useEffect.
useEffect re-running loop due to dependency changes
If useEffect still re-runs in a loop despite having a dependency array specified, it is because one of the dependencies is different on every re-render. This can be debugged by logging dependencies to the console and using Object.is() to compare specific dependencies between re-renders.
Debugging useEffect dependency changes with browser console
To debug why useEffect runs in a loop: log the dependency array to console, right-click the arrays from different re-renders, select 'Store as a global variable' for both, then use Object.is(temp1[index], temp2[index]) to check if each dependency changed between re-renders.
useEffect infinite cycle condition
An Effect runs in an infinite cycle when it updates some state, and that state leads to a re-render, which causes the Effect's dependencies to change.
useEffect infinite cycle debugging approach
Before fixing an infinite loop, determine if the Effect is connecting to an external system (DOM, network, third-party widget). If not, consider removing the Effect. If synchronizing with an external system, verify the Effect updates state at the right time and not more than needed. If the state update causes dependencies to change, debug the dependency changes.
useEffect cleanup runs before re-renders with changed dependencies
The cleanup function runs not only during unmount, but also before every re-render when dependencies have changed. Additionally, in development, React runs setup+cleanup one extra time immediately after component mounts.
useEffect cleanup without setup is a code smell
Cleanup logic without corresponding setup logic indicates a code smell. Cleanup logic should be symmetrical to the setup logic and should stop or undo whatever the setup did.
useEffect visual flicker should use useLayoutEffect
If an Effect does something visual and causes a flicker before it runs, the Effect must block the browser from painting the screen. In this case, replace useEffect with useLayoutEffect. This should only be needed if it is crucial to run the Effect before browser paint, such as measuring and positioning a tooltip before the user sees it.
useEffect cleanup logic symmetry example
Example of symmetrical setup and cleanup: useEffect(() => { const connection = createConnection(serverUrl, roomId); connection.connect(); return () => { connection.disconnect(); }; }, [serverUrl, roomId]); The cleanup disconnects what the setup connected.
useEffectEvent hook signature and basic usage
useEffectEvent is a React Hook that lets you separate events from Effects. It is called at the top level of a component with a callback function and returns an Effect Event function. The signature is: const onEvent = useEffectEvent(callback). The returned Effect Event function has the same type signature as the callback passed in.
useEffectEvent error: calling during rendering
If you get an error 'A function wrapped in useEffectEvent can't be called during rendering', it means you're calling an Effect Event function during the render phase of your component. Effect Events can only be called from inside Effects or other Effect Events, not during render. Move the call into an Effect or call the logic directly without wrapping in useEffectEvent.
useEffectEvent lint error: function in dependency array
If you see a lint warning that 'Functions returned from useEffectEvent must not be included in the dependency array', remove the Effect Event from your dependencies. Effect Events are designed to be called from Effects without being listed as dependencies. Including them would cause your Effect to re-run on every render because the function identity is intentionally not stable.
useEffectEvent lint error: calling from wrong place
If you see a lint warning that a function 'is a function created with useEffectEvent, and can only be called from Effects', you're calling it from the wrong place such as an event handler or passing it to a child component. Effect Events are specifically designed to be used in Effects local to the component they're defined in. For event handlers or to pass to children, use a regular function or useCallback instead.
useEffectEvent callback parameters and return values
The callback passed to useEffectEvent can accept any number of arguments and return any value. When the returned Effect Event function is called, the callback always accesses the latest committed values from render (like props and state) at the time of the call.
useEffectEvent can only be called from Effects or other Effect Events
The returned Effect Event function can only be called from inside useEffect, useLayoutEffect, useInsertionEffect, or from within other Effect Events in the same component. It cannot be called during rendering or passed to other components or Hooks.
useEffectEvent must be called at the top level of component
useEffectEvent 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 Effect Event into it.
useEffectEvent should not be used to skip dependencies
Do not use useEffectEvent to avoid specifying dependencies in your Effect's dependency array, as this hides bugs and makes code harder to understand. Only use useEffectEvent for logic that is genuinely an event fired from Effects.
useEffectEvent functions do not have stable identity
Effect Event functions do not have a stable identity. Their identity intentionally changes on every render. This is a deliberate design choice because Effect Events are meant to be called only from within Effects in the same component and cannot be passed to other components or included in dependency arrays, so a stable identity would serve no purpose and would actually mask bugs.