diff --git a/public/images/docs/diagrams/19_2_batching_after.dark.png b/public/images/docs/diagrams/19_2_batching_after.dark.png new file mode 100644 index 0000000000..29ff140939 Binary files /dev/null and b/public/images/docs/diagrams/19_2_batching_after.dark.png differ diff --git a/public/images/docs/diagrams/19_2_batching_after.png b/public/images/docs/diagrams/19_2_batching_after.png new file mode 100644 index 0000000000..0ae652f792 Binary files /dev/null and b/public/images/docs/diagrams/19_2_batching_after.png differ diff --git a/public/images/docs/diagrams/19_2_batching_before.dark.png b/public/images/docs/diagrams/19_2_batching_before.dark.png new file mode 100644 index 0000000000..758afceb15 Binary files /dev/null and b/public/images/docs/diagrams/19_2_batching_before.dark.png differ diff --git a/public/images/docs/diagrams/19_2_batching_before.png b/public/images/docs/diagrams/19_2_batching_before.png new file mode 100644 index 0000000000..7e260135f0 Binary files /dev/null and b/public/images/docs/diagrams/19_2_batching_before.png differ diff --git a/public/images/docs/performance-tracks/changed-props.dark.png b/public/images/docs/performance-tracks/changed-props.dark.png new file mode 100644 index 0000000000..6709a7ea88 Binary files /dev/null and b/public/images/docs/performance-tracks/changed-props.dark.png differ diff --git a/public/images/docs/performance-tracks/changed-props.png b/public/images/docs/performance-tracks/changed-props.png new file mode 100644 index 0000000000..33efe92893 Binary files /dev/null and b/public/images/docs/performance-tracks/changed-props.png differ diff --git a/public/images/docs/performance-tracks/components-effects.dark.png b/public/images/docs/performance-tracks/components-effects.dark.png new file mode 100644 index 0000000000..57e3a30b09 Binary files /dev/null and b/public/images/docs/performance-tracks/components-effects.dark.png differ diff --git a/public/images/docs/performance-tracks/components-effects.png b/public/images/docs/performance-tracks/components-effects.png new file mode 100644 index 0000000000..ff315b99df Binary files /dev/null and b/public/images/docs/performance-tracks/components-effects.png differ diff --git a/public/images/docs/performance-tracks/components-render.dark.png b/public/images/docs/performance-tracks/components-render.dark.png new file mode 100644 index 0000000000..c0608b153f Binary files /dev/null and b/public/images/docs/performance-tracks/components-render.dark.png differ diff --git a/public/images/docs/performance-tracks/components-render.png b/public/images/docs/performance-tracks/components-render.png new file mode 100644 index 0000000000..4367377670 Binary files /dev/null and b/public/images/docs/performance-tracks/components-render.png differ diff --git a/public/images/docs/performance-tracks/overview.dark.png b/public/images/docs/performance-tracks/overview.dark.png new file mode 100644 index 0000000000..07513fe90b Binary files /dev/null and b/public/images/docs/performance-tracks/overview.dark.png differ diff --git a/public/images/docs/performance-tracks/overview.png b/public/images/docs/performance-tracks/overview.png new file mode 100644 index 0000000000..835a247cf6 Binary files /dev/null and b/public/images/docs/performance-tracks/overview.png differ diff --git a/public/images/docs/performance-tracks/scheduler-cascading-update.dark.png b/public/images/docs/performance-tracks/scheduler-cascading-update.dark.png new file mode 100644 index 0000000000..beb4512d2d Binary files /dev/null and b/public/images/docs/performance-tracks/scheduler-cascading-update.dark.png differ diff --git a/public/images/docs/performance-tracks/scheduler-cascading-update.png b/public/images/docs/performance-tracks/scheduler-cascading-update.png new file mode 100644 index 0000000000..8631c4896f Binary files /dev/null and b/public/images/docs/performance-tracks/scheduler-cascading-update.png differ diff --git a/public/images/docs/performance-tracks/scheduler-update.dark.png b/public/images/docs/performance-tracks/scheduler-update.dark.png new file mode 100644 index 0000000000..df252663a4 Binary files /dev/null and b/public/images/docs/performance-tracks/scheduler-update.dark.png differ diff --git a/public/images/docs/performance-tracks/scheduler-update.png b/public/images/docs/performance-tracks/scheduler-update.png new file mode 100644 index 0000000000..79a361d2aa Binary files /dev/null and b/public/images/docs/performance-tracks/scheduler-update.png differ diff --git a/public/images/docs/performance-tracks/scheduler.dark.png b/public/images/docs/performance-tracks/scheduler.dark.png new file mode 100644 index 0000000000..7e48020f8a Binary files /dev/null and b/public/images/docs/performance-tracks/scheduler.dark.png differ diff --git a/public/images/docs/performance-tracks/scheduler.png b/public/images/docs/performance-tracks/scheduler.png new file mode 100644 index 0000000000..1cd07a1447 Binary files /dev/null and b/public/images/docs/performance-tracks/scheduler.png differ diff --git a/public/images/docs/performance-tracks/server-overview.dark.png b/public/images/docs/performance-tracks/server-overview.dark.png new file mode 100644 index 0000000000..221fb1204a Binary files /dev/null and b/public/images/docs/performance-tracks/server-overview.dark.png differ diff --git a/public/images/docs/performance-tracks/server-overview.png b/public/images/docs/performance-tracks/server-overview.png new file mode 100644 index 0000000000..85c7eed27a Binary files /dev/null and b/public/images/docs/performance-tracks/server-overview.png differ diff --git a/src/components/MDX/Sandpack/template.ts b/src/components/MDX/Sandpack/template.ts index 358c8616ed..ed594887bd 100644 --- a/src/components/MDX/Sandpack/template.ts +++ b/src/components/MDX/Sandpack/template.ts @@ -35,8 +35,8 @@ root.render( eject: 'react-scripts eject', }, dependencies: { - react: '^19.1.0', - 'react-dom': '^19.1.0', + react: '^19.2.0', + 'react-dom': '^19.2.0', 'react-scripts': '^5.0.0', }, }, diff --git a/src/components/MDX/SandpackWithHTMLOutput.tsx b/src/components/MDX/SandpackWithHTMLOutput.tsx index c5149deb94..49e980d324 100644 --- a/src/components/MDX/SandpackWithHTMLOutput.tsx +++ b/src/components/MDX/SandpackWithHTMLOutput.tsx @@ -56,8 +56,8 @@ export default function formatHTML(markup) { const packageJSON = ` { "dependencies": { - "react": "18.3.0-canary-6db7f4209-20231021", - "react-dom": "18.3.0-canary-6db7f4209-20231021", + "react": "^19.2.0", + "react-dom": "^19.2.0", "react-scripts": "^5.0.0", "html-format": "^1.1.2" }, diff --git a/src/content/blog/2025/04/23/react-labs-view-transitions-activity-and-more.md b/src/content/blog/2025/04/23/react-labs-view-transitions-activity-and-more.md index c4447188a1..5f767daf33 100644 --- a/src/content/blog/2025/04/23/react-labs-view-transitions-activity-and-more.md +++ b/src/content/blog/2025/04/23/react-labs-view-transitions-activity-and-more.md @@ -18,11 +18,17 @@ description: 在 React Labs 系列文章中,我们会介绍正在积极研究 +<<<<<<< HEAD React Conf 2025 将于 10 月 7-8 日在内华达州亨德森举行! 我们正在寻找演讲者,帮助我们创建关于本文所涵盖功能的演讲。如果你有兴趣在 ReactConf 上发言,[请在此申请](https://forms.reform.app/react-conf/call-for-speakers/)(无需提交演讲提案)。 有关门票、免费直播、赞助等更多信息,请查看 [React Conf 网站](https://conf.react.dev)。 +======= +React Conf 2025 is scheduled for October 7–8 in Henderson, Nevada! + +Watch the livestream on [the React Conf website](https://conf.react.dev). +>>>>>>> 996ef72c1a9f7ecc003588a897be85fc647393c3 @@ -11544,7 +11550,7 @@ function App() { ```js src/App.js -import { unstable_ViewTransition as ViewTransition, unstable_Activity as Activity } from "react"; import Details from "./Details"; import Home from "./Home"; import { useRouter } from "./router"; +import { unstable_ViewTransition as ViewTransition } from "react"; import Details from "./Details"; import Home from "./Home"; import { useRouter } from "./router"; import { unstable_Activity, Activity as ActivityStable} from 'react'; let Activity = ActivityStable ?? unstable_Activity; export default function App() { const { url } = useRouter(); @@ -12881,7 +12887,7 @@ With this update, if the content on the next page has time to pre-render, it wil ```js src/App.js -import { unstable_ViewTransition as ViewTransition, unstable_Activity as Activity, use } from "react"; import Details from "./Details"; import Home from "./Home"; import { useRouter } from "./router"; import {fetchVideos} from './data' +import { unstable_ViewTransition as ViewTransition, use } from "react"; import Details from "./Details"; import Home from "./Home"; import { useRouter } from "./router"; import {fetchVideos} from './data'; import { unstable_Activity, Activity as ActivityStable} from 'react'; let Activity = ActivityStable ?? unstable_Activity; export default function App() { const { url } = useRouter(); diff --git a/src/content/blog/2025/10/01/react-19-2.md b/src/content/blog/2025/10/01/react-19-2.md new file mode 100644 index 0000000000..1162e05552 --- /dev/null +++ b/src/content/blog/2025/10/01/react-19-2.md @@ -0,0 +1,339 @@ +--- +title: "React 19.2" +author: The React Team +date: 2025/10/01 +description: React 19.2 adds new features like Activity, React Performance Tracks, useEffectEvent, and more. +--- + +October 1, 2025 by [The React Team](/community/team) + +--- + + + +React 19.2 is now available on npm! + + + +This is our third release in the last year, following React 19 in December and React 19.1 in June. In this post, we'll give an overview of the new features in React 19.2, and highlight some notable changes. + + + +--- + +## New React Features {/*new-react-features*/} + +### `` {/*activity*/} + +`` lets you break your app into "activities" that can be controlled and prioritized. + +You can use Activity as an alternative to conditionally rendering parts of your app: + +```js +// Before +{isVisible && } + +// After + + + +``` + +In React 19.2, Activity supports two modes: `visible` and `hidden`. + +- `hidden`: hides the children, unmounts effects, and defers all updates until React has nothing left to work on. +- `visible`: shows the children, mounts effects, and allows updates to be processed normally. + +This means you can pre-render and keep rendering hidden parts of the app without impacting the performance of anything visible on screen. + +You can use Activity to render hidden parts of the app that a user is likely to navigate to next, or to save the state of parts the user navigates away from. This helps make navigations quicker by loading data, css, and images in the background, and allows back navigations to maintain state such as input fields. + +In the future, we plan to add more modes to Activity for different use cases. + +For examples on how to use Activity, check out the [Activity docs](/reference/react/Activity). + +--- + +### `useEffectEvent` {/*use-effect-event*/} + +One common pattern with `useEffect` is to notify the app code about some kind of "events" from an external system. For example, when a chat room gets connected, you might want to display a notification: + +```js {5,11} +function ChatRoom({ roomId, theme }) { + useEffect(() => { + const connection = createConnection(serverUrl, roomId); + connection.on('connected', () => { + showNotification('Connected!', theme); + }); + connection.connect(); + return () => { + connection.disconnect() + }; + }, [roomId, theme]); + // ... +``` + +The problem with the code above is that a change to any values used inside such an "event" will cause the surrounding Effect to re-run. For example, changing the `theme` will cause the chat room to reconnect. This makes sense for values related to the Effect logic itself, like `roomId`, but it doesn't make sense for `theme`. + +To solve this, most users just disable the lint rule and exclude the dependency. But that can lead to bugs since the linter can no longer help you keep the dependencies up to date if you need to update the Effect later. + +With `useEffectEvent`, you can split the "event" part of this logic out of the Effect that emits it: + +```js {2,3,4,9} +function ChatRoom({ roomId, theme }) { + const onConnected = useEffectEvent(() => { + showNotification('Connected!', theme); + }); + + useEffect(() => { + const connection = createConnection(serverUrl, roomId); + connection.on('connected', () => { + onConnected(); + }); + connection.connect(); + return () => connection.disconnect(); + }, [roomId]); // ✅ All dependencies declared (Effect Events aren't dependencies) + // ... +``` + +Similar to DOM events, Effect Events always “see” the latest props and state. + +**Effect Events should _not_ be declared in the dependency array**. You'll need to upgrade to `eslint-plugin-react-hooks@6.1.0` so that the linter doesn't try to insert them as dependencies. Note that Effect Events can only be declared in the same component or Hook as "their" Effect. These restrictions are verified by the linter. + + + +#### When to use `useEffectEvent` {/*when-to-use-useeffectevent*/} + +You should use `useEffectEvent` for functions that are conceptually "events" that happen to be fired from an Effect instead of a user event (that's what makes it an "Effect Event"). You don't need to wrap everything in `useEffectEvent`, or to use it just to silence the lint error, as this can lead to bugs. + +For a deep dive on how to think about Event Effects, see: [Separating Events from Effects](/learn/separating-events-from-effects#extracting-non-reactive-logic-out-of-effects). + + + +--- + +### `cacheSignal` {/*cache-signal*/} + + + +`cacheSignal` is only for use with [React Server Components](/reference/rsc/server-components). + + + +`cacheSignal` allows you to know when the [`cache()`](/reference/react/cache) lifetime is over: + +``` +import {cache, cacheSignal} from 'react'; +const dedupedFetch = cache(fetch); + +async function Component() { + await dedupedFetch(url, { signal: cacheSignal() }); +} +``` + +This allows you to clean up or abort work when the result will no longer be used in the cache, such as: + +- React has successfully completed rendering +- The render was aborted +- The render has failed + +For more info, see the [`cacheSignal` docs](/reference/react/cacheSignal). + +--- + +### Performance Tracks {/*performance-tracks*/} + +React 19.2 adds a new set of [custom tracks](https://developer.chrome.com/docs/devtools/performance/extension) to Chrome DevTools performance profiles to provide more information about the performance of your React app: + +
+ + + + + + + + +
+ +The [React Performance Tracks docs](/reference/dev-tools/react-performance-tracks) explain everything included in the tracks, but here is a high-level overview. + +#### Scheduler ⚛ {/*scheduler-*/} + +The Scheduler track shows what React is working on for different priorities such as "blocking" for user interactions, or "transition" for updates inside startTransition. Inside each track, you will see the type of work being performed such as the event that scheduled an update, and when the render for that update happened. + +We also show information such as when an update is blocked waiting for a different priority, or when React is waiting for paint before continuing. The Scheduler track helps you understand how React splits your code into different priorities, and the order it completed the work. + +See the [Scheduler track](/reference/dev-tools/react-performance-tracks#scheduler) docs to see everything included. + +#### Components ⚛ {/*components-*/} + +The Components track shows the tree of components that React is working on either to render or run effects. Inside you'll see labels such as "Mount" for when children mount or effects are mounted, or "Blocked" for when rendering is blocked due to yielding to work outside React. + +The Component track helps you understand when components are rendered or run effects, and the time it takes to complete that work to help identify performance problems. + +See the [Component track docs](/reference/dev-tools/react-performance-tracks#components) for see everything included. + +--- + +## New React DOM Features {/*new-react-dom-features*/} + +### Partial Pre-rendering {/*partial-pre-rendering*/} + +In 19.2 we're adding a new capability to pre-render part of the app ahead of time, and resume rendering it later. + +This feature is called "Partial Pre-rendering", and allows you to pre-render the static parts of your app and serve it from a CDN, and then resume rendering the shell to fill it in with dynamic content later. + +To pre-render an app to resume later, first call `prerender` with an `AbortController`: + +``` +const {prelude, postponed} = await prerender(, { + signal: controller.signal, +}); + +// Save the postponed state for later +await savePostponedState(postponed); + +// Send prelude to client or CDN. +``` + +Then, you can return the `prelude` shell to the client, and later call `resume` to "resume" to a SSR stream: + +``` +const postponed = await getPostponedState(request); +const resumeStream = await resume(, postponed); + +// Send stream to client. +``` + +Or you can call `resumeAndPrerender` to resume to get static HTML for SSG: + +``` +const postponedState = await getPostponedState(request); +const { prelude } = await resumeAndPrerender(, postponedState); + +// Send complete HTML prelude to CDN. +``` + +For more info, see the docs for the new APIs: +- `react-dom/server` + - [`resume`](/reference/react-dom/server/resume): for Web Streams. + - [`resumeToPipeableStream`](/reference/react-dom/server/resumeToPipeableStream) for Node Streams. +- `react-dom/static` + - [`resumeAndPrerender`](/reference/react-dom/static/resumeAndPrerender) for Web Streams. + - [`resumeAndPrerenderToNodeStream`](/reference/react-dom/static/resumeAndPrerenderToNodeStream) for Node Streams. + +Additionally, the prerender apis now return a `postpone` state to pass to the `resume` apis. + +--- + +## Notable Changes {/*notable-changes*/} + +### Batching Suspense Boundaries for SSR {/*batching-suspense-boundaries-for-ssr*/} + +We fixed a behavioral bug where Suspense boundaries would reveal differently depending on if they were rendered on the client or when streaming from server-side rendering. + +Starting in 19.2, React will batch reveals of server-rendered Suspense boundaries for a short time, to allow more content to be revealed together and align with the client-rendered behavior. + + + +Previously, during streaming server-side rendering, suspense content would immediately replace fallbacks. + + + + + +In React 19.2, suspense boundaries are batched for a small amount of time, to allow revealing more content together. + + + +This fix also prepares apps for supporting `` for Suspense during SSR. By revealing more content together, animations can run in larger batches of content, and avoid chaining animations of content that stream in close together. + + + +React uses heuristics to ensure throttling does not impact core web vitals and search ranking. + +For example, if the total page load time is approaching 2.5s (which is the time considered "good" for [LCP](https://web.dev/articles/lcp)), React will stop batching and reveal content immediately so that the throttling is not the reason to miss the metric. + + + +--- + +### SSR: Web Streams support for Node {/*ssr-web-streams-support-for-node*/} + +React 19.2 adds support for Web Streams for streaming SSR in Node.js: +- [`renderToReadableStream`](/reference/react-dom/server/renderToReadableStream) is now available for Node.js +- [`prerender`](/reference/react-dom/static/prerender) is now available for Node.js + +As well as the new `resume` APIs: +- [`resume`](/reference/react-dom/server/resume): is available for Node.js. +- [`resumeAndPrerender`](/reference/react-dom/static/resumeAndPrerender) is available for Node.js. + + + + +#### Prefer Node Streams for server-side rendering in Node.js {/*prefer-node-streams-for-server-side-rendering-in-nodejs*/} + +In Node.js environments, we still highly recommend using the Node Streams APIs: + +- [`renderToPipeableStream`](/reference/react-dom/server/renderToPipeableStream) +- [`resumeToPipeableStream`](/reference/react-dom/server/resumeToPipeableStream) +- [`prerenderToNodeStream`](/reference/react-dom/static/prerenderToNodeStream) +- [`resumeAndPrerenderToNodeStream`](/reference/react-dom/static/resumeAndPrerenderToNodeStream) + +This is because Node Streams are much faster than Web Streams in Node, and Web Streams do not support compression by default, leading to users accidentally missing the benefits of streaming. + + + +--- + +### `eslint-plugin-react-hooks` v6 {/*eslint-plugin-react-hooks*/} + +We also published `eslint-plugin-react-hooks@6.1.0` with flat config by default in the `recommended` preset, and opt-in for new React Compiler powered rules. + +To continue using the legacy config, you can change to `recommended-legacy`: + +```diff +- extends: ['plugin:react-hooks/recommended'] ++ extends: ['plugin:react-hooks/recommended-legacy'] +``` + +For a full list of compiler enabled rules, [check out the linter docs](/reference/eslint-plugin-react-hooks#additional-rules). + +Check out the `eslint-plugin-react-hooks` [changelog for a full list of changes](https://github.com/facebook/react/blob/main/packages/eslint-plugin-react-hooks/CHANGELOG.md#610). + +--- + +### Update the default `useId` prefix {/*update-the-default-useid-prefix*/} + +In 19.2, we're updating the default `useId` prefix from `:r:` (19.0.0) or `«r»` (19.1.0) to `_r_`. + +The original intent of using a special character that was not valid for CSS selectors was that it would be unlikely to collide with IDs written by users. However, to support View Transitions, we need to ensure that IDs generated by `useId` are valid for `view-transition-name` and XML 1.0 names. + +--- + +## Changelog {/*changelog*/} + +Other notable changes +- `react-dom`: Allow nonce to be used on hoistable styles [#32461](https://github.com/facebook/react/pull/32461) +- `react-dom`: Warn for using a React owned node as a Container if it also has text content [#32774](https://github.com/facebook/react/pull/32774) + +Notable bug fixes +- `react`: Stringify context as "SomeContext" instead of "SomeContext.Provider" [#33507](https://github.com/facebook/react/pull/33507) +- `react`: Fix infinite useDeferredValue loop in popstate event [#32821](https://github.com/facebook/react/pull/32821) +- `react`: Fix a bug when an initial value was passed to useDeferredValue [#34376](https://github.com/facebook/react/pull/34376) +- `react`: Fix a crash when submitting forms with Client Actions [#33055](https://github.com/facebook/react/pull/33055) +- `react`: Hide/unhide the content of dehydrated suspense boundaries if they resuspend [#32900](https://github.com/facebook/react/pull/32900) +- `react`: Avoid stack overflow on wide trees during Hot Reload [#34145](https://github.com/facebook/react/pull/34145) +- `react`: Improve component stacks in various places [#33629](https://github.com/facebook/react/pull/33629), [#33724](https://github.com/facebook/react/pull/33724), [#32735](https://github.com/facebook/react/pull/32735), [#33723](https://github.com/facebook/react/pull/33723) +- `react`: Fix a bug with React.use inside React.lazy-ed Component [#33941](https://github.com/facebook/react/pull/33941) +- `react-dom`: Stop warning when ARIA 1.3 attributes are used [#34264](https://github.com/facebook/react/pull/34264) +- `react-dom`: Fix a bug with deeply nested Suspense inside Suspense fallbacks [#33467](https://github.com/facebook/react/pull/33467) +- `react-dom`: Avoid hanging when suspending after aborting while rendering [#34192](https://github.com/facebook/react/pull/34192) + +For a full list of changes, please see the [Changelog](https://github.com/facebook/react/blob/main/CHANGELOG.md). + + +--- + +_Thanks to [Ricky Hanlon](https://bsky.app/profile/ricky.fm) for [writing this post](https://www.youtube.com/shorts/T9X3YkgZRG0), [Dan Abramov](https://bsky.app/profile/danabra.mov), [Matt Carroll](https://twitter.com/mattcarrollcode), [Jack Pope](https://jackpope.me), and [Joe Savona](https://x.com/en_JS) for reviewing this post._ diff --git a/src/content/blog/index.md b/src/content/blog/index.md index 2c19a3a860..4c1dfe59cd 100644 --- a/src/content/blog/index.md +++ b/src/content/blog/index.md @@ -11,6 +11,12 @@ title: React Blog
+ + +React 19.2 adds new features like Activity, React Performance Tracks, useEffectEvent, and more. In this post ... + + + In React Labs posts, we write about projects in active research and development. In this post, we're sharing two new experimental features that are ready to try today, and sharing other areas we're working on now ... diff --git a/src/content/learn/escape-hatches.md b/src/content/learn/escape-hatches.md index e54b0dc6d7..69b01525d8 100644 --- a/src/content/learn/escape-hatches.md +++ b/src/content/learn/escape-hatches.md @@ -312,6 +312,7 @@ React 提供了检查工具规则来检查是否正确地指定了 Effect 的依 ## 将事件从 Effect 中分开 {/*separating-events-from-effects*/} +<<<<<<< HEAD 本节描述了一个在稳定版本的 React 中 **尚未发布** 的实验性 API。 @@ -319,6 +320,9 @@ React 提供了检查工具规则来检查是否正确地指定了 Effect 的依 事件处理程序仅在再次执行相同的交互时重新运行。与事件处理程序不同,如果 Effect 读取的任何值(如 props 或 state)与上次渲染期间不同,则会重新同步。有时,需要混合两种行为:Effect 重新运行以响应某些值而不是其他值。 +======= +Event handlers only re-run when you perform the same interaction again. Unlike event handlers, Effects re-synchronize if any of the values they read, like props or state, are different than during last render. Sometimes, you want a mix of both behaviors: an Effect that re-runs in response to some values but not others. +>>>>>>> 996ef72c1a9f7ecc003588a897be85fc647393c3 Effect 中的所有代码都是 **响应式的**。如果它读取的某些响应式的值由于重新渲染而发生变化,它将再次运行。例如,如果 `roomId` 或 `theme` 发生变化,这个 Effect 将重新连接到聊天: @@ -455,8 +459,8 @@ label { display: block; margin-top: 10px; } ```json package.json hidden { "dependencies": { - "react": "canary", - "react-dom": "canary", + "react": "latest", + "react-dom": "latest", "react-scripts": "latest", "toastify-js": "1.12.0" }, diff --git a/src/content/learn/removing-effect-dependencies.md b/src/content/learn/removing-effect-dependencies.md index 5ffb55aae5..638e6c5d8f 100644 --- a/src/content/learn/removing-effect-dependencies.md +++ b/src/content/learn/removing-effect-dependencies.md @@ -610,6 +610,7 @@ function ChatRoom({ roomId }) { ### 你想读取一个值而不对其变化做出“反应”吗? {/*do-you-want-to-read-a-value-without-reacting-to-its-changes*/} +<<<<<<< HEAD **`useEffectEvent` API 当前仅在 React Canary 和 实验发行版中可用**。 @@ -619,6 +620,9 @@ function ChatRoom({ roomId }) { 假设你希望在用户收到新消息时播放声音,`isMuted` 为 `true` 除外: +======= +Suppose that you want to play a sound when the user receives a new message unless `isMuted` is `true`: +>>>>>>> 996ef72c1a9f7ecc003588a897be85fc647393c3 ```js {3,10-12} function ChatRoom({ roomId }) { @@ -1260,22 +1264,6 @@ Effect 中是否有一行代码不应该是响应式的?如何将非响应式 -```json package.json hidden -{ - "dependencies": { - "react": "canary", - "react-dom": "canary", - "react-scripts": "latest" - }, - "scripts": { - "start": "react-scripts start", - "build": "react-scripts build", - "test": "react-scripts test --env=jsdom", - "eject": "react-scripts eject" - } -} -``` - ```js import { useState, useEffect, useRef } from 'react'; import { useEffectEvent } from 'react'; @@ -1387,22 +1375,6 @@ Effect 需要读取 `duration` 的最新值,但你不希望它对 `duration` -```json package.json hidden -{ - "dependencies": { - "react": "canary", - "react-dom": "canary", - "react-scripts": "latest" - }, - "scripts": { - "start": "react-scripts start", - "build": "react-scripts build", - "test": "react-scripts test --env=jsdom", - "eject": "react-scripts eject" - } -} -``` - ```js import { useState, useEffect, useRef } from 'react'; import { FadeInAnimation } from './animation.js'; @@ -1826,8 +1798,8 @@ label, button { display: block; margin-bottom: 5px; } ```json package.json hidden { "dependencies": { - "react": "canary", - "react-dom": "canary", + "react": "latest", + "react-dom": "latest", "react-scripts": "latest", "toastify-js": "1.12.0" }, @@ -2121,8 +2093,8 @@ export default function ChatRoom({ roomId, isEncrypted, onMessage }) { // Reacti ```json package.json hidden { "dependencies": { - "react": "canary", - "react-dom": "canary", + "react": "latest", + "react-dom": "latest", "react-scripts": "latest", "toastify-js": "1.12.0" }, diff --git a/src/content/learn/reusing-logic-with-custom-hooks.md b/src/content/learn/reusing-logic-with-custom-hooks.md index 830b5b5fa0..8c1259a356 100644 --- a/src/content/learn/reusing-logic-with-custom-hooks.md +++ b/src/content/learn/reusing-logic-with-custom-hooks.md @@ -837,6 +837,7 @@ export default function ChatRoom({ roomId }) { ### 把事件处理函数传到自定义 Hook 中 {/*passing-event-handlers-to-custom-hooks*/} +<<<<<<< HEAD **`useEffectEvent` API 当前仅在 React Canary 和 实验发行版中可用**。 @@ -846,6 +847,9 @@ export default function ChatRoom({ roomId }) { 当你在更多组件中使用 `useChatRoom` 时,你可能希望组件能定制它的行为。例如现在 Hook 内部收到消息的处理逻辑是硬编码: +======= +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: +>>>>>>> 996ef72c1a9f7ecc003588a897be85fc647393c3 ```js {9-11} export function useChatRoom({ serverUrl, roomId }) { @@ -1072,8 +1076,8 @@ export function showNotification(message, theme = 'dark') { ```json package.json hidden { "dependencies": { - "react": "canary", - "react-dom": "canary", + "react": "latest", + "react-dom": "latest", "react-scripts": "latest", "toastify-js": "1.12.0" }, @@ -1718,22 +1722,6 @@ html, body { min-height: 300px; } } ``` -```json package.json hidden -{ - "dependencies": { - "react": "canary", - "react-dom": "canary", - "react-scripts": "latest" - }, - "scripts": { - "start": "react-scripts start", - "build": "react-scripts build", - "test": "react-scripts test --env=jsdom", - "eject": "react-scripts eject" - } -} -``` - 但是 **没有必要** 这样做。和常规函数一样,最终是由你决定在哪里划分代码不同部分之间的边界。你也可以采取不一样的方法。把大部分必要的逻辑移入一个 [JavaScript 类](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Classes),而不是把逻辑保留在 Effect 中: @@ -2207,22 +2195,6 @@ export function useInterval(onTick, delay) { -```json package.json hidden -{ - "dependencies": { - "react": "canary", - "react-dom": "canary", - "react-scripts": "latest" - }, - "scripts": { - "start": "react-scripts start", - "build": "react-scripts build", - "test": "react-scripts test --env=jsdom", - "eject": "react-scripts eject" - } -} -``` - ```js import { useCounter } from './useCounter.js'; import { useInterval } from './useInterval.js'; @@ -2278,22 +2250,6 @@ export function useInterval(onTick, delay) { -```json package.json hidden -{ - "dependencies": { - "react": "canary", - "react-dom": "canary", - "react-scripts": "latest" - }, - "scripts": { - "start": "react-scripts start", - "build": "react-scripts build", - "test": "react-scripts test --env=jsdom", - "eject": "react-scripts eject" - } -} -``` - ```js import { useCounter } from './useCounter.js'; diff --git a/src/content/learn/separating-events-from-effects.md b/src/content/learn/separating-events-from-effects.md index 6d2991e4c5..228c177775 100644 --- a/src/content/learn/separating-events-from-effects.md +++ b/src/content/learn/separating-events-from-effects.md @@ -400,6 +400,7 @@ label { display: block; margin-top: 10px; } ### 声明一个 Effect Event {/*declaring-an-effect-event*/} +<<<<<<< HEAD **`useEffectEvent` API 当前仅在 React Canary 和 实验发行版中可用**。 @@ -409,6 +410,9 @@ label { display: block; margin-top: 10px; } 使用 [`useEffectEvent`](/reference/react/useEffectEvent) 这个特殊的 Hook 从 Effect 中提取非响应式逻辑: +======= +Use a special Hook called [`useEffectEvent`](/reference/react/useEffectEvent) to extract this non-reactive logic out of your Effect: +>>>>>>> 996ef72c1a9f7ecc003588a897be85fc647393c3 ```js {1,4-6} import { useEffect, useEffectEvent } from 'react'; @@ -450,8 +454,8 @@ function ChatRoom({ roomId, theme }) { ```json package.json hidden { "dependencies": { - "react": "canary", - "react-dom": "canary", + "react": "latest", + "react-dom": "latest", "react-scripts": "latest", "toastify-js": "1.12.0" }, @@ -580,6 +584,7 @@ label { display: block; margin-top: 10px; } ### 使用 Effect Event 读取最新的 props 和 state {/*reading-latest-props-and-state-with-effect-events*/} +<<<<<<< HEAD **`useEffectEvent` API 当前仅在 React Canary 和 实验发行版中可用**。 @@ -589,6 +594,9 @@ label { display: block; margin-top: 10px; } Effect Event 可以修复之前许多你可能试图抑制依赖项检查工具的地方。 +======= +Effect Events let you fix many patterns where you might be tempted to suppress the dependency linter. +>>>>>>> 996ef72c1a9f7ecc003588a897be85fc647393c3 例如,假设你有一个记录页面访问的 Effect: @@ -729,7 +737,11 @@ function Page({ url }) { } ``` +<<<<<<< HEAD 等 `useEffectEvent` 成为 React 稳定部分后,我们会推荐 **永远不要抑制代码检查工具**。 +======= +We recommend **never suppressing the linter**. +>>>>>>> 996ef72c1a9f7ecc003588a897be85fc647393c3 抑制规则的第一个缺点是当 Effect 需要对一个已经在代码中出现过的新响应式依赖项做出“响应”时,React 不会再发出警告。在稍早之前的示例中,你将 `url` 添加为依赖项,**是因为** React 提醒你去做这件事。如果禁用代码检查,你未来将不会再收到任何关于 Effect 修改的提醒。这引起了 bug。 @@ -804,22 +816,6 @@ body { -```json package.json hidden -{ - "dependencies": { - "react": "canary", - "react-dom": "canary", - "react-scripts": "latest" - }, - "scripts": { - "start": "react-scripts start", - "build": "react-scripts build", - "test": "react-scripts test --env=jsdom", - "eject": "react-scripts eject" - } -} -``` - ```js import { useState, useEffect } from 'react'; import { useEffectEvent } from 'react'; @@ -882,6 +878,7 @@ body { ### Effect Event 的局限性 {/*limitations-of-effect-events*/} +<<<<<<< HEAD **`useEffectEvent` API 当前仅在 React Canary 和 实验发行版中可用**。 @@ -891,6 +888,9 @@ body { Effect Event 的局限性在于你如何使用他们: +======= +Effect Events are very limited in how you can use them: +>>>>>>> 996ef72c1a9f7ecc003588a897be85fc647393c3 * **只在 Effect 内部调用他们**。 * **永远不要把他们传给其他的组件或者 Hook**。 @@ -979,23 +979,6 @@ Effect Event 是 Effect 代码的非响应式“片段”。他们应该在使 -```json package.json hidden -{ - "dependencies": { - "react": "canary", - "react-dom": "canary", - "react-scripts": "latest" - }, - "scripts": { - "start": "react-scripts start", - "build": "react-scripts build", - "test": "react-scripts test --env=jsdom", - "eject": "react-scripts eject" - } -} -``` - - ```js {expectedErrors: {'react-compiler': [14]}} import { useState, useEffect } from 'react'; @@ -1049,22 +1032,6 @@ button { margin: 10px; } -```json package.json hidden -{ - "dependencies": { - "react": "canary", - "react-dom": "canary", - "react-scripts": "latest" - }, - "scripts": { - "start": "react-scripts start", - "build": "react-scripts build", - "test": "react-scripts test --env=jsdom", - "eject": "react-scripts eject" - } -} -``` - ```js import { useState, useEffect } from 'react'; @@ -1127,22 +1094,6 @@ button { margin: 10px; } -```json package.json hidden -{ - "dependencies": { - "react": "canary", - "react-dom": "canary", - "react-scripts": "latest" - }, - "scripts": { - "start": "react-scripts start", - "build": "react-scripts build", - "test": "react-scripts test --env=jsdom", - "eject": "react-scripts eject" - } -} -``` - ```js import { useState, useEffect } from 'react'; import { useEffectEvent } from 'react'; @@ -1196,22 +1147,6 @@ button { margin: 10px; } -```json package.json hidden -{ - "dependencies": { - "react": "canary", - "react-dom": "canary", - "react-scripts": "latest" - }, - "scripts": { - "start": "react-scripts start", - "build": "react-scripts build", - "test": "react-scripts test --env=jsdom", - "eject": "react-scripts eject" - } -} -``` - ```js import { useState, useEffect } from 'react'; import { useEffectEvent } from 'react'; @@ -1278,22 +1213,6 @@ Effect Event 内部的代码是非响应式的。哪些情况下你会 **想要* -```json package.json hidden -{ - "dependencies": { - "react": "canary", - "react-dom": "canary", - "react-scripts": "latest" - }, - "scripts": { - "start": "react-scripts start", - "build": "react-scripts build", - "test": "react-scripts test --env=jsdom", - "eject": "react-scripts eject" - } -} -``` - ```js import { useState, useEffect } from 'react'; import { useEffectEvent } from 'react'; @@ -1365,22 +1284,6 @@ button { margin: 10px; } -```json package.json hidden -{ - "dependencies": { - "react": "canary", - "react-dom": "canary", - "react-scripts": "latest" - }, - "scripts": { - "start": "react-scripts start", - "build": "react-scripts build", - "test": "react-scripts test --env=jsdom", - "eject": "react-scripts eject" - } -} -``` - ```js import { useState, useEffect } from 'react'; import { useEffectEvent } from 'react'; @@ -1464,8 +1367,8 @@ button { margin: 10px; } ```json package.json hidden { "dependencies": { - "react": "canary", - "react-dom": "canary", + "react": "latest", + "react-dom": "latest", "react-scripts": "latest", "toastify-js": "1.12.0" }, @@ -1605,8 +1508,8 @@ Effect Event 伴随着两秒的延迟被调用。如果你快速地从 travel ```json package.json hidden { "dependencies": { - "react": "canary", - "react-dom": "canary", + "react": "latest", + "react-dom": "latest", "react-scripts": "latest", "toastify-js": "1.12.0" }, @@ -1743,8 +1646,8 @@ label { display: block; margin-top: 10px; } ```json package.json hidden { "dependencies": { - "react": "canary", - "react-dom": "canary", + "react": "latest", + "react-dom": "latest", "react-scripts": "latest", "toastify-js": "1.12.0" }, diff --git a/src/content/reference/dev-tools/react-performance-tracks.md b/src/content/reference/dev-tools/react-performance-tracks.md new file mode 100644 index 0000000000..4f613f802f --- /dev/null +++ b/src/content/reference/dev-tools/react-performance-tracks.md @@ -0,0 +1,152 @@ +--- +title: React Performance tracks +--- + + + +React Performance tracks are specialized custom entries that appear on the Performance panel's timeline in your browser developer tools. + + + +These tracks are designed to provide developers with comprehensive insights into their React application's performance by visualizing React-specific events and metrics alongside other critical data sources such as network requests, JavaScript execution, and event loop activity, all synchronized on a unified timeline within the Performance panel for a complete understanding of application behavior. + +
+ React Performance Tracks + React Performance Tracks +
+ + + +--- + +## Usage {/*usage*/} + +React Performance tracks are only available in development and profiling builds of React: + +- **Development**: enabled by default. +- **Profiling**: you can either wrap a subtree that you want to instrument with [``](/reference/react/Profiler) component or have [React Developer Tools extension](/learn/react-developer-tools) enabled. Tracks specific to React Server Components are not enabled in profiling builds. + +If enabled, tracks should appear automatically in the traces you record with the Performance panel of browsers that provide [extensibility APIs](https://developer.chrome.com/docs/devtools/performance/extension). + + + +The profiling instrumentation that powers React Performance tracks adds some additional overhead, so it is disabled in production builds by default. +Server Components and Server Requests tracks are only available in development builds. + + + +--- + +## Tracks {/*tracks*/} + +### Scheduler {/*scheduler*/} + +The Scheduler is an internal React concept used for managing tasks with different priorities. This track consists of 4 subtracks, each representing work of a specific priority: + +- **Blocking** - The synchronous updates, which could've been initiated by user interactions. +- **Transition** - Non-blocking work that happens in the background, usually initiated via [`startTransition`](/reference/react/startTransition). +- **Suspense** - Work related to Suspense boundaries, such as displaying fallbacks or revealing content. +- **Idle** - The lowest priority work that is done when there are no other tasks with higher priority. + +
+ Scheduler track + Scheduler track +
+ +#### Renders {/*renders*/} + +Every render pass consists of multiple phases that you can see on a timeline: + +- **Update** - this is what caused a new render pass. +- **Render** - React renders the updated subtree by calling render functions of components. You can see the rendered components subtree on [Components track](#components), which follows the same color scheme. +- **Commit** - After rendering components, React will submit the changes to the DOM and run layout effects, like [`useLayoutEffect`](/reference/react/useLayoutEffect). +- **Remaining Effects** - React runs passive effects of a rendered subtree. This usually happens after the paint, and this is when React runs hooks like [`useEffect`](/reference/react/useEffect). One known exception is user interactions, like clicks, or other discrete events. In this scenario, this phase could run before the paint. + +
+ Scheduler track: updates + Scheduler track: updates +
+ +[Learn more about renders and commits](/learn/render-and-commit). + +#### Cascading updates {/*cascading-updates*/} + +Cascading updates is one of the patterns for performance regressions. If an update was scheduled during a render pass, React could discard completed work and start a new pass. + +In development builds, React can show you which Component scheduled a new update. This includes both general updates and cascading ones. You can see the enhanced stack trace by clicking on the "Cascading update" entry, which should also display the name of the method that scheduled an update. + +
+ Scheduler track: cascading updates + Scheduler track: cascading updates +
+ +[Learn more about Effects](/learn/you-might-not-need-an-effect). + +### Components {/*components*/} + +The Components track visualizes the durations of React components. They are displayed as a flamegraph, where each entry represents the duration of the corresponding component render and all its descendant children components. + +
+ Components track: render durations + Components track: render durations +
+ +Similar to render durations, effect durations are also represented as a flamegraph, but with a different color scheme that aligns with the corresponding phase on the Scheduler track. + +
+ Components track: effects durations + Components track: effects durations +
+ + + +Unlike renders, not all effects are shown on the Components track by default. + +To maintain performance and prevent UI clutter, React will only display those effects, which had a duration of 0.05ms or longer, or triggered an update. + + + +Additional events may be displayed during the render and effects phases: + +- Mount - A corresponding subtree of component renders or effects was mounted. +- Unmount - A corresponding subtree of component renders or effects was unmounted. +- Reconnect - Similar to Mount, but limited to cases when [``](/reference/react/Activity) is used. +- Disconnect - Similar to Unmount, but limited to cases when [``](/reference/react/Activity) is used. + +#### Changed props {/*changed-props*/} + +In development builds, when you click on a component render entry, you can inspect potential changes in props. You can use this information to identify unnecessary renders. + +
+ Components track: changed props + Components track: changed props +
+ +### Server {/*server*/} + +
+ React Server Performance Tracks + React Server Performance Tracks +
+ +#### Server Requests {/*server-requests*/} + +The Server Requests track visualized all Promises that eventually end up in a React Server Component. This includes any `async` operations like calling `fetch` or async Node.js file operations. + +React will try to combine Promises that are started from inside third-party code into a single span representing the the duration of the entire operation blocking 1st party code. +For example, a third party library method called `getUser` that calls `fetch` internally multiple times will be represented as a single span called `getUser`, instead of showing multiple `fetch` spans. + +Clicking on spans will show you a stack trace of where the Promise was created as well as a view of the value that the Promise resolved to, if available. + +Rejected Promises are displayed as red with their rejected value. + +#### Server Components {/*server-components*/} + +The Server Components tracks visualize the durations of React Server Components Promises they awaited. Timings are displayed as a flamegraph, where each entry represents the duration of the corresponding component render and all its descendant children components. + +If you await a Promise, React will display duration of that Promise. To see all I/O operations, use the Server Requests track. + +Different colors are used to indicate the duration of the component render. The darker the color, the longer the duration. + +The Server Components track group will always contain a "Primary" track. If React is able to render Server Components concurrently, it will display addititional "Parallel" tracks. +If more than 8 Server Components are rendered concurrently, React will associate them with the last "Parallel" track instead of adding more tracks. diff --git a/src/content/reference/eslint-plugin-react-hooks/index.md b/src/content/reference/eslint-plugin-react-hooks/index.md index 21e1222d80..34dbdc632c 100644 --- a/src/content/reference/eslint-plugin-react-hooks/index.md +++ b/src/content/reference/eslint-plugin-react-hooks/index.md @@ -9,14 +9,6 @@ version: rc - - -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. @@ -25,14 +17,16 @@ When the compiler reports a diagnostic, it means that the compiler was able to s 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*/} +## Recommended Rules {/*recommended*/} -These rules are available in the stable version of `eslint-plugin-react-hooks`: +These rules are included in the `recommended` preset `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`: +## Additional Rules {/*additional-rules*/} + +Starting in version 6.0, these rules are available to opt-in: * [`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 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 index 44d23d758f..49d6b6d43d 100644 --- 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 @@ -1,6 +1,5 @@ --- title: component-hook-factories -version: rc --- @@ -9,13 +8,11 @@ Validates against higher order functions defining nested components or hooks. Co - + -This rule is available in the RC version of `eslint-plugin-react-hooks`. +This rule is available in `eslint-plugin-react-hooks` v6. -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*/} diff --git a/src/content/reference/eslint-plugin-react-hooks/lints/config.md b/src/content/reference/eslint-plugin-react-hooks/lints/config.md index f7e099752b..98bc8b2785 100644 --- a/src/content/reference/eslint-plugin-react-hooks/lints/config.md +++ b/src/content/reference/eslint-plugin-react-hooks/lints/config.md @@ -9,13 +9,11 @@ Validates the compiler [configuration options](/reference/react-compiler/configu - + -This rule is available in the RC version of `eslint-plugin-react-hooks`. +This rule is available in `eslint-plugin-react-hooks` v6. -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*/} 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 index bd013f5325..c9430ea36b 100644 --- a/src/content/reference/eslint-plugin-react-hooks/lints/error-boundaries.md +++ b/src/content/reference/eslint-plugin-react-hooks/lints/error-boundaries.md @@ -9,13 +9,11 @@ Validates usage of Error Boundaries instead of try/catch for errors in child com - + -This rule is available in the RC version of `eslint-plugin-react-hooks`. +This rule is available in `eslint-plugin-react-hooks` v6. -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*/} 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 index cd26483a14..a2c98fa9f3 100644 --- a/src/content/reference/eslint-plugin-react-hooks/lints/exhaustive-deps.md +++ b/src/content/reference/eslint-plugin-react-hooks/lints/exhaustive-deps.md @@ -140,7 +140,21 @@ useEffect(() => { ## Options {/*options*/} -This rule accepts an options object: +You can configure custom effect hooks using shared ESLint settings (available in `eslint-plugin-react-hooks` 6.1.0 and later): + +```js +{ + "settings": { + "react-hooks": { + "additionalEffectHooks": "(useMyEffect|useCustomEffect)" + } + } +} +``` + +- `additionalEffectHooks`: Regex pattern matching custom hooks that should be checked for exhaustive dependencies. This configuration is shared across all `react-hooks` rules. + +For backward compatibility, this rule also accepts a rule-level option: ```js { @@ -152,4 +166,4 @@ This rule accepts an options object: } ``` -- `additionalHooks`: Regex for hooks that should be checked for exhaustive dependencies +- `additionalHooks`: Regex for hooks that should be checked for exhaustive dependencies. **Note:** If this rule-level option is specified, it takes precedence over the shared `settings` configuration. diff --git a/src/content/reference/eslint-plugin-react-hooks/lints/gating.md b/src/content/reference/eslint-plugin-react-hooks/lints/gating.md index fdbbadf0ef..62b98df089 100644 --- a/src/content/reference/eslint-plugin-react-hooks/lints/gating.md +++ b/src/content/reference/eslint-plugin-react-hooks/lints/gating.md @@ -9,13 +9,11 @@ Validates configuration of [gating mode](/reference/react-compiler/gating). - + -This rule is available in the RC version of `eslint-plugin-react-hooks`. +This rule is available in `eslint-plugin-react-hooks` v6. -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*/} diff --git a/src/content/reference/eslint-plugin-react-hooks/lints/globals.md b/src/content/reference/eslint-plugin-react-hooks/lints/globals.md index b9a20d4dbc..ea429404a8 100644 --- a/src/content/reference/eslint-plugin-react-hooks/lints/globals.md +++ b/src/content/reference/eslint-plugin-react-hooks/lints/globals.md @@ -9,13 +9,11 @@ Validates against assignment/mutation of globals during render, part of ensuring - + -This rule is available in the RC version of `eslint-plugin-react-hooks`. +This rule is available in `eslint-plugin-react-hooks` v6. -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*/} diff --git a/src/content/reference/eslint-plugin-react-hooks/lints/immutability.md b/src/content/reference/eslint-plugin-react-hooks/lints/immutability.md index 9376271ebd..33498ebe8c 100644 --- a/src/content/reference/eslint-plugin-react-hooks/lints/immutability.md +++ b/src/content/reference/eslint-plugin-react-hooks/lints/immutability.md @@ -9,13 +9,11 @@ Validates against mutating props, state, and other values that [are immutable](/ - + -This rule is available in the RC version of `eslint-plugin-react-hooks`. +This rule is available in `eslint-plugin-react-hooks` v6. -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*/} 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 index aa58775032..b041d02c5a 100644 --- a/src/content/reference/eslint-plugin-react-hooks/lints/incompatible-library.md +++ b/src/content/reference/eslint-plugin-react-hooks/lints/incompatible-library.md @@ -9,13 +9,11 @@ Validates against usage of libraries which are incompatible with memoization (ma - - -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). +This rule is available in `eslint-plugin-react-hooks` v6. - + 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 index 2f296ac56b..5efc2f82d7 100644 --- 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 @@ -9,13 +9,11 @@ Validates that existing manual memoization is preserved by the compiler. React C - + -This rule is available in the RC version of `eslint-plugin-react-hooks`. +This rule is available in `eslint-plugin-react-hooks` v6. -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*/} diff --git a/src/content/reference/eslint-plugin-react-hooks/lints/purity.md b/src/content/reference/eslint-plugin-react-hooks/lints/purity.md index 14c870fb53..74c1327597 100644 --- a/src/content/reference/eslint-plugin-react-hooks/lints/purity.md +++ b/src/content/reference/eslint-plugin-react-hooks/lints/purity.md @@ -9,13 +9,11 @@ Validates that [components/hooks are pure](/reference/rules/components-and-hooks - + -This rule is available in the RC version of `eslint-plugin-react-hooks`. +This rule is available in `eslint-plugin-react-hooks` v6. -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*/} diff --git a/src/content/reference/eslint-plugin-react-hooks/lints/refs.md b/src/content/reference/eslint-plugin-react-hooks/lints/refs.md index 78776ba572..d8fe222e82 100644 --- a/src/content/reference/eslint-plugin-react-hooks/lints/refs.md +++ b/src/content/reference/eslint-plugin-react-hooks/lints/refs.md @@ -9,13 +9,11 @@ Validates correct usage of refs, not reading/writing during render. See the "pit - + -This rule is available in the RC version of `eslint-plugin-react-hooks`. +This rule is available in `eslint-plugin-react-hooks` v6. -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*/} 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 index 6508fc867f..87a02c3564 100644 --- 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 @@ -159,3 +159,21 @@ const [permissions, setPermissions] = useState( userType === 'admin' ? adminPerms : userPerms ); ``` + +## Options {/*options*/} + +You can configure custom effect hooks using shared ESLint settings (available in `eslint-plugin-react-hooks` 6.1.0 and later): + +```js +{ + "settings": { + "react-hooks": { + "additionalEffectHooks": "(useMyEffect|useCustomEffect)" + } + } +} +``` + +- `additionalEffectHooks`: Regex pattern matching custom hooks that should be treated as effects. This allows `useEffectEvent` and similar event functions to be called from your custom effect hooks. + +This shared configuration is used by both `rules-of-hooks` and `exhaustive-deps` rules, ensuring consistent behavior across all hook-related linting. diff --git a/src/content/reference/eslint-plugin-react-hooks/lints/set-state-in-effect.md b/src/content/reference/eslint-plugin-react-hooks/lints/set-state-in-effect.md index df285fedfe..55b63b3116 100644 --- a/src/content/reference/eslint-plugin-react-hooks/lints/set-state-in-effect.md +++ b/src/content/reference/eslint-plugin-react-hooks/lints/set-state-in-effect.md @@ -9,13 +9,11 @@ Validates against calling setState synchronously in an effect, which can lead to - + -This rule is available in the RC version of `eslint-plugin-react-hooks`. +This rule is available in `eslint-plugin-react-hooks` v6. -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*/} diff --git a/src/content/reference/eslint-plugin-react-hooks/lints/set-state-in-render.md b/src/content/reference/eslint-plugin-react-hooks/lints/set-state-in-render.md index c8cfa22a02..a88de10066 100644 --- a/src/content/reference/eslint-plugin-react-hooks/lints/set-state-in-render.md +++ b/src/content/reference/eslint-plugin-react-hooks/lints/set-state-in-render.md @@ -5,28 +5,26 @@ version: rc -Validates against setting state during render, which can trigger additional renders and potential infinite render loops. +Validates against unconditionally setting state during render, which can trigger additional renders and potential infinite render loops. - + -This rule is available in the RC version of `eslint-plugin-react-hooks`. +This rule is available in `eslint-plugin-react-hooks` v6. -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*/} -Calling `setState` during render triggers another render before the current one finishes. This creates an infinite loop that crashes your app. +Calling `setState` during render unconditionally triggers another render before the current one finishes. This creates an infinite loop that crashes your app. ## Common Violations {/*common-violations*/} ### Invalid {/*invalid*/} ```js {expectedErrors: {'react-compiler': [4]}} -// ❌ setState directly in render +// ❌ Unconditional setState directly in render function Component({value}) { const [count, setCount] = useState(0); setCount(value); // Infinite loop! @@ -59,6 +57,19 @@ function Component({user}) { const email = user?.email || ''; return
{name}
; } + +// ✅ Conditionally derive state from props and state from previous renders +function Component({ items }) { + const [isReverse, setIsReverse] = useState(false); + const [selection, setSelection] = useState(null); + + const [prevItems, setPrevItems] = useState(items); + if (items !== prevItems) { // This condition makes it valid + setPrevItems(items); + setSelection(null); + } + // ... +} ``` ## Troubleshooting {/*troubleshooting*/} @@ -102,3 +113,5 @@ function Counter({max}) { ``` Now the setter only runs in response to the click, React finishes the render normally, and `count` never crosses `max`. + +In rare cases, you may need to adjust state based on information from previous renders. For those, follow [this pattern](https://react.dev/reference/react/useState#storing-information-from-previous-renders) of setting state conditionally. diff --git a/src/content/reference/eslint-plugin-react-hooks/lints/static-components.md b/src/content/reference/eslint-plugin-react-hooks/lints/static-components.md index 06945838f3..b403cc0b6a 100644 --- a/src/content/reference/eslint-plugin-react-hooks/lints/static-components.md +++ b/src/content/reference/eslint-plugin-react-hooks/lints/static-components.md @@ -9,13 +9,11 @@ Validates that components are static, not recreated every render. Components tha - - -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). +This rule is available in `eslint-plugin-react-hooks` v6. - + ## Rule Details {/*rule-details*/} diff --git a/src/content/reference/eslint-plugin-react-hooks/lints/unsupported-syntax.md b/src/content/reference/eslint-plugin-react-hooks/lints/unsupported-syntax.md index 3f3e04d53a..b6055449a1 100644 --- a/src/content/reference/eslint-plugin-react-hooks/lints/unsupported-syntax.md +++ b/src/content/reference/eslint-plugin-react-hooks/lints/unsupported-syntax.md @@ -9,13 +9,11 @@ Validates against syntax that React Compiler does not support. If you need to, y - - -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). +This rule is available in `eslint-plugin-react-hooks` v6. - + ## Rule Details {/*rule-details*/} diff --git a/src/content/reference/eslint-plugin-react-hooks/lints/use-memo.md b/src/content/reference/eslint-plugin-react-hooks/lints/use-memo.md index db022a802f..faa8c42d12 100644 --- a/src/content/reference/eslint-plugin-react-hooks/lints/use-memo.md +++ b/src/content/reference/eslint-plugin-react-hooks/lints/use-memo.md @@ -5,17 +5,15 @@ version: rc -Validates usage of the `useMemo` hook without a return value. See [`useMemo` docs](/reference/react/useMemo) for more information. +Validates that the `useMemo` hook is used with a return value. See [`useMemo` docs](/reference/react/useMemo) for more information. - + -This rule is available in the RC version of `eslint-plugin-react-hooks`. +This rule is available in `eslint-plugin-react-hooks` v6. -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*/} diff --git a/src/content/reference/react-dom/server/index.md b/src/content/reference/react-dom/server/index.md index 49464ae793..94b2b7b334 100644 --- a/src/content/reference/react-dom/server/index.md +++ b/src/content/reference/react-dom/server/index.md @@ -10,6 +10,7 @@ title: Server React DOM API --- +<<<<<<< HEAD ## Node.js 流服务器 API {/*server-apis-for-nodejs-streams*/} 以下方法仅在具有 [Node.js 流](https://nodejs.org/api/stream.html) 的环境中可用: @@ -19,10 +20,33 @@ title: Server React DOM API --- ## Web 流服务器 API {/*server-apis-for-web-streams*/} +======= +## Server APIs for Web Streams {/*server-apis-for-web-streams*/} +>>>>>>> 996ef72c1a9f7ecc003588a897be85fc647393c3 以下方法仅在具有 [web 流](https://developer.mozilla.org/zh-CN/docs/Web/API/Streams_API) 的环境中可用,包括浏览器、Deno,以及一些现代 edge 运行时: +<<<<<<< HEAD + [`renderToReadableStream`](/reference/react-dom/server/renderToReadableStream) 将 React 树渲染为 [可读的 web 流](https://developer.mozilla.org/zh-CN/docs/Web/API/ReadableStream)。 +======= +* [`renderToReadableStream`](/reference/react-dom/server/renderToReadableStream) renders a React tree to a [Readable Web Stream.](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) +* [`resume`](/reference/react-dom/server/renderToPipeableStream) resumes [`prerender`](/reference/react-dom/static/prerender) to a [Readable Web Stream](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream). + + + + +Node.js also includes these methods for compatibility, but they are not recommended due to worse performance. Use the [dedicated Node.js APIs](#server-apis-for-nodejs-streams) instead. + + +--- + +## Server APIs for Node.js Streams {/*server-apis-for-nodejs-streams*/} + +These methods are only available in the environments with [Node.js Streams:](https://nodejs.org/api/stream.html) + +* [`renderToPipeableStream`](/reference/react-dom/server/renderToPipeableStream) renders a React tree to a pipeable [Node.js Stream.](https://nodejs.org/api/stream.html) +* [`resumeToPipeableStream`](/reference/react-dom/server/renderToPipeableStream) resumes [`prerenderToNodeStream`](/reference/react-dom/static/prerenderToNodeStream) to a pipeable [Node.js Stream.](https://nodejs.org/api/stream.html) +>>>>>>> 996ef72c1a9f7ecc003588a897be85fc647393c3 --- diff --git a/src/content/reference/react-dom/server/resume.md b/src/content/reference/react-dom/server/resume.md new file mode 100644 index 0000000000..17b48b2acf --- /dev/null +++ b/src/content/reference/react-dom/server/resume.md @@ -0,0 +1,236 @@ +--- +title: resume +--- + + + +`resume` streams a pre-rendered React tree to a [Readable Web Stream.](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) + +```js +const stream = await resume(reactNode, postponedState, options?) +``` + + + + + + + +This API depends on [Web Streams.](https://developer.mozilla.org/en-US/docs/Web/API/Streams_API) For Node.js, use [`resumeToNodeStream`](/reference/react-dom/server/renderToPipeableStream) instead. + + + +--- + +## Reference {/*reference*/} + +### `resume(node, postponedState, options?)` {/*resume*/} + +Call `resume` to resume rendering a pre-rendered React tree as HTML into a [Readable Web Stream.](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) + +```js +import { resume } from 'react-dom/server'; +import {getPostponedState} from './storage'; + +async function handler(request, writable) { + const postponed = await getPostponedState(request); + const resumeStream = await resume(, postponed); + return resumeStream.pipeTo(writable) +} +``` + +[See more examples below.](#usage) + +#### Parameters {/*parameters*/} + +* `reactNode`: The React node you called `prerender` with. For example, a JSX element like ``. It is expected to represent the entire document, so the `App` component should render the `` tag. +* `postponedState`: The opaque `postpone` object returned from a [prerender API](/reference/react-dom/static/index), loaded from wherever you stored it (e.g. redis, a file, or S3). +* **optional** `options`: An object with streaming options. + * **optional** `nonce`: A [`nonce`](http://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#nonce) string to allow scripts for [`script-src` Content-Security-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src). + * **optional** `signal`: An [abort signal](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) that lets you [abort server rendering](#aborting-server-rendering) and render the rest on the client. + * **optional** `onError`: A callback that fires whenever there is a server error, whether [recoverable](/reference/react-dom/server/renderToReadableStream#recovering-from-errors-outside-the-shell) or [not.](/reference/react-dom/server/renderToReadableStream#recovering-from-errors-inside-the-shell) By default, this only calls `console.error`. If you override it to [log crash reports,](/reference/react-dom/server/renderToReadableStream#logging-crashes-on-the-server) make sure that you still call `console.error`. + + +#### Returns {/*returns*/} + +`resume` returns a Promise: + +- If `resume` successfully produced a [shell](/reference/react-dom/server/renderToReadableStream#specifying-what-goes-into-the-shell), that Promise will resolve to a [Readable Web Stream.](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) that can be piped to a [Writable Web Stream.](https://developer.mozilla.org/en-US/docs/Web/API/WritableStream). +- If an error happens in the shell, the Promise will reject with that error. + +The returned stream has an additional property: + +* `allReady`: A Promise that resolves when all rendering is complete. You can `await stream.allReady` before returning a response [for crawlers and static generation.](/reference/react-dom/server/renderToReadableStream#waiting-for-all-content-to-load-for-crawlers-and-static-generation) If you do that, you won't get any progressive loading. The stream will contain the final HTML. + +#### Caveats {/*caveats*/} + +- `resume` does not accept options for `bootstrapScripts`, `bootstrapScriptContent`, or `bootstrapModules`. Instead, you need to pass these options to the `prerender` call that generates the `postponedState`. You can also inject bootstrap content into the writable stream manually. +- `resume` does not accept `identifierPrefix` since the prefix needs to be the same in both `prerender` and `resume`. +- Since `nonce` cannot be provided to prerender, you should only provide `nonce` to `resume` if you're not providing scripts to prerender. +- `resume` re-renders from the root until it finds a component that was not fully pre-rendered. Only fully prerendered Components (the Component and its children finished prerendering) are skipped entirely. + +## Usage {/*usage*/} + +### Resuming a prerender {/*resuming-a-prerender*/} + + + +```js src/App.js hidden +``` + +```html public/index.html + + + + + + Document + + + + + +``` + +```js src/index.js +import { + flushReadableStreamToFrame, + getUser, + Postponed, + sleep, +} from "./demo-helpers"; +import { StrictMode, Suspense, use, useEffect } from "react"; +import { prerender } from "react-dom/static"; +import { resume } from "react-dom/server"; +import { hydrateRoot } from "react-dom/client"; + +function Header() { + return
Me and my descendants can be prerendered
; +} + +const { promise: cookies, resolve: resolveCookies } = Promise.withResolvers(); + +function Main() { + const { sessionID } = use(cookies); + const user = getUser(sessionID); + + useEffect(() => { + console.log("reached interactivity!"); + }, []); + + return ( +
+ Hello, {user.name}! + +
+ ); +} + +function Shell({ children }) { + // In a real app, this is where you would put your html and body. + // We're just using tags here we can include in an existing body for demonstration purposes + return ( + + {children} + + ); +} + +function App() { + return ( + + +
+ + +
+ + + ); +} + +async function main(frame) { + // Layer 1 + const controller = new AbortController(); + const prerenderedApp = prerender(, { + signal: controller.signal, + onError(error) { + if (error instanceof Postponed) { + } else { + console.error(error); + } + }, + }); + // We're immediately aborting in a macrotask. + // Any data fetching that's not available synchronously, or in a microtask, will not have finished. + setTimeout(() => { + controller.abort(new Postponed()); + }); + + const { prelude, postponed } = await prerenderedApp; + await flushReadableStreamToFrame(prelude, frame); + + // Layer 2 + // Just waiting here for demonstration purposes. + // In a real app, the prelude and postponed state would've been serialized in Layer 1 and Layer would deserialize them. + // The prelude content could be flushed immediated as plain HTML while + // React is continuing to render from where the prerender left off. + await sleep(2000); + + // You would get the cookies from the incoming HTTP request + resolveCookies({ sessionID: "abc" }); + + const stream = await resume(, postponed); + + await flushReadableStreamToFrame(stream, frame); + + // Layer 3 + // Just waiting here for demonstration purposes. + await sleep(2000); + + hydrateRoot(frame.contentWindow.document, ); +} + +main(document.getElementById("container")); + +``` + +```js src/demo-helpers.js +export async function flushReadableStreamToFrame(readable, frame) { + const document = frame.contentWindow.document; + const decoder = new TextDecoder(); + for await (const chunk of readable) { + const partialHTML = decoder.decode(chunk); + document.write(partialHTML); + } +} + +// This doesn't need to be an error. +// You can use any other means to check if an error during prerender was +// from an intentional abort or a real error. +export class Postponed extends Error {} + +// We're just hardcoding a session here. +export function getUser(sessionID) { + return { + name: "Alice", + }; +} + +export function sleep(timeoutMS) { + return new Promise((resolve) => { + setTimeout(() => { + resolve(); + }, timeoutMS); + }); +} +``` + + + +### Further reading {/*further-reading*/} + +Resuming behaves like `renderToReadableStream`. For more examples, check out the [usage section of `renderToReadableStream`](/reference/react-dom/server/renderToReadableStream#usage). +The [usage section of `prerender`](/reference/react-dom/static/prerender#usage) includes examples of how to use `prerender` specifically. \ No newline at end of file diff --git a/src/content/reference/react-dom/server/resumeToPipeableStream.md b/src/content/reference/react-dom/server/resumeToPipeableStream.md new file mode 100644 index 0000000000..48caa3be64 --- /dev/null +++ b/src/content/reference/react-dom/server/resumeToPipeableStream.md @@ -0,0 +1,78 @@ +--- +title: resumeToPipeableStream +--- + + + +`resumeToPipeableStream` streams a pre-rendered React tree to a pipeable [Node.js Stream.](https://nodejs.org/api/stream.html) + +```js +const {pipe, abort} = await resumeToPipeableStream(reactNode, postponedState, options?) +``` + + + + + + + +This API is specific to Node.js. Environments with [Web Streams,](https://developer.mozilla.org/en-US/docs/Web/API/Streams_API) like Deno and modern edge runtimes, should use [`resume`](/reference/react-dom/server/renderToReadableStream) instead. + + + +--- + +## Reference {/*reference*/} + +### `resumeToPipeableStream(node, postponed, options?)` {/*resume-to-pipeable-stream*/} + +Call `resume` to resume rendering a pre-rendered React tree as HTML into a [Node.js Stream.](https://nodejs.org/api/stream.html#writable-streams) + +```js +import { resume } from 'react-dom/server'; +import {getPostponedState} from './storage'; + +async function handler(request, response) { + const postponed = await getPostponedState(request); + const {pipe} = resumeToPipeableStream(, postponed, { + onShellReady: () => { + pipe(response); + } + }); +} +``` + +[See more examples below.](#usage) + +#### Parameters {/*parameters*/} + +* `reactNode`: The React node you called `prerender` with. For example, a JSX element like ``. It is expected to represent the entire document, so the `App` component should render the `` tag. +* `postponedState`: The opaque `postpone` object returned from a [prerender API](/reference/react-dom/static/index), loaded from wherever you stored it (e.g. redis, a file, or S3). +* **optional** `options`: An object with streaming options. + * **optional** `nonce`: A [`nonce`](http://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#nonce) string to allow scripts for [`script-src` Content-Security-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src). + * **optional** `signal`: An [abort signal](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) that lets you [abort server rendering](#aborting-server-rendering) and render the rest on the client. + * **optional** `onError`: A callback that fires whenever there is a server error, whether [recoverable](/reference/react-dom/server/renderToReadableStream#recovering-from-errors-outside-the-shell) or [not.](/reference/react-dom/server/renderToReadableStream#recovering-from-errors-inside-the-shell) By default, this only calls `console.error`. If you override it to [log crash reports,](/reference/react-dom/server/renderToReadableStream#logging-crashes-on-the-server) make sure that you still call `console.error`. + * **optional** `onShellReady`: A callback that fires right after the [shell](#specifying-what-goes-into-the-shell) has finished. You can call `pipe` here to start streaming. React will [stream the additional content](#streaming-more-content-as-it-loads) after the shell along with the inline `