@@ -273,11 +273,22 @@ export default function CatFriends() {
}
function setupCatList() {
- const catList = [];
- for (let i = 0; i < 10; i++) {
- catList.push("https://loremflickr.com/320/240/cat?lock=" + i);
+ const catCount = 10;
+ const catList = new Array(catCount)
+ for (let i = 0; i < catCount; i++) {
+ let imageUrl = '';
+ if (i < 5) {
+ imageUrl = "https://placecats.com/neo/320/240";
+ } else if (i < 8) {
+ imageUrl = "https://placecats.com/millie/320/240";
+ } else {
+ imageUrl = "https://placecats.com/bella/320/240";
+ }
+ catList[i] = {
+ id: i,
+ imageUrl,
+ };
}
-
return catList;
}
@@ -876,12 +887,30 @@ export default function CatFriends() {
);
}
-const catList = [];
-for (let i = 0; i < 10; i++) {
- catList.push({
+const catCount = 10;
+const catList = new Array(catCount);
+for (let i = 0; i < catCount; i++) {
+ const bucket = Math.floor(Math.random() * catCount) % 2;
+ let imageUrl = '';
+ switch (bucket) {
+ case 0: {
+ imageUrl = "https://placecats.com/neo/250/200";
+ break;
+ }
+ case 1: {
+ imageUrl = "https://placecats.com/millie/250/200";
+ break;
+ }
+ case 2:
+ default: {
+ imageUrl = "https://placecats.com/bella/250/200";
+ break;
+ }
+ }
+ catList[i] = {
id: i,
- imageUrl: 'https://loremflickr.com/250/200/cat?lock=' + i
- });
+ imageUrl,
+ };
}
```
@@ -961,7 +990,7 @@ export default function CatFriends() {
behavior: 'smooth',
block: 'nearest',
inline: 'center'
- });
+ });
}}>
Next
@@ -993,12 +1022,30 @@ export default function CatFriends() {
);
}
-const catList = [];
-for (let i = 0; i < 10; i++) {
- catList.push({
+const catCount = 10;
+const catList = new Array(catCount);
+for (let i = 0; i < catCount; i++) {
+ const bucket = Math.floor(Math.random() * catCount) % 2;
+ let imageUrl = '';
+ switch (bucket) {
+ case 0: {
+ imageUrl = "https://placecats.com/neo/250/200";
+ break;
+ }
+ case 1: {
+ imageUrl = "https://placecats.com/millie/250/200";
+ break;
+ }
+ case 2:
+ default: {
+ imageUrl = "https://placecats.com/bella/250/200";
+ break;
+ }
+ }
+ catList[i] = {
id: i,
- imageUrl: 'https://loremflickr.com/250/200/cat?lock=' + i
- });
+ imageUrl,
+ };
}
```
diff --git a/src/content/learn/preserving-and-resetting-state.md b/src/content/learn/preserving-and-resetting-state.md
index 11d398d23..041fae355 100644
--- a/src/content/learn/preserving-and-resetting-state.md
+++ b/src/content/learn/preserving-and-resetting-state.md
@@ -704,7 +704,7 @@ Here, the `MyTextField` component function is defined *inside* `MyComponent`:
-```js
+```js {expectedErrors: {'react-compiler': [7]}}
import { useState } from 'react';
export default function MyComponent() {
diff --git a/src/content/learn/react-compiler/installation.md b/src/content/learn/react-compiler/installation.md
index 3606c9c6d..a40b1f5af 100644
--- a/src/content/learn/react-compiler/installation.md
+++ b/src/content/learn/react-compiler/installation.md
@@ -176,16 +176,7 @@ Install the ESLint plugin:
npm install -D eslint-plugin-react-hooks@rc
-Then enable the compiler rule in your ESLint configuration:
-
-```js {3}
-// .eslintrc.js
-module.exports = {
- rules: {
- 'react-hooks/react-compiler': 'error',
- },
-};
-```
+If you haven't already configured eslint-plugin-react-hooks, follow the [installation instructions in the readme](https://github.com/facebook/react/blob/main/packages/eslint-plugin-react-hooks/README.md#installation). The compiler rule is enabled by default in the latest RC, so no additional configuration is needed.
The ESLint rule will:
- Identify violations of the [Rules of React](/reference/rules)
diff --git a/src/content/learn/react-compiler/introduction.md b/src/content/learn/react-compiler/introduction.md
index 440c66ab6..96fa8802a 100644
--- a/src/content/learn/react-compiler/introduction.md
+++ b/src/content/learn/react-compiler/introduction.md
@@ -28,7 +28,7 @@ React Compiler automatically optimizes your React application at build time. Rea
Without the compiler, you need to manually memoize components and values to optimize re-renders:
-```js
+```js {expectedErrors: {'react-compiler': [4]}}
import { useMemo, useCallback, memo } from 'react';
const ExpensiveComponent = memo(function ExpensiveComponent({ data, onClick }) {
@@ -50,6 +50,21 @@ const ExpensiveComponent = memo(function ExpensiveComponent({ data, onClick }) {
});
```
+
+
+
+This manual memoization has a subtle bug that breaks memoization:
+
+```js [[2, 1, "() => handleClick(item)"]]
+ handleClick(item)} />
+```
+
+Even though `handleClick` is wrapped in `useCallback`, the arrow function `() => handleClick(item)` creates a new function every time the component renders. This means that `Item` will always receive a new `onClick` prop, breaking memoization.
+
+React Compiler is able to optimize this correctly with or without the arrow function, ensuring that `Item` only re-renders when `props.onClick` changes.
+
+
+
### After React Compiler {/*after-react-compiler*/}
With React Compiler, you write the same code without manual memoization:
@@ -74,7 +89,7 @@ function ExpensiveComponent({ data, onClick }) {
_[See this example in the React Compiler Playground](https://playground.react.dev/#N4Igzg9grgTgxgUxALhAMygOzgFwJYSYAEAogB4AOCmYeAbggMIQC2Fh1OAFMEQCYBDHAIA0RQowA2eOAGsiAXwCURYAB1iROITA4iFGBERgwCPgBEhAogF4iCStVoMACoeO1MAcy6DhSgG4NDSItHT0ACwFMPkkmaTlbIi48HAQWFRsAPlUQ0PFMKRlZFLSWADo8PkC8hSDMPJgEHFhiLjzQgB4+eiyO-OADIwQTM0thcpYBClL02xz2zXz8zoBJMqJZBABPG2BU9Mq+BQKiuT2uTJyomLizkoOMk4B6PqX8pSUFfs7nnro3qEapgFCAFEA)_
-React Compiler automatically applies the equivalent optimizations, ensuring your app only re-renders when necessary.
+React Compiler automatically applies the optimal memoization, ensuring your app only re-renders when necessary.
#### What kind of memoization does React Compiler add? {/*what-kind-of-memoization-does-react-compiler-add*/}
@@ -154,7 +169,7 @@ Next.js users can enable the swc-invoked React Compiler by using [v15.3.1](https
## What should I do about useMemo, useCallback, and React.memo? {/*what-should-i-do-about-usememo-usecallback-and-reactmemo*/}
-If you are using React Compiler, [`useMemo`](/reference/react/useMemo), [`useCallback`](/reference/react/useCallback), and [`React.memo`](/reference/react/memo) can be removed. React Compiler adds automatic memoization more precisely and granularly than is possible with these hooks. If you choose to keep manual memoization, React Compiler will analyze them and determine if your manual memoization matches its automatically inferred memoization. If there isn't a match, the compiler will choose to bail out of optimizing that component.
+React Compiler adds automatic memoization more precisely and granularly than is possible with [`useMemo`](/reference/react/useMemo), [`useCallback`](/reference/react/useCallback), and [`React.memo`](/reference/react/memo). If you choose to keep manual memoization, React Compiler will analyze them and determine if your manual memoization matches its automatically inferred memoization. If there isn't a match, the compiler will choose to bail out of optimizing that component.
This is done out of caution as a common anti-pattern with manual memoization is using it for correctness. This means your app depends on specific values being memoized to work properly. For example, in order to prevent an infinite loop, you may have memoized some values to stop a `useEffect` call from firing. This breaks the Rules of React, but since it can potentially be dangerous for the compiler to automatically remove manual memoization, the compiler will just bail out instead. You should manually remove your handwritten memoization and verify that your app still works as expected.
diff --git a/src/content/learn/referencing-values-with-refs.md b/src/content/learn/referencing-values-with-refs.md
index fab6599f2..4386e2bdc 100644
--- a/src/content/learn/referencing-values-with-refs.md
+++ b/src/content/learn/referencing-values-with-refs.md
@@ -211,7 +211,7 @@ If you tried to implement this with a ref, React would never re-render the compo
-```js
+```js {expectedErrors: {'react-compiler': [13]}}
import { useRef } from 'react';
export default function Counter() {
@@ -313,7 +313,7 @@ Regular variables like `let timeoutID` don't "survive" between re-renders becaus
-```js
+```js {expectedErrors: {'react-compiler': [10]}}
import { useState } from 'react';
export default function Chat() {
@@ -418,7 +418,7 @@ This button is supposed to toggle between showing "On" and "Off". However, it al
-```js
+```js {expectedErrors: {'react-compiler': [10]}}
import { useRef } from 'react';
export default function Toggle() {
diff --git a/src/content/learn/removing-effect-dependencies.md b/src/content/learn/removing-effect-dependencies.md
index 9a848862a..fb98a0cd1 100644
--- a/src/content/learn/removing-effect-dependencies.md
+++ b/src/content/learn/removing-effect-dependencies.md
@@ -303,7 +303,7 @@ Suppressing the linter leads to very unintuitive bugs that are hard to find and
-```js
+```js {expectedErrors: {'react-compiler': [14]}}
import { useState, useEffect } from 'react';
export default function Timer() {
@@ -609,11 +609,13 @@ function ChatRoom({ roomId }) {
### Do you want to read a value without "reacting" to its changes? {/*do-you-want-to-read-a-value-without-reacting-to-its-changes*/}
-
+
+
+**The `useEffectEvent` API is currently only available in React’s Canary and Experimental channels.**
-This section describes an **experimental API that has not yet been released** in a stable version of React.
+[Learn more about React’s release channels here.](/community/versioning-policy#all-release-channels)
-
+
Suppose that you want to play a sound when the user receives a new message unless `isMuted` is `true`:
@@ -794,7 +796,7 @@ It is important to declare it as a dependency! This ensures, for example, that i
-```js
+```js {expectedErrors: {'react-compiler': [10]}}
import { useState, useEffect } from 'react';
import { createConnection } from './chat.js';
@@ -1262,8 +1264,8 @@ Is there a line of code inside the Effect that should not be reactive? How can y
```json package.json hidden
{
"dependencies": {
- "react": "experimental",
- "react-dom": "experimental",
+ "react": "canary",
+ "react-dom": "canary",
"react-scripts": "latest"
},
"scripts": {
@@ -1277,7 +1279,7 @@ Is there a line of code inside the Effect that should not be reactive? How can y
```js
import { useState, useEffect, useRef } from 'react';
-import { experimental_useEffectEvent as useEffectEvent } from 'react';
+import { useEffectEvent } from 'react';
import { FadeInAnimation } from './animation.js';
function Welcome({ duration }) {
@@ -1389,8 +1391,8 @@ Your Effect needs to read the latest value of `duration`, but you don't want it
```json package.json hidden
{
"dependencies": {
- "react": "experimental",
- "react-dom": "experimental",
+ "react": "canary",
+ "react-dom": "canary",
"react-scripts": "latest"
},
"scripts": {
@@ -1405,7 +1407,7 @@ Your Effect needs to read the latest value of `duration`, but you don't want it
```js
import { useState, useEffect, useRef } from 'react';
import { FadeInAnimation } from './animation.js';
-import { experimental_useEffectEvent as useEffectEvent } from 'react';
+import { useEffectEvent } from 'react';
function Welcome({ duration }) {
const ref = useRef(null);
@@ -1825,8 +1827,8 @@ Another of these functions only exists to pass some state to an imported API met
```json package.json hidden
{
"dependencies": {
- "react": "experimental",
- "react-dom": "experimental",
+ "react": "canary",
+ "react-dom": "canary",
"react-scripts": "latest",
"toastify-js": "1.12.0"
},
@@ -1907,7 +1909,7 @@ export default function App() {
```js src/ChatRoom.js active
import { useState, useEffect } from 'react';
-import { experimental_useEffectEvent as useEffectEvent } from 'react';
+import { useEffectEvent } from 'react';
export default function ChatRoom({ roomId, createConnection, onMessage }) {
useEffect(() => {
@@ -2120,8 +2122,8 @@ As a result, the chat re-connects only when something meaningful (`roomId` or `i
```json package.json hidden
{
"dependencies": {
- "react": "experimental",
- "react-dom": "experimental",
+ "react": "canary",
+ "react-dom": "canary",
"react-scripts": "latest",
"toastify-js": "1.12.0"
},
@@ -2189,7 +2191,7 @@ export default function App() {
```js src/ChatRoom.js active
import { useState, useEffect } from 'react';
-import { experimental_useEffectEvent as useEffectEvent } from 'react';
+import { useEffectEvent } from 'react';
import {
createEncryptedConnection,
createUnencryptedConnection,
diff --git a/src/content/learn/responding-to-events.md b/src/content/learn/responding-to-events.md
index b6eacdde0..6fb835721 100644
--- a/src/content/learn/responding-to-events.md
+++ b/src/content/learn/responding-to-events.md
@@ -546,7 +546,7 @@ button { margin-left: 5px; }
-```js
+```js {expectedErrors: {'react-compiler': [5, 7]}}
export default function LightSwitch() {
function handleClick() {
let bodyStyle = document.body.style;
diff --git a/src/content/learn/reusing-logic-with-custom-hooks.md b/src/content/learn/reusing-logic-with-custom-hooks.md
index b6562e2df..2038e59e1 100644
--- a/src/content/learn/reusing-logic-with-custom-hooks.md
+++ b/src/content/learn/reusing-logic-with-custom-hooks.md
@@ -837,11 +837,13 @@ Every time your `ChatRoom` component re-renders, it passes the latest `roomId` a
### Passing event handlers to custom Hooks {/*passing-event-handlers-to-custom-hooks*/}
-
+
-This section describes an **experimental API that has not yet been released** in a stable version of React.
+**The `useEffectEvent` API is currently only available in React’s Canary and Experimental channels.**
-
+[Learn more about React’s release channels here.](/community/versioning-policy#all-release-channels)
+
+
As you start using `useChatRoom` in more components, you might want to let components customize its behavior. For example, currently, the logic for what to do when a message arrives is hardcoded inside the Hook:
@@ -985,7 +987,7 @@ export default function ChatRoom({ roomId }) {
```js src/useChatRoom.js
import { useEffect } from 'react';
-import { experimental_useEffectEvent as useEffectEvent } from 'react';
+import { useEffectEvent } from 'react';
import { createConnection } from './chat.js';
export function useChatRoom({ serverUrl, roomId, onReceiveMessage }) {
@@ -1070,8 +1072,8 @@ export function showNotification(message, theme = 'dark') {
```json package.json hidden
{
"dependencies": {
- "react": "experimental",
- "react-dom": "experimental",
+ "react": "canary",
+ "react-dom": "canary",
"react-scripts": "latest",
"toastify-js": "1.12.0"
},
@@ -1419,10 +1421,29 @@ Similar to a [design system,](https://uxdesign.cc/everything-you-need-to-know-ab
#### Will React provide any built-in solution for data fetching? {/*will-react-provide-any-built-in-solution-for-data-fetching*/}
+Today, with the [`use`](/reference/react/use#streaming-data-from-server-to-client) API, data can be read in render by passing a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) to `use`:
+
+```js {1,4,11}
+import { use, Suspense } from "react";
+
+function Message({ messagePromise }) {
+ const messageContent = use(messagePromise);
+ return
Here is the message: {messageContent}
;
+}
+
+export function MessageContainer({ messagePromise }) {
+ return (
+ ⌛Downloading message...}>
+
+
+ );
+}
+```
+
We're still working out the details, but we expect that in the future, you'll write data fetching like this:
```js {1,4,6}
-import { use } from 'react'; // Not available yet!
+import { use } from 'react';
function ShippingForm({ country }) {
const cities = use(fetch(`/api/cities?country=${country}`));
@@ -1647,7 +1668,7 @@ export default function App() {
```js src/useFadeIn.js active
import { useState, useEffect } from 'react';
-import { experimental_useEffectEvent as useEffectEvent } from 'react';
+import { useEffectEvent } from 'react';
export function useFadeIn(ref, duration) {
const [isRunning, setIsRunning] = useState(true);
@@ -1700,8 +1721,8 @@ html, body { min-height: 300px; }
```json package.json hidden
{
"dependencies": {
- "react": "experimental",
- "react-dom": "experimental",
+ "react": "canary",
+ "react-dom": "canary",
"react-scripts": "latest"
},
"scripts": {
@@ -2189,8 +2210,8 @@ It looks like your `useInterval` Hook accepts an event listener as an argument.
```json package.json hidden
{
"dependencies": {
- "react": "experimental",
- "react-dom": "experimental",
+ "react": "canary",
+ "react-dom": "canary",
"react-scripts": "latest"
},
"scripts": {
@@ -2233,7 +2254,7 @@ export function useCounter(delay) {
```js src/useInterval.js
import { useEffect } from 'react';
-import { experimental_useEffectEvent as useEffectEvent } from 'react';
+import { useEffectEvent } from 'react';
export function useInterval(onTick, delay) {
useEffect(() => {
@@ -2260,8 +2281,8 @@ With this change, both intervals work as expected and don't interfere with each
```json package.json hidden
{
"dependencies": {
- "react": "experimental",
- "react-dom": "experimental",
+ "react": "canary",
+ "react-dom": "canary",
"react-scripts": "latest"
},
"scripts": {
@@ -2305,7 +2326,7 @@ export function useCounter(delay) {
```js src/useInterval.js active
import { useEffect } from 'react';
-import { experimental_useEffectEvent as useEffectEvent } from 'react';
+import { useEffectEvent } from 'react';
export function useInterval(callback, delay) {
const onTick = useEffectEvent(callback);
diff --git a/src/content/learn/separating-events-from-effects.md b/src/content/learn/separating-events-from-effects.md
index 03223183b..2a19e0f13 100644
--- a/src/content/learn/separating-events-from-effects.md
+++ b/src/content/learn/separating-events-from-effects.md
@@ -400,13 +400,15 @@ You need a way to separate this non-reactive logic from the reactive Effect arou
### Declaring an Effect Event {/*declaring-an-effect-event*/}
-
+
-This section describes an **experimental API that has not yet been released** in a stable version of React.
+**The `useEffectEvent` API is currently only available in React’s Canary and Experimental channels.**
-
+[Learn more about React’s release channels here.](/community/versioning-policy#all-release-channels)
-Use a special Hook called [`useEffectEvent`](/reference/react/experimental_useEffectEvent) to extract this non-reactive logic out of your Effect:
+
+
+Use a special Hook called [`useEffectEvent`](/reference/react/useEffectEvent) to extract this non-reactive logic out of your Effect:
```js {1,4-6}
import { useEffect, useEffectEvent } from 'react';
@@ -448,8 +450,8 @@ Verify that the new behavior works as you would expect:
```json package.json hidden
{
"dependencies": {
- "react": "experimental",
- "react-dom": "experimental",
+ "react": "canary",
+ "react-dom": "canary",
"react-scripts": "latest",
"toastify-js": "1.12.0"
},
@@ -464,7 +466,7 @@ Verify that the new behavior works as you would expect:
```js
import { useState, useEffect } from 'react';
-import { experimental_useEffectEvent as useEffectEvent } from 'react';
+import { useEffectEvent } from 'react';
import { createConnection, sendMessage } from './chat.js';
import { showNotification } from './notifications.js';
@@ -578,11 +580,13 @@ You can think of Effect Events as being very similar to event handlers. The main
### Reading latest props and state with Effect Events {/*reading-latest-props-and-state-with-effect-events*/}
-
+
+
+**The `useEffectEvent` API is currently only available in React’s Canary and Experimental channels.**
-This section describes an **experimental API that has not yet been released** in a stable version of React.
+[Learn more about React’s release channels here.](/community/versioning-policy#all-release-channels)
-
+
Effect Events let you fix many patterns where you might be tempted to suppress the dependency linter.
@@ -711,7 +715,7 @@ Here, `url` inside `onVisit` corresponds to the *latest* `url` (which could have
In the existing codebases, you may sometimes see the lint rule suppressed like this:
-```js {7-9}
+```js {expectedErrors: {'react-compiler': [8]}} {7-9}
function Page({ url }) {
const { items } = useContext(ShoppingCartContext);
const numberOfItems = items.length;
@@ -735,7 +739,7 @@ Can you see why?
-```js
+```js {expectedErrors: {'react-compiler': [16]}}
import { useState, useEffect } from 'react';
export default function App() {
@@ -803,8 +807,8 @@ With `useEffectEvent`, there is no need to "lie" to the linter, and the code wor
```json package.json hidden
{
"dependencies": {
- "react": "experimental",
- "react-dom": "experimental",
+ "react": "canary",
+ "react-dom": "canary",
"react-scripts": "latest"
},
"scripts": {
@@ -818,7 +822,7 @@ With `useEffectEvent`, there is no need to "lie" to the linter, and the code wor
```js
import { useState, useEffect } from 'react';
-import { experimental_useEffectEvent as useEffectEvent } from 'react';
+import { useEffectEvent } from 'react';
export default function App() {
const [position, setPosition] = useState({ x: 0, y: 0 });
@@ -878,11 +882,13 @@ Read [Removing Effect Dependencies](/learn/removing-effect-dependencies) for oth
### Limitations of Effect Events {/*limitations-of-effect-events*/}
-
+
-This section describes an **experimental API that has not yet been released** in a stable version of React.
+**The `useEffectEvent` API is currently only available in React’s Canary and Experimental channels.**
-
+[Learn more about React’s release channels here.](/community/versioning-policy#all-release-channels)
+
+
Effect Events are very limited in how you can use them:
@@ -976,8 +982,8 @@ To fix this code, it's enough to follow the rules.
```json package.json hidden
{
"dependencies": {
- "react": "experimental",
- "react-dom": "experimental",
+ "react": "canary",
+ "react-dom": "canary",
"react-scripts": "latest"
},
"scripts": {
@@ -990,7 +996,7 @@ To fix this code, it's enough to follow the rules.
```
-```js
+```js {expectedErrors: {'react-compiler': [14]}}
import { useState, useEffect } from 'react';
export default function Timer() {
@@ -1046,8 +1052,8 @@ If you remove the suppression comment, React will tell you that this Effect's co
```json package.json hidden
{
"dependencies": {
- "react": "experimental",
- "react-dom": "experimental",
+ "react": "canary",
+ "react-dom": "canary",
"react-scripts": "latest"
},
"scripts": {
@@ -1124,8 +1130,8 @@ It seems like the Effect which sets up the timer "reacts" to the `increment` val
```json package.json hidden
{
"dependencies": {
- "react": "experimental",
- "react-dom": "experimental",
+ "react": "canary",
+ "react-dom": "canary",
"react-scripts": "latest"
},
"scripts": {
@@ -1139,7 +1145,7 @@ It seems like the Effect which sets up the timer "reacts" to the `increment` val
```js
import { useState, useEffect } from 'react';
-import { experimental_useEffectEvent as useEffectEvent } from 'react';
+import { useEffectEvent } from 'react';
export default function Timer() {
const [count, setCount] = useState(0);
@@ -1193,8 +1199,8 @@ To solve the issue, extract an `onTick` Effect Event from the Effect:
```json package.json hidden
{
"dependencies": {
- "react": "experimental",
- "react-dom": "experimental",
+ "react": "canary",
+ "react-dom": "canary",
"react-scripts": "latest"
},
"scripts": {
@@ -1208,7 +1214,7 @@ To solve the issue, extract an `onTick` Effect Event from the Effect:
```js
import { useState, useEffect } from 'react';
-import { experimental_useEffectEvent as useEffectEvent } from 'react';
+import { useEffectEvent } from 'react';
export default function Timer() {
const [count, setCount] = useState(0);
@@ -1275,8 +1281,8 @@ Code inside Effect Events is not reactive. Are there cases in which you would _w
```json package.json hidden
{
"dependencies": {
- "react": "experimental",
- "react-dom": "experimental",
+ "react": "canary",
+ "react-dom": "canary",
"react-scripts": "latest"
},
"scripts": {
@@ -1290,7 +1296,7 @@ Code inside Effect Events is not reactive. Are there cases in which you would _w
```js
import { useState, useEffect } from 'react';
-import { experimental_useEffectEvent as useEffectEvent } from 'react';
+import { useEffectEvent } from 'react';
export default function Timer() {
const [count, setCount] = useState(0);
@@ -1362,8 +1368,8 @@ The problem with the above example is that it extracted an Effect Event called `
```json package.json hidden
{
"dependencies": {
- "react": "experimental",
- "react-dom": "experimental",
+ "react": "canary",
+ "react-dom": "canary",
"react-scripts": "latest"
},
"scripts": {
@@ -1377,7 +1383,7 @@ The problem with the above example is that it extracted an Effect Event called `
```js
import { useState, useEffect } from 'react';
-import { experimental_useEffectEvent as useEffectEvent } from 'react';
+import { useEffectEvent } from 'react';
export default function Timer() {
const [count, setCount] = useState(0);
@@ -1458,8 +1464,8 @@ Your Effect knows which room it connected to. Is there any information that you
```json package.json hidden
{
"dependencies": {
- "react": "experimental",
- "react-dom": "experimental",
+ "react": "canary",
+ "react-dom": "canary",
"react-scripts": "latest",
"toastify-js": "1.12.0"
},
@@ -1474,7 +1480,7 @@ Your Effect knows which room it connected to. Is there any information that you
```js
import { useState, useEffect } from 'react';
-import { experimental_useEffectEvent as useEffectEvent } from 'react';
+import { useEffectEvent } from 'react';
import { createConnection, sendMessage } from './chat.js';
import { showNotification } from './notifications.js';
@@ -1599,8 +1605,8 @@ To fix the issue, instead of reading the *latest* `roomId` inside the Effect Eve
```json package.json hidden
{
"dependencies": {
- "react": "experimental",
- "react-dom": "experimental",
+ "react": "canary",
+ "react-dom": "canary",
"react-scripts": "latest",
"toastify-js": "1.12.0"
},
@@ -1615,7 +1621,7 @@ To fix the issue, instead of reading the *latest* `roomId` inside the Effect Eve
```js
import { useState, useEffect } from 'react';
-import { experimental_useEffectEvent as useEffectEvent } from 'react';
+import { useEffectEvent } from 'react';
import { createConnection, sendMessage } from './chat.js';
import { showNotification } from './notifications.js';
@@ -1736,8 +1742,8 @@ To solve the additional challenge, save the notification timeout ID and clear it
```json package.json hidden
{
"dependencies": {
- "react": "experimental",
- "react-dom": "experimental",
+ "react": "canary",
+ "react-dom": "canary",
"react-scripts": "latest",
"toastify-js": "1.12.0"
},
@@ -1752,7 +1758,7 @@ To solve the additional challenge, save the notification timeout ID and clear it
```js
import { useState, useEffect } from 'react';
-import { experimental_useEffectEvent as useEffectEvent } from 'react';
+import { useEffectEvent } from 'react';
import { createConnection, sendMessage } from './chat.js';
import { showNotification } from './notifications.js';
diff --git a/src/content/learn/state-a-components-memory.md b/src/content/learn/state-a-components-memory.md
index 7213a5300..739266f0c 100644
--- a/src/content/learn/state-a-components-memory.md
+++ b/src/content/learn/state-a-components-memory.md
@@ -23,7 +23,7 @@ title: "Стан: пам'ять компонента"
-```js
+```js {expectedErrors: {'react-compiler': [7]}}
import { sculptureList } from './data.js';
export default function Gallery() {
@@ -1229,7 +1229,7 @@ img { width: 120px; height: 120px; }
-```js
+```js {expectedErrors: {'react-compiler': [6]}}
export default function Form() {
let firstName = '';
let lastName = '';
@@ -1337,7 +1337,7 @@ h1 { margin-top: 10px; }
-```js
+```js {expectedErrors: {'react-compiler': [9]}}
import { useState } from 'react';
export default function FeedbackForm() {
diff --git a/src/content/learn/synchronizing-with-effects.md b/src/content/learn/synchronizing-with-effects.md
index c0ad34709..7f61a255b 100644
--- a/src/content/learn/synchronizing-with-effects.md
+++ b/src/content/learn/synchronizing-with-effects.md
@@ -95,7 +95,7 @@ You might be tempted to try to call `play()` or `pause()` during rendering, but
-```js
+```js {expectedErrors: {'react-compiler': [7, 9]}}
import { useState, useRef, useEffect } from 'react';
function VideoPlayer({ src, isPlaying }) {
@@ -617,7 +617,7 @@ A common pitfall for preventing Effects firing twice in development is to use a
This makes it so you only see `"✅ Connecting..."` once in development, but it doesn't fix the bug.
-When the user navigates away, the connection still isn't closed and when they navigate back, a new connection is created. As the user navigates across the app, the connections would keep piling up, the same as it would before the "fix".
+When the user navigates away, the connection still isn't closed and when they navigate back, a new connection is created. As the user navigates across the app, the connections would keep piling up, the same as it would before the "fix".
To fix the bug, it is not enough to just make the Effect run once. The effect needs to work after re-mounting, which means the connection needs to be cleaned up like in the solution above.
@@ -1005,7 +1005,7 @@ export default function MyInput({ value, onChange }) {
const ref = useRef(null);
// TODO: This doesn't quite work. Fix it.
- // ref.current.focus()
+ // ref.current.focus()
return (
-```js src/App.js
+{/* not the most efficient, but this validation is enabled in the linter only, so it's fine to ignore it here since we know what we're doing */}
+```js {expectedErrors: {'react-compiler': [9]}} src/App.js
import { useState, useEffect } from 'react';
import { fetchBio } from './api.js';
@@ -1541,7 +1542,8 @@ To fix this race condition, add a cleanup function:
-```js src/App.js
+{/* not the most efficient, but this validation is enabled in the linter only, so it's fine to ignore it here since we know what we're doing */}
+```js {expectedErrors: {'react-compiler': [9]}} src/App.js
import { useState, useEffect } from 'react';
import { fetchBio } from './api.js';
@@ -1605,4 +1607,3 @@ In addition to ignoring the result of an outdated API call, you can also use [`A
-
diff --git a/src/content/learn/tutorial-tic-tac-toe.md b/src/content/learn/tutorial-tic-tac-toe.md
index ab4d5aff3..bc883a40a 100644
--- a/src/content/learn/tutorial-tic-tac-toe.md
+++ b/src/content/learn/tutorial-tic-tac-toe.md
@@ -283,7 +283,7 @@ In CodeSandbox you'll see three main sections:

-1. The _Files_ section with a list of files like `App.js`, `index.js`, `styles.css` and a folder called `public`
+1. The _Files_ section with a list of files like `App.js`, `index.js`, `styles.css` in `src` folder and a folder called `public`
1. The _code editor_ where you'll see the source code of your selected file
1. The _browser_ section where you'll see how the code you've written will be displayed
diff --git a/src/content/learn/typescript.md b/src/content/learn/typescript.md
index 4589ae82d..317404b9b 100644
--- a/src/content/learn/typescript.md
+++ b/src/content/learn/typescript.md
@@ -32,7 +32,7 @@ TypeScript — це популярний спосіб додавання виз
Щоб встановити останню версію визначень типів для React, виконайте команду:
-npm install @types/react @types/react-dom
+npm install --save-dev @types/react @types/react-dom
У вашому `tsconfig.json` потрібно вказати такі параметри компілятора:
diff --git a/src/content/learn/updating-objects-in-state.md b/src/content/learn/updating-objects-in-state.md
index 93ea93bd2..ca6585145 100644
--- a/src/content/learn/updating-objects-in-state.md
+++ b/src/content/learn/updating-objects-in-state.md
@@ -55,7 +55,7 @@ This example holds an object in state to represent the current pointer position.
-```js
+```js {expectedErrors: {'react-compiler': [11]}}
import { useState } from 'react';
export default function MovingDot() {
@@ -209,7 +209,7 @@ These input fields don't work because the `onChange` handlers mutate the state:
-```js
+```js {expectedErrors: {'react-compiler': [11, 15, 19]}}
import { useState } from 'react';
export default function Form() {
@@ -832,7 +832,7 @@ Your task is to fix all of these bugs. As you fix them, explain why each of them
-```js
+```js {expectedErrors: {'react-compiler': [11]}}
import { useState } from 'react';
export default function Scoreboard() {
@@ -988,7 +988,7 @@ If something unexpected changes, there is a mutation. Find the mutation in `App.
-```js src/App.js
+```js {expectedErrors: {'react-compiler': [17]}} src/App.js
import { useState } from 'react';
import Background from './Background.js';
import Box from './Box.js';
@@ -1293,7 +1293,7 @@ This is the same buggy example as in the previous challenge. This time, fix the
-```js src/App.js
+```js {expectedErrors: {'react-compiler': [18]}} src/App.js
import { useState } from 'react';
import { useImmer } from 'use-immer';
import Background from './Background.js';
diff --git a/src/content/learn/you-might-not-need-an-effect.md b/src/content/learn/you-might-not-need-an-effect.md
index 88e490e1a..39a1dc740 100644
--- a/src/content/learn/you-might-not-need-an-effect.md
+++ b/src/content/learn/you-might-not-need-an-effect.md
@@ -34,7 +34,7 @@ To help you gain the right intuition, let's look at some common concrete example
Suppose you have a component with two state variables: `firstName` and `lastName`. You want to calculate a `fullName` from them by concatenating them. Moreover, you'd like `fullName` to update whenever `firstName` or `lastName` change. Your first instinct might be to add a `fullName` state variable and update it in an Effect:
-```js {5-9}
+```js {expectedErrors: {'react-compiler': [8]}} {5-9}
function Form() {
const [firstName, setFirstName] = useState('Taylor');
const [lastName, setLastName] = useState('Swift');
@@ -66,7 +66,7 @@ function Form() {
This component computes `visibleTodos` by taking the `todos` it receives by props and filtering them according to the `filter` prop. You might feel tempted to store the result in state and update it from an Effect:
-```js {4-8}
+```js {expectedErrors: {'react-compiler': [7]}} {4-8}
function TodoList({ todos, filter }) {
const [newTodo, setNewTodo] = useState('');
@@ -165,7 +165,7 @@ Also note that measuring performance in development will not give you the most a
This `ProfilePage` component receives a `userId` prop. The page contains a comment input, and you use a `comment` state variable to hold its value. One day, you notice a problem: when you navigate from one profile to another, the `comment` state does not get reset. As a result, it's easy to accidentally post a comment on a wrong user's profile. To fix the issue, you want to clear out the `comment` state variable whenever the `userId` changes:
-```js {4-7}
+```js {expectedErrors: {'react-compiler': [6]}} {4-7}
export default function ProfilePage({ userId }) {
const [comment, setComment] = useState('');
@@ -208,7 +208,7 @@ Sometimes, you might want to reset or adjust a part of the state on a prop chang
This `List` component receives a list of `items` as a prop, and maintains the selected item in the `selection` state variable. You want to reset the `selection` to `null` whenever the `items` prop receives a different array:
-```js {5-8}
+```js {expectedErrors: {'react-compiler': [7]}} {5-8}
function List({ items }) {
const [isReverse, setIsReverse] = useState(false);
const [selection, setSelection] = useState(null);
@@ -819,7 +819,7 @@ Simplify this component by removing all the unnecessary state and Effects.
-```js
+```js {expectedErrors: {'react-compiler': [12, 16, 20]}}
import { useState, useEffect } from 'react';
import { initialTodos, createTodo } from './todos.js';
@@ -1022,7 +1022,7 @@ One solution is to add a `useMemo` call to cache the visible todos. There is als
-```js
+```js {expectedErrors: {'react-compiler': [11]}}
import { useState, useEffect } from 'react';
import { initialTodos, createTodo, getVisibleTodos } from './todos.js';
@@ -1106,7 +1106,7 @@ Remove the state variable and the Effect, and instead add a `useMemo` call to ca
-```js
+```js {expectedErrors: {'react-compiler': [8]}}
import { useState, useMemo } from 'react';
import { initialTodos, createTodo, getVisibleTodos } from './todos.js';
@@ -1363,7 +1363,7 @@ export default function ContactList({
}
```
-```js src/EditContact.js active
+```js {expectedErrors: {'react-compiler': [8, 9]}} src/EditContact.js active
import { useState, useEffect } from 'react';
export default function EditContact({ savedContact, onSave }) {
diff --git a/src/content/reference/eslint-plugin-react-hooks/index.md b/src/content/reference/eslint-plugin-react-hooks/index.md
new file mode 100644
index 000000000..21e1222d8
--- /dev/null
+++ b/src/content/reference/eslint-plugin-react-hooks/index.md
@@ -0,0 +1,51 @@
+---
+title: eslint-plugin-react-hooks
+version: rc
+---
+
+
+
+`eslint-plugin-react-hooks` provides ESLint rules to enforce the [Rules of React](/reference/rules).
+
+
+
+
+
+These docs include rules available in the RC version of `eslint-plugin-react-hooks`.
+
+You can try them by upgrading the lint plugin [to the most recent RC version](/learn/react-compiler/installation#eslint-integration).
+
+
+
+This plugin helps you catch violations of React's rules at build time, ensuring your components and hooks follow React's rules for correctness and performance. The lints cover both fundamental React patterns (exhaustive-deps and rules-of-hooks) and issues flagged by React Compiler. React Compiler diagnostics are automatically surfaced by this ESLint plugin, and can be used even if your app hasn't adopted the compiler yet.
+
+
+When the compiler reports a diagnostic, it means that the compiler was able to statically detect a pattern that is not supported or breaks the Rules of React. When it detects this, it **automatically** skips over those components and hooks, while keeping the rest of your app compiled. This ensures optimal coverage of safe optimizations that won't break your app.
+
+What this means for linting, is that you don’t need to fix all violations immediately. Address them at your own pace to gradually increase the number of optimized components.
+
+
+## Available Lints {/*available-lints*/}
+
+These rules are available in the stable version of `eslint-plugin-react-hooks`:
+
+* [`exhaustive-deps`](/reference/eslint-plugin-react-hooks/lints/exhaustive-deps) - Validates that dependency arrays for React hooks contain all necessary dependencies
+* [`rules-of-hooks`](/reference/eslint-plugin-react-hooks/lints/rules-of-hooks) - Validates that components and hooks follow the Rules of Hooks
+
+These rules are available in the RC version of `eslint-plugin-react-hooks`:
+
+* [`component-hook-factories`](/reference/eslint-plugin-react-hooks/lints/component-hook-factories) - Validates higher order functions defining nested components or hooks
+* [`config`](/reference/eslint-plugin-react-hooks/lints/config) - Validates the compiler configuration options
+* [`error-boundaries`](/reference/eslint-plugin-react-hooks/lints/error-boundaries) - Validates usage of Error Boundaries instead of try/catch for child errors
+* [`gating`](/reference/eslint-plugin-react-hooks/lints/gating) - Validates configuration of gating mode
+* [`globals`](/reference/eslint-plugin-react-hooks/lints/globals) - Validates against assignment/mutation of globals during render
+* [`immutability`](/reference/eslint-plugin-react-hooks/lints/immutability) - Validates against mutating props, state, and other immutable values
+* [`incompatible-library`](/reference/eslint-plugin-react-hooks/lints/incompatible-library) - Validates against usage of libraries which are incompatible with memoization
+* [`preserve-manual-memoization`](/reference/eslint-plugin-react-hooks/lints/preserve-manual-memoization) - Validates that existing manual memoization is preserved by the compiler
+* [`purity`](/reference/eslint-plugin-react-hooks/lints/purity) - Validates that components/hooks are pure by checking known-impure functions
+* [`refs`](/reference/eslint-plugin-react-hooks/lints/refs) - Validates correct usage of refs, not reading/writing during render
+* [`set-state-in-effect`](/reference/eslint-plugin-react-hooks/lints/set-state-in-effect) - Validates against calling setState synchronously in an effect
+* [`set-state-in-render`](/reference/eslint-plugin-react-hooks/lints/set-state-in-render) - Validates against setting state during render
+* [`static-components`](/reference/eslint-plugin-react-hooks/lints/static-components) - Validates that components are static, not recreated every render
+* [`unsupported-syntax`](/reference/eslint-plugin-react-hooks/lints/unsupported-syntax) - Validates against syntax that React Compiler does not support
+* [`use-memo`](/reference/eslint-plugin-react-hooks/lints/use-memo) - Validates usage of the `useMemo` hook without a return value
\ No newline at end of file
diff --git a/src/content/reference/eslint-plugin-react-hooks/lints/component-hook-factories.md b/src/content/reference/eslint-plugin-react-hooks/lints/component-hook-factories.md
new file mode 100644
index 000000000..44d23d758
--- /dev/null
+++ b/src/content/reference/eslint-plugin-react-hooks/lints/component-hook-factories.md
@@ -0,0 +1,111 @@
+---
+title: component-hook-factories
+version: rc
+---
+
+
+
+Validates against higher order functions defining nested components or hooks. Components and hooks should be defined at the module level.
+
+
+
+
+
+This rule is available in the RC version of `eslint-plugin-react-hooks`.
+
+You can try it by upgrading the lint plugin [to the most recent RC version](/learn/react-compiler/installation#eslint-integration).
+
+
+
+## Rule Details {/*rule-details*/}
+
+Defining components or hooks inside other functions creates new instances on every call. React treats each as a completely different component, destroying and recreating the entire component tree, losing all state, and causing performance problems.
+
+### Invalid {/*invalid*/}
+
+Examples of incorrect code for this rule:
+
+```js {expectedErrors: {'react-compiler': [14]}}
+// ❌ Factory function creating components
+function createComponent(defaultValue) {
+ return function Component() {
+ // ...
+ };
+}
+
+// ❌ Component defined inside component
+function Parent() {
+ function Child() {
+ // ...
+ }
+
+ return ;
+}
+
+// ❌ Hook factory function
+function createCustomHook(endpoint) {
+ return function useData() {
+ // ...
+ };
+}
+```
+
+### Valid {/*valid*/}
+
+Examples of correct code for this rule:
+
+```js
+// ✅ Component defined at module level
+function Component({ defaultValue }) {
+ // ...
+}
+
+// ✅ Custom hook at module level
+function useData(endpoint) {
+ // ...
+}
+```
+
+## Troubleshooting {/*troubleshooting*/}
+
+### I need dynamic component behavior {/*dynamic-behavior*/}
+
+You might think you need a factory to create customized components:
+
+```js
+// ❌ Wrong: Factory pattern
+function makeButton(color) {
+ return function Button({children}) {
+ return (
+
+ );
+ };
+}
+
+const RedButton = makeButton('red');
+const BlueButton = makeButton('blue');
+```
+
+Pass [JSX as children](/learn/passing-props-to-a-component#passing-jsx-as-children) instead:
+
+```js
+// ✅ Better: Pass JSX as children
+function Button({color, children}) {
+ return (
+
+ );
+}
+
+function App() {
+ return (
+ <>
+
+
+ >
+ );
+}
+```
diff --git a/src/content/reference/eslint-plugin-react-hooks/lints/config.md b/src/content/reference/eslint-plugin-react-hooks/lints/config.md
new file mode 100644
index 000000000..f7e099752
--- /dev/null
+++ b/src/content/reference/eslint-plugin-react-hooks/lints/config.md
@@ -0,0 +1,99 @@
+---
+title: config
+version: rc
+---
+
+
+
+Validates the compiler [configuration options](/reference/react-compiler/configuration).
+
+
+
+
+
+This rule is available in the RC version of `eslint-plugin-react-hooks`.
+
+You can try it by upgrading the lint plugin [to the most recent RC version](/learn/react-compiler/installation#eslint-integration).
+
+
+
+## Rule Details {/*rule-details*/}
+
+React Compiler accepts various [configuration options](/reference/react-compiler/configuration) to control its behavior. This rule validates that your configuration uses correct option names and value types, preventing silent failures from typos or incorrect settings.
+
+### Invalid {/*invalid*/}
+
+Examples of incorrect code for this rule:
+
+```js
+// ❌ Unknown option name
+module.exports = {
+ plugins: [
+ ['babel-plugin-react-compiler', {
+ compileMode: 'all' // Typo: should be compilationMode
+ }]
+ ]
+};
+
+// ❌ Invalid option value
+module.exports = {
+ plugins: [
+ ['babel-plugin-react-compiler', {
+ compilationMode: 'everything' // Invalid: use 'all' or 'infer'
+ }]
+ ]
+};
+```
+
+### Valid {/*valid*/}
+
+Examples of correct code for this rule:
+
+```js
+// ✅ Valid compiler configuration
+module.exports = {
+ plugins: [
+ ['babel-plugin-react-compiler', {
+ compilationMode: 'infer',
+ panicThreshold: 'critical_errors'
+ }]
+ ]
+};
+```
+
+## Troubleshooting {/*troubleshooting*/}
+
+### Configuration not working as expected {/*config-not-working*/}
+
+Your compiler configuration might have typos or incorrect values:
+
+```js
+// ❌ Wrong: Common configuration mistakes
+module.exports = {
+ plugins: [
+ ['babel-plugin-react-compiler', {
+ // Typo in option name
+ compilationMod: 'all',
+ // Wrong value type
+ panicThreshold: true,
+ // Unknown option
+ optimizationLevel: 'max'
+ }]
+ ]
+};
+```
+
+Check the [configuration documentation](/reference/react-compiler/configuration) for valid options:
+
+```js
+// ✅ Better: Valid configuration
+module.exports = {
+ plugins: [
+ ['babel-plugin-react-compiler', {
+ compilationMode: 'all', // or 'infer'
+ panicThreshold: 'none', // or 'critical_errors', 'all_errors'
+ // Only use documented options
+ }]
+ ]
+};
+```
diff --git a/src/content/reference/eslint-plugin-react-hooks/lints/error-boundaries.md b/src/content/reference/eslint-plugin-react-hooks/lints/error-boundaries.md
new file mode 100644
index 000000000..bd013f532
--- /dev/null
+++ b/src/content/reference/eslint-plugin-react-hooks/lints/error-boundaries.md
@@ -0,0 +1,81 @@
+---
+title: error-boundaries
+version: rc
+---
+
+
+
+Validates usage of Error Boundaries instead of try/catch for errors in child components.
+
+
+
+
+
+This rule is available in the RC version of `eslint-plugin-react-hooks`.
+
+You can try it by upgrading the lint plugin [to the most recent RC version](/learn/react-compiler/installation#eslint-integration).
+
+
+
+## Rule Details {/*rule-details*/}
+
+Try/catch blocks can't catch errors that happen during React's rendering process. Errors thrown in rendering methods or hooks bubble up through the component tree. Only [Error Boundaries](/reference/react/Component#catching-rendering-errors-with-an-error-boundary) can catch these errors.
+
+### Invalid {/*invalid*/}
+
+Examples of incorrect code for this rule:
+
+```js {expectedErrors: {'react-compiler': [4]}}
+// ❌ Try/catch won't catch render errors
+function Parent() {
+ try {
+ return ; // If this throws, catch won't help
+ } catch (error) {
+ return
Error occurred
;
+ }
+}
+```
+
+### Valid {/*valid*/}
+
+Examples of correct code for this rule:
+
+```js
+// ✅ Using error boundary
+function Parent() {
+ return (
+
+
+
+ );
+}
+```
+
+## Troubleshooting {/*troubleshooting*/}
+
+### Why is the linter telling me not to wrap `use` in `try`/`catch`? {/*why-is-the-linter-telling-me-not-to-wrap-use-in-trycatch*/}
+
+The `use` hook doesn't throw errors in the traditional sense, it suspends component execution. When `use` encounters a pending promise, it suspends the component and lets React show a fallback. Only Suspense and Error Boundaries can handle these cases. The linter warns against `try`/`catch` around `use` to prevent confusion as the `catch` block would never run.
+
+```js {expectedErrors: {'react-compiler': [5]}}
+// ❌ Try/catch around `use` hook
+function Component({promise}) {
+ try {
+ const data = use(promise); // Won't catch - `use` suspends, not throws
+ return
}>
+ Loading...}>
+
+
+
+ );
+}
+```
\ No newline at end of file
diff --git a/src/content/reference/eslint-plugin-react-hooks/lints/exhaustive-deps.md b/src/content/reference/eslint-plugin-react-hooks/lints/exhaustive-deps.md
new file mode 100644
index 000000000..cd26483a1
--- /dev/null
+++ b/src/content/reference/eslint-plugin-react-hooks/lints/exhaustive-deps.md
@@ -0,0 +1,155 @@
+---
+title: exhaustive-deps
+---
+
+
+
+Validates that dependency arrays for React hooks contain all necessary dependencies.
+
+
+
+## Rule Details {/*rule-details*/}
+
+React hooks like `useEffect`, `useMemo`, and `useCallback` accept dependency arrays. When a value referenced inside these hooks isn't included in the dependency array, React won't re-run the effect or recalculate the value when that dependency changes. This causes stale closures where the hook uses outdated values.
+
+## Common Violations {/*common-violations*/}
+
+This error often happens when you try to "trick" React about dependencies to control when an effect runs. Effects should synchronize your component with external systems. The dependency array tells React which values the effect uses, so React knows when to re-synchronize.
+
+If you find yourself fighting with the linter, you likely need to restructure your code. See [Removing Effect Dependencies](/learn/removing-effect-dependencies) to learn how.
+
+### Invalid {/*invalid*/}
+
+Examples of incorrect code for this rule:
+
+```js
+// ❌ Missing dependency
+useEffect(() => {
+ console.log(count);
+}, []); // Missing 'count'
+
+// ❌ Missing prop
+useEffect(() => {
+ fetchUser(userId);
+}, []); // Missing 'userId'
+
+// ❌ Incomplete dependencies
+useMemo(() => {
+ return items.sort(sortOrder);
+}, [items]); // Missing 'sortOrder'
+```
+
+### Valid {/*valid*/}
+
+Examples of correct code for this rule:
+
+```js
+// ✅ All dependencies included
+useEffect(() => {
+ console.log(count);
+}, [count]);
+
+// ✅ All dependencies included
+useEffect(() => {
+ fetchUser(userId);
+}, [userId]);
+```
+
+## Troubleshooting {/*troubleshooting*/}
+
+### Adding a function dependency causes infinite loops {/*function-dependency-loops*/}
+
+You have an effect, but you're creating a new function on every render:
+
+```js
+// ❌ Causes infinite loop
+const logItems = () => {
+ console.log(items);
+};
+
+useEffect(() => {
+ logItems();
+}, [logItems]); // Infinite loop!
+```
+
+In most cases, you don't need the effect. Call the function where the action happens instead:
+
+```js
+// ✅ Call it from the event handler
+const logItems = () => {
+ console.log(items);
+};
+
+return ;
+
+// ✅ Or derive during render if there's no side effect
+items.forEach(item => {
+ console.log(item);
+});
+```
+
+If you genuinely need the effect (for example, to subscribe to something external), make the dependency stable:
+
+```js
+// ✅ useCallback keeps the function reference stable
+const logItems = useCallback(() => {
+ console.log(items);
+}, [items]);
+
+useEffect(() => {
+ logItems();
+}, [logItems]);
+
+// ✅ Or move the logic straight into the effect
+useEffect(() => {
+ console.log(items);
+}, [items]);
+```
+
+### Running an effect only once {/*effect-on-mount*/}
+
+You want to run an effect once on mount, but the linter complains about missing dependencies:
+
+```js
+// ❌ Missing dependency
+useEffect(() => {
+ sendAnalytics(userId);
+}, []); // Missing 'userId'
+```
+
+Either include the dependency (recommended) or use a ref if you truly need to run once:
+
+```js
+// ✅ Include dependency
+useEffect(() => {
+ sendAnalytics(userId);
+}, [userId]);
+
+// ✅ Or use a ref guard inside an effect
+const sent = useRef(false);
+
+useEffect(() => {
+ if (sent.current) {
+ return;
+ }
+
+ sent.current = true;
+ sendAnalytics(userId);
+}, [userId]);
+```
+
+## Options {/*options*/}
+
+This rule accepts an options object:
+
+```js
+{
+ "rules": {
+ "react-hooks/exhaustive-deps": ["warn", {
+ "additionalHooks": "(useMyCustomHook|useAnotherHook)"
+ }]
+ }
+}
+```
+
+- `additionalHooks`: Regex for hooks that should be checked for exhaustive dependencies
diff --git a/src/content/reference/eslint-plugin-react-hooks/lints/gating.md b/src/content/reference/eslint-plugin-react-hooks/lints/gating.md
new file mode 100644
index 000000000..fdbbadf0e
--- /dev/null
+++ b/src/content/reference/eslint-plugin-react-hooks/lints/gating.md
@@ -0,0 +1,81 @@
+---
+title: gating
+version: rc
+---
+
+
+
+Validates configuration of [gating mode](/reference/react-compiler/gating).
+
+
+
+
+
+This rule is available in the RC version of `eslint-plugin-react-hooks`.
+
+You can try it by upgrading the lint plugin [to the most recent RC version](/learn/react-compiler/installation#eslint-integration).
+
+
+
+## Rule Details {/*rule-details*/}
+
+Gating mode lets you gradually adopt React Compiler by marking specific components for optimization. This rule ensures your gating configuration is valid so the compiler knows which components to process.
+
+### Invalid {/*invalid*/}
+
+Examples of incorrect code for this rule:
+
+```js
+// ❌ Missing required fields
+module.exports = {
+ plugins: [
+ ['babel-plugin-react-compiler', {
+ gating: {
+ importSpecifierName: '__experimental_useCompiler'
+ // Missing 'source' field
+ }
+ }]
+ ]
+};
+
+// ❌ Invalid gating type
+module.exports = {
+ plugins: [
+ ['babel-plugin-react-compiler', {
+ gating: '__experimental_useCompiler' // Should be object
+ }]
+ ]
+};
+```
+
+### Valid {/*valid*/}
+
+Examples of correct code for this rule:
+
+```js
+// ✅ Complete gating configuration
+module.exports = {
+ plugins: [
+ ['babel-plugin-react-compiler', {
+ gating: {
+ importSpecifierName: 'isCompilerEnabled', // exported function name
+ source: 'featureFlags' // module name
+ }
+ }]
+ ]
+};
+
+// featureFlags.js
+export function isCompilerEnabled() {
+ // ...
+}
+
+// ✅ No gating (compile everything)
+module.exports = {
+ plugins: [
+ ['babel-plugin-react-compiler', {
+ // No gating field - compiles all components
+ }]
+ ]
+};
+```
diff --git a/src/content/reference/eslint-plugin-react-hooks/lints/globals.md b/src/content/reference/eslint-plugin-react-hooks/lints/globals.md
new file mode 100644
index 000000000..b9a20d4db
--- /dev/null
+++ b/src/content/reference/eslint-plugin-react-hooks/lints/globals.md
@@ -0,0 +1,93 @@
+---
+title: globals
+version: rc
+---
+
+
+
+Validates against assignment/mutation of globals during render, part of ensuring that [side effects must run outside of render](/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render).
+
+
+
+
+
+This rule is available in the RC version of `eslint-plugin-react-hooks`.
+
+You can try it by upgrading the lint plugin [to the most recent RC version](/learn/react-compiler/installation#eslint-integration).
+
+
+
+## Rule Details {/*rule-details*/}
+
+Global variables exist outside React's control. When you modify them during render, you break React's assumption that rendering is pure. This can cause components to behave differently in development vs production, break Fast Refresh, and make your app impossible to optimize with features like React Compiler.
+
+### Invalid {/*invalid*/}
+
+Examples of incorrect code for this rule:
+
+```js
+// ❌ Global counter
+let renderCount = 0;
+function Component() {
+ renderCount++; // Mutating global
+ return
+ );
+}
+```
+
+### I need to update nested objects {/*update-nested-objects*/}
+
+Mutating nested properties doesn't trigger re-renders:
+
+```js
+// ❌ Wrong: Mutating nested object
+function UserProfile() {
+ const [user, setUser] = useState({
+ name: 'Alice',
+ settings: {
+ theme: 'light',
+ notifications: true
+ }
+ });
+
+ const toggleTheme = () => {
+ user.settings.theme = 'dark'; // Mutation!
+ setUser(user); // Same object reference
+ };
+}
+```
+
+Spread at each level that needs updating:
+
+```js
+// ✅ Better: Create new objects at each level
+function UserProfile() {
+ const [user, setUser] = useState({
+ name: 'Alice',
+ settings: {
+ theme: 'light',
+ notifications: true
+ }
+ });
+
+ const toggleTheme = () => {
+ setUser({
+ ...user,
+ settings: {
+ ...user.settings,
+ theme: 'dark'
+ }
+ });
+ };
+}
+```
\ No newline at end of file
diff --git a/src/content/reference/eslint-plugin-react-hooks/lints/incompatible-library.md b/src/content/reference/eslint-plugin-react-hooks/lints/incompatible-library.md
new file mode 100644
index 000000000..aa5877503
--- /dev/null
+++ b/src/content/reference/eslint-plugin-react-hooks/lints/incompatible-library.md
@@ -0,0 +1,147 @@
+---
+title: incompatible-library
+version: rc
+---
+
+
+
+Validates against usage of libraries which are incompatible with memoization (manual or automatic).
+
+
+
+
+
+This rule is available in the RC version of `eslint-plugin-react-hooks`.
+
+You can try it by upgrading the lint plugin [to the most recent RC version](/learn/react-compiler/installation#eslint-integration).
+
+
+
+
+
+These libraries were designed before React's memoization rules were fully documented. They made the correct choices at the time to optimize for ergonomic ways to keep components just the right amount of reactive as app state changes. While these legacy patterns worked, we have since discovered that it's incompatible with React's programming model. We will continue working with library authors to migrate these libraries to use patterns that follow the Rules of React.
+
+
+
+## Rule Details {/*rule-details*/}
+
+Some libraries use patterns that aren't supported by React. When the linter detects usages of these APIs from a [known list](https://github.com/facebook/react/blob/main/compiler/packages/babel-plugin-react-compiler/src/HIR/DefaultModuleTypeProvider.ts), it flags them under this rule. This means that React Compiler can automatically skip over components that use these incompatible APIs, in order to avoid breaking your app.
+
+```js
+// Example of how memoization breaks with these libraries
+function Form() {
+ const { watch } = useForm();
+
+ // ❌ This value will never update, even when 'name' field changes
+ const name = useMemo(() => watch('name'), [watch]);
+
+ return
Name: {name}
; // UI appears "frozen"
+}
+```
+
+React Compiler automatically memoizes values following the Rules of React. If something breaks with manual `useMemo`, it will also break the compiler's automatic optimization. This rule helps identify these problematic patterns.
+
+
+
+#### Designing APIs that follow the Rules of React {/*designing-apis-that-follow-the-rules-of-react*/}
+
+One question to think about when designing a library API or hook is whether calling the API can be safely memoized with `useMemo`. If it can't, then both manual and React Compiler memoizations will break your user's code.
+
+For example, one such incompatible pattern is "interior mutability". Interior mutability is when an object or function keeps its own hidden state that changes over time, even though the reference to it stays the same. Think of it like a box that looks the same on the outside but secretly rearranges its contents. React can't tell anything changed because it only checks if you gave it a different box, not what's inside. This breaks memoization, since React relies on the outer object (or function) changing if part of its value has changed.
+
+As a rule of thumb, when designing React APIs, think about whether `useMemo` would break it:
+
+```js
+function Component() {
+ const { someFunction } = useLibrary();
+ // it should always be safe to memoize functions like this
+ const result = useMemo(() => someFunction(), [someFunction]);
+}
+```
+
+Instead, design APIs that return immutable state and use explicit update functions:
+
+```js
+// ✅ Good: Return immutable state that changes reference when updated
+function Component() {
+ const { field, updateField } = useLibrary();
+ // this is always safe to memo
+ const greeting = useMemo(() => `Hello, ${field.name}!`, [field.name]);
+
+ return (
+
;
+}
+```
+
+
+
+#### MobX {/*mobx*/}
+
+MobX patterns like `observer` also break memoization assumptions, but the linter does not yet detect them. If you rely on MobX and find that your app doesn't work with React Compiler, you may need to use the `"use no memo" directive`.
+
+```js
+// ❌ MobX `observer`
+const Component = observer(() => {
+ const [timer] = useState(() => new Timer());
+ return Seconds passed: {timer.secondsPassed};
+});
+```
+
+
+
+### Valid {/*valid*/}
+
+Examples of correct code for this rule:
+
+```js
+// ✅ For react-hook-form, use `useWatch`:
+function Component() {
+ const {register, control} = useForm();
+ const watchedValue = useWatch({
+ control,
+ name: 'field'
+ });
+
+ return (
+ <>
+
+
Current value: {watchedValue}
+ >
+ );
+}
+```
+
+Some other libraries do not yet have alternative APIs that are compatible with React's memoization model. If the linter doesn't automatically skip over your components or hooks that call these APIs, please [file an issue](https://github.com/facebook/react/issues) so we can add it to the linter.
diff --git a/src/content/reference/eslint-plugin-react-hooks/lints/preserve-manual-memoization.md b/src/content/reference/eslint-plugin-react-hooks/lints/preserve-manual-memoization.md
new file mode 100644
index 000000000..2f296ac56
--- /dev/null
+++ b/src/content/reference/eslint-plugin-react-hooks/lints/preserve-manual-memoization.md
@@ -0,0 +1,102 @@
+---
+title: preserve-manual-memoization
+version: rc
+---
+
+
+
+Validates that existing manual memoization is preserved by the compiler. React Compiler will only compile components and hooks if its inference [matches or exceeds the existing manual memoization](/learn/react-compiler/introduction#what-should-i-do-about-usememo-usecallback-and-reactmemo).
+
+
+
+
+
+This rule is available in the RC version of `eslint-plugin-react-hooks`.
+
+You can try it by upgrading the lint plugin [to the most recent RC version](/learn/react-compiler/installation#eslint-integration).
+
+
+
+## Rule Details {/*rule-details*/}
+
+React Compiler preserves your existing `useMemo`, `useCallback`, and `React.memo` calls. If you've manually memoized something, the compiler assumes you had a good reason and won't remove it. However, incomplete dependencies prevent the compiler from understanding your code's data flow and applying further optimizations.
+
+### Invalid {/*invalid*/}
+
+Examples of incorrect code for this rule:
+
+```js
+// ❌ Missing dependencies in useMemo
+function Component({ data, filter }) {
+ const filtered = useMemo(
+ () => data.filter(filter),
+ [data] // Missing 'filter' dependency
+ );
+
+ return ;
+}
+
+// ❌ Missing dependencies in useCallback
+function Component({ onUpdate, value }) {
+ const handleClick = useCallback(() => {
+ onUpdate(value);
+ }, [onUpdate]); // Missing 'value'
+
+ return ;
+}
+```
+
+### Valid {/*valid*/}
+
+Examples of correct code for this rule:
+
+```js
+// ✅ Complete dependencies
+function Component({ data, filter }) {
+ const filtered = useMemo(
+ () => data.filter(filter),
+ [data, filter] // All dependencies included
+ );
+
+ return ;
+}
+
+// ✅ Or let the compiler handle it
+function Component({ data, filter }) {
+ // No manual memoization needed
+ const filtered = data.filter(filter);
+ return ;
+}
+```
+
+## Troubleshooting {/*troubleshooting*/}
+
+### Should I remove my manual memoization? {/*remove-manual-memoization*/}
+
+You might wonder if React Compiler makes manual memoization unnecessary:
+
+```js
+// Do I still need this?
+function Component({items, sortBy}) {
+ const sorted = useMemo(() => {
+ return [...items].sort((a, b) => {
+ return a[sortBy] - b[sortBy];
+ });
+ }, [items, sortBy]);
+
+ return ;
+}
+```
+
+You can safely remove it if using React Compiler:
+
+```js
+// ✅ Better: Let the compiler optimize
+function Component({items, sortBy}) {
+ const sorted = [...items].sort((a, b) => {
+ return a[sortBy] - b[sortBy];
+ });
+
+ return ;
+}
+```
\ No newline at end of file
diff --git a/src/content/reference/eslint-plugin-react-hooks/lints/purity.md b/src/content/reference/eslint-plugin-react-hooks/lints/purity.md
new file mode 100644
index 000000000..14c870fb5
--- /dev/null
+++ b/src/content/reference/eslint-plugin-react-hooks/lints/purity.md
@@ -0,0 +1,92 @@
+---
+title: purity
+version: rc
+---
+
+
+
+Validates that [components/hooks are pure](/reference/rules/components-and-hooks-must-be-pure) by checking that they do not call known-impure functions.
+
+
+
+
+
+This rule is available in the RC version of `eslint-plugin-react-hooks`.
+
+You can try it by upgrading the lint plugin [to the most recent RC version](/learn/react-compiler/installation#eslint-integration).
+
+
+
+## Rule Details {/*rule-details*/}
+
+React components must be pure functions - given the same props, they should always return the same JSX. When components use functions like `Math.random()` or `Date.now()` during render, they produce different output each time, breaking React's assumptions and causing bugs like hydration mismatches, incorrect memoization, and unpredictable behavior.
+
+## Common Violations {/*common-violations*/}
+
+In general, any API that returns a different value for the same inputs violates this rule. Usual examples include:
+
+- `Math.random()`
+- `Date.now()` / `new Date()`
+- `crypto.randomUUID()`
+- `performance.now()`
+
+### Invalid {/*invalid*/}
+
+Examples of incorrect code for this rule:
+
+```js
+// ❌ Math.random() in render
+function Component() {
+ const id = Math.random(); // Different every render
+ return
;
+}
+```
+
+### Valid {/*valid*/}
+
+Examples of correct code for this rule:
+
+```js
+// ✅ Stable IDs from initial state
+function Component() {
+ const [id] = useState(() => crypto.randomUUID());
+ return
Content
;
+}
+```
+
+## Troubleshooting {/*troubleshooting*/}
+
+### I need to show the current time {/*current-time*/}
+
+Calling `Date.now()` during render makes your component impure:
+
+```js {expectedErrors: {'react-compiler': [3]}}
+// ❌ Wrong: Time changes every render
+function Clock() {
+ return
;
+}
+```
\ No newline at end of file
diff --git a/src/content/reference/eslint-plugin-react-hooks/lints/refs.md b/src/content/reference/eslint-plugin-react-hooks/lints/refs.md
new file mode 100644
index 000000000..78776ba57
--- /dev/null
+++ b/src/content/reference/eslint-plugin-react-hooks/lints/refs.md
@@ -0,0 +1,124 @@
+---
+title: refs
+version: rc
+---
+
+
+
+Validates correct usage of refs, not reading/writing during render. See the "pitfalls" section in [`useRef()` usage](/reference/react/useRef#usage).
+
+
+
+
+
+This rule is available in the RC version of `eslint-plugin-react-hooks`.
+
+You can try it by upgrading the lint plugin [to the most recent RC version](/learn/react-compiler/installation#eslint-integration).
+
+
+
+## Rule Details {/*rule-details*/}
+
+Refs hold values that aren't used for rendering. Unlike state, changing a ref doesn't trigger a re-render. Reading or writing `ref.current` during render breaks React's expectations. Refs might not be initialized when you try to read them, and their values can be stale or inconsistent.
+
+## How It Detects Refs {/*how-it-detects-refs*/}
+
+The lint only applies these rules to values it knows are refs. A value is inferred as a ref when the compiler sees any of the following patterns:
+
+- Returned from `useRef()` or `React.createRef()`.
+
+ ```js
+ const scrollRef = useRef(null);
+ ```
+
+- An identifier named `ref` or ending in `Ref` that reads from or writes to `.current`.
+
+ ```js
+ buttonRef.current = node;
+ ```
+
+- Passed through a JSX `ref` prop (for example ``).
+
+ ```jsx
+
+ ```
+
+Once something is marked as a ref, that inference follows the value through assignments, destructuring, or helper calls. This lets the lint surface violations even when `ref.current` is accessed inside another function that received the ref as an argument.
+
+## Common Violations {/*common-violations*/}
+
+- Reading `ref.current` during render
+- Updating `refs` during render
+- Using `refs` for values that should be state
+
+### Invalid {/*invalid*/}
+
+Examples of incorrect code for this rule:
+
+```js
+// ❌ Reading ref during render
+function Component() {
+ const ref = useRef(0);
+ const value = ref.current; // Don't read during render
+ return
{value}
;
+}
+
+// ❌ Modifying ref during render
+function Component({value}) {
+ const ref = useRef(null);
+ ref.current = value; // Don't modify during render
+ return ;
+}
+```
+
+### Valid {/*valid*/}
+
+Examples of correct code for this rule:
+
+```js
+// ✅ Read ref in effects/handlers
+function Component() {
+ const ref = useRef(null);
+
+ useEffect(() => {
+ if (ref.current) {
+ console.log(ref.current.offsetWidth); // OK in effect
+ }
+ });
+
+ return ;
+}
+
+// ✅ Use state for UI values
+function Component() {
+ const [count, setCount] = useState(0);
+
+ return (
+
+ );
+}
+
+// ✅ Lazy initialization of ref value
+function Component() {
+ const ref = useRef(null);
+
+ // Initialize only once on first use
+ if (ref.current === null) {
+ ref.current = expensiveComputation(); // OK - lazy initialization
+ }
+
+ const handleClick = () => {
+ console.log(ref.current); // Use the initialized value
+ };
+
+ return ;
+}
+```
+
+## Troubleshooting {/*troubleshooting*/}
+
+### The lint flagged my plain object with `.current` {/*plain-object-current*/}
+
+The name heuristic intentionally treats `ref.current` and `fooRef.current` as real refs. If you're modeling a custom container object, pick a different name (for example, `box`) or move the mutable value into state. Renaming avoids the lint because the compiler stops inferring it as a ref.
diff --git a/src/content/reference/eslint-plugin-react-hooks/lints/rules-of-hooks.md b/src/content/reference/eslint-plugin-react-hooks/lints/rules-of-hooks.md
new file mode 100644
index 000000000..6508fc867
--- /dev/null
+++ b/src/content/reference/eslint-plugin-react-hooks/lints/rules-of-hooks.md
@@ -0,0 +1,161 @@
+---
+title: rules-of-hooks
+---
+
+
+
+Validates that components and hooks follow the [Rules of Hooks](/reference/rules/rules-of-hooks).
+
+
+
+## Rule Details {/*rule-details*/}
+
+React relies on the order in which hooks are called to correctly preserve state between renders. Each time your component renders, React expects the exact same hooks to be called in the exact same order. When hooks are called conditionally or in loops, React loses track of which state corresponds to which hook call, leading to bugs like state mismatches and "Rendered fewer/more hooks than expected" errors.
+
+## Common Violations {/*common-violations*/}
+
+These patterns violate the Rules of Hooks:
+
+- **Hooks in conditions** (`if`/`else`, ternary, `&&`/`||`)
+- **Hooks in loops** (`for`, `while`, `do-while`)
+- **Hooks after early returns**
+- **Hooks in callbacks/event handlers**
+- **Hooks in async functions**
+- **Hooks in class methods**
+- **Hooks at module level**
+
+
+
+### `use` hook {/*use-hook*/}
+
+The `use` hook is different from other React hooks. You can call it conditionally and in loops:
+
+```js
+// ✅ `use` can be conditional
+if (shouldFetch) {
+ const data = use(fetchPromise);
+}
+
+// ✅ `use` can be in loops
+for (const promise of promises) {
+ results.push(use(promise));
+}
+```
+
+However, `use` still has restrictions:
+- Can't be wrapped in try/catch
+- Must be called inside a component or hook
+
+Learn more: [`use` API Reference](/reference/react/use)
+
+
+
+### Invalid {/*invalid*/}
+
+Examples of incorrect code for this rule:
+
+```js
+// ❌ Hook in condition
+if (isLoggedIn) {
+ const [user, setUser] = useState(null);
+}
+
+// ❌ Hook after early return
+if (!data) return ;
+const [processed, setProcessed] = useState(data);
+
+// ❌ Hook in callback
+
-[//]: # 'Uncomment the next line, and delete this line after the `useOptimistic` reference documentatino page is published'
+[//]: # 'Uncomment the next line, and delete this line after the `useOptimistic` reference documentation page is published'
[//]: # 'To learn more about the `useOptimistic` Hook see the [reference documentation](/reference/react/useOptimistic).'
### Handling form submission errors {/*handling-form-submission-errors*/}
diff --git a/src/content/reference/react-dom/components/style.md b/src/content/reference/react-dom/components/style.md
index b98b6088c..561c207d1 100644
--- a/src/content/reference/react-dom/components/style.md
+++ b/src/content/reference/react-dom/components/style.md
@@ -49,7 +49,7 @@ React can move `