Effect Event identity changes cause Effect to re-run if included in dependencies
If you incorrectly depend on an Effect Event function's identity by including it in a dependency array, the Effect will re-run on every render because the function identity changes each render. This non-stable identity acts as a runtime assertion to make the bug obvious.
useEffectEvent example: using an event in an Effect
Example showing how to create an Effect Event that can access latest values without re-running the Effect: const onConnected = useEffectEvent(() => { if (!muted) { showNotification('Connected!'); } }); useEffect(() => { const connection = createConnection(roomId); connection.on('connected', onConnected); connection.connect(); return () => { connection.disconnect(); } }, [roomId]);
useEffectEvent with setInterval to read latest values
useEffectEvent is useful with setInterval or setTimeout in Effects when you want to read the latest values from render without restarting the timer whenever those values change. The Effect Event always reads the latest values without causing the interval to restart.
useEffectEvent with event listeners to avoid re-setup
When setting up event listeners in an Effect, useEffectEvent lets you read the latest values from render in the callback without needing to include those values in dependencies, which would cause the listener to be removed and re-added on every change.
useEffectEvent example: cursor follower with event listener
Example of using useEffectEvent with event listeners: const onMove = useEffectEvent(e => { if (canMove) { setPosition({ x: e.clientX, y: e.clientY }); } }); useEffect(() => { window.addEventListener('pointermove', onMove); return () => window.removeEventListener('pointermove', onMove); }, []);
useEffectEvent to avoid reconnecting external systems
useEffectEvent is useful when you want to do something in response to an Effect, but that action depends on a value you don't want to react to. For example, in a chat component connecting to a room, you can use an Effect Event to show a notification based on a muted state without reconnecting to the chat room every time the muted setting changes.
useEffectEvent in custom Hooks
You can use useEffectEvent inside custom Hooks to create reusable Hooks that encapsulate Effects while keeping some values non-reactive. This allows custom Hooks to wrap callbacks in Effect Events so that the Effect does not reset even if a new callback is passed in every render.
Use Strict Mode and ESLint plugin to enforce Rules of React
React strongly recommends using Strict Mode alongside the ESLint plugin (eslint-plugin-react-hooks) to help your codebase follow the Rules of React and find bugs early.
Props and state are immutable
A component's props and state are immutable snapshots with respect to a single render. You must never mutate props or state directly.
Values are immutable after being passed to JSX
You must not mutate values after they have been used in JSX. Move any mutations before the JSX is created.
Components must be idempotent
React components are assumed to always return the same output with respect to their inputs - props, state, and context. This is a requirement for writing idiomatic React code.
Example: Don't mutate props, create a copy instead
// Bad:
function Post({ item }) {
item.url = new Url(item.url, base);
return <Link url={item.url}>{item.title}</Link>;
}
// Good:
function Post({ item }) {
const url = new Url(item.url, base);
return <Link url={url}>{item.title}</Link>;
}
Instead of mutating the prop item.url directly, create a new variable url and use that.
Example: Don't mutate values used in JSX, create new values instead
// Bad:
function Page({ colour }) {
const styles = { colour, size: "large" };
const header = <Header styles={styles} />;
styles.size = "small";
const footer = <Footer styles={styles} />;
return <>{header}<Content />{footer}</>;
}
// Good:
function Page({ colour }) {
const headerStyles = { colour, size: "large" };
const header = <Header styles={headerStyles} />;
const footerStyles = { colour, size: "small" };
const footer = <Footer styles={footerStyles} />;
return <>{header}<Content />{footer}</>;
}
Create separate value objects instead of mutating styles after it's used in JSX.
Why render purity matters in React
When render is kept pure, React can understand how to prioritize which updates are most important for the user to see first. This enables React to pause rendering components that aren't as important to update and resume them later when needed, providing a better user experience. If a component has untracked side effects during render, React running the rendering code multiple times will trigger those side effects in ways that don't match the intended behavior, leading to unexpected bugs.
React rendering phases explained
React operates in phases: (1) Rendering – calculating what the next version of the UI should look like. (2) Comparing – React compares the new calculation to the previous version. (3) Committing – React commits only the minimum changes needed to the DOM. (4) Effect flush – Effects are run until there are no more left. Only code in the rendering phase runs during render; event handlers and Effects run outside of it.
How to identify if code runs during render
Code that runs during render is typically written at the top level of the component function body. Event handlers and Effects do not run during render – they only run when the user triggers them or after rendering completes, respectively.
Components must be idempotent - no non-idempotent functions during render
Code that runs during render must be idempotent, meaning it always returns the same result with the same inputs. Functions like new Date() and Math.random() are not idempotent because they return different results every time they're called, even with the same inputs. These should not be called during render; instead, use an Effect to synchronize external state or an event handler for user interactions.
Example: Clock component with non-idempotent new Date() using useEffect
import { useState, useEffect } from 'react';
function useTime() {
const [time, setTime] = useState(() => new Date());
useEffect(() => {
const id = setInterval(() => {
setTime(new Date());
}, 1000);
return () => clearInterval(id);
}, []);
return time;
}
export default function Clock() {
const time = useTime();
return <span>{time.toLocaleString()}</span>;
}
This example shows how to move a non-idempotent function (new Date()) into an Effect so it runs outside of rendering.
Side effects must run outside of render
Side effects should never run during render because React can render components multiple times. A side effect is any code that has an observable effect other than returning a value to the caller. Side effects should be written inside event handlers or Effects, never during render.
Local mutation is allowed during render
Local mutation – changing the value of non-primitive values that are created within the component during render – is acceptable and common in React. For example, creating a local array and pushing items into it during render is fine. The key is that the mutation is local and the mutated value doesn't persist across renders, so the component still returns the same result when rendered with the same inputs.
Example: Local mutation with array during render
function FriendList({ friends }) {
const items = [];
for (let i = 0; i < friends.length; i++) {
const friend = friends[i];
items.push(
<Friend key={friend.id} friend={friend} />
);
}
return <section>{items}</section>;
}
This shows local mutation is acceptable because items is created and mutated only within the render phase and is not remembered between renders.
Non-local mutation is forbidden
Mutating values created outside of a component during render breaks purity rules. If a variable is created outside the component, it retains its state across renders, and mutating it during render causes the component to have observable side effects that affect its behavior on subsequent renders. This violates idempotency.
Lazy initialization is acceptable
Lazy initialization – calling functions to initialize values only when needed – is acceptable during render as long as it doesn't affect other components' rendering. For example, calling SuperCalculator.initializeIfNotReady() is fine if its effects are isolated.
Directly mutating the DOM is forbidden during render
Side effects that are directly visible to the user, such as changing document.title, are not allowed in the render logic of React components. Calling a component function should not by itself produce a change on the screen. Use Effects to synchronize the component with external state like document.title instead.
Props are immutable
Props are immutable snapshots that should never be mutated directly. Mutating props produces inconsistent output that can be hard to debug because behavior may vary depending on circumstances. Instead of mutating props, create a new value based on the prop and pass that down or use that new value within the component.
Values passed to JSX become immutable
Don't mutate values after they've been passed to JSX expressions. React may eagerly evaluate JSX before the component finishes rendering, so mutating values after they've been used in JSX can lead to outdated UIs because React won't know to update the component's output. Move mutations to before the JSX is created.
Components and Hooks must be pure - three requirements
A pure component or hook must meet three criteria: (1) Idempotent – always returns the same result with the same inputs (props, state, context for components; arguments for hooks). (2) Has no side effects in render – code with side effects must run separately from rendering, such as in event handlers or Effects. (3) Does not mutate non-local values – components and hooks must never modify values that aren't created locally during render.
Never call component functions directly
Components should only be used in JSX. Do not call them as regular functions. React must decide when your component function is called during rendering. Use components in JSX syntax like <ComponentName /> instead of calling them directly like ComponentName().
Example of correct component usage
function BlogPost() {
return <Layout><Article /></Layout>; // ✅ Good: Only use components in JSX
}
This example shows the correct way to use components in JSX syntax.
Example of incorrect component call
function BlogPost() {
return <Layout>{Article()}</Layout>; // 🔴 Bad: Never call them directly
}
This example shows calling a component function directly instead of using JSX syntax.
Benefits of letting React call components
Components become more than functions, allowing React to augment them with features like local state through Hooks tied to the component's identity in the tree. Component types participate in reconciliation, React can enhance user experience by allowing the browser to work between component calls, developers get better debugging tools, and React can skip re-rendering components that don't need updates.
React philosophy: same programming model across all platforms and environments
React's philosophy is to provide the same programming model across all platforms and environments. When possible, if a feature is introduced on the client, the goal is to make it also work on the server, and vice versa. This allows creating a single set of APIs that work no matter where the app runs, making it easier to upgrade to different environments later.
React Canary channel allows adopting new features before stable release
React Canaries provide an option to adopt individual new stable features as soon as their design is close to final, before they are released in a stable semver version. Canaries allow building in public with community help to finalize features, so users hear about new features sooner as they are being finalized rather than after completion.
React 19 is the next major version with breaking changes
After iteration of react@canary, the next version of React will be React 19, a major version. Asset Loading and Document Metadata may be breaking changes for some apps. React 19 also includes long-requested improvements requiring breaking changes such as support for Web Components.
React 18 release timeline
The projected React 18 release timeline includes: Library Alpha (available immediately), Public Beta (at least several months), Release Candidate (at least several weeks after Beta), and General Availability (at least several weeks after RC).
React 18 alpha releases on npm
React 18 alpha releases are regularly published to npm using the @alpha tag. These releases are built using the most recent commit to the main repository. When a feature or bugfix is merged, it appears in an alpha the following weekday. Alpha releases are not recommended for user-facing, production applications.
Concurrent rendering in React 18
React 18 adds a new opt-in mechanism called concurrent rendering that lets React prepare multiple versions of the UI at the same time. Concurrent rendering is only enabled for updates triggered by new features, enabling gradual adoption without an all-or-nothing mode.
React 18 new features: automatic batching, startTransition, streaming server renderer
React 18 includes out-of-the-box improvements like automatic batching, new APIs like startTransition, and a new streaming server renderer with built-in support for React.lazy.
React 18 upgrade is opt-in with gradual adoption strategy
Concurrency in React 18 is opt-in, so there are no significant out-of-the-box breaking changes to component behavior. Applications can upgrade to React 18 with minimal or no changes to application code, with effort comparable to a typical major React release.
React 18 release features and concurrent renderer
React 18 adds a long-awaited concurrent renderer and updates to Suspense without major breaking changes. Apps can upgrade to React 18 and gradually adopt concurrent features with effort on par with any other major release. There is no concurrent mode, only concurrent features.