diff --git a/package.json b/package.json index 5ff6adbab..472ef79c9 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "next-remote-watch": "^1.0.0", "parse-numeric-range": "^1.2.0", "react": "^0.0.0-experimental-16d053d59-20230506", - "react-collapsed": "npm:@gaearon/react-collapsed@3.1.0-forked.1", + "react-collapsed": "4.0.4", "react-dom": "^0.0.0-experimental-16d053d59-20230506", "remark-frontmatter": "^4.0.1", "remark-gfm": "^3.0.1" diff --git a/public/images/docs/diagrams/conditional_render_tree.dark.png b/public/images/docs/diagrams/conditional_render_tree.dark.png new file mode 100644 index 000000000..5189a44c8 Binary files /dev/null and b/public/images/docs/diagrams/conditional_render_tree.dark.png differ diff --git a/public/images/docs/diagrams/conditional_render_tree.png b/public/images/docs/diagrams/conditional_render_tree.png new file mode 100644 index 000000000..c76e8cb63 Binary files /dev/null and b/public/images/docs/diagrams/conditional_render_tree.png differ diff --git a/public/images/docs/diagrams/generic_dependency_tree.dark.png b/public/images/docs/diagrams/generic_dependency_tree.dark.png new file mode 100644 index 000000000..64694f585 Binary files /dev/null and b/public/images/docs/diagrams/generic_dependency_tree.dark.png differ diff --git a/public/images/docs/diagrams/generic_dependency_tree.png b/public/images/docs/diagrams/generic_dependency_tree.png new file mode 100644 index 000000000..8ab6f1a34 Binary files /dev/null and b/public/images/docs/diagrams/generic_dependency_tree.png differ diff --git a/public/images/docs/diagrams/generic_render_tree.dark.png b/public/images/docs/diagrams/generic_render_tree.dark.png new file mode 100644 index 000000000..859fdaa96 Binary files /dev/null and b/public/images/docs/diagrams/generic_render_tree.dark.png differ diff --git a/public/images/docs/diagrams/generic_render_tree.png b/public/images/docs/diagrams/generic_render_tree.png new file mode 100644 index 000000000..952cf5faa Binary files /dev/null and b/public/images/docs/diagrams/generic_render_tree.png differ diff --git a/public/images/docs/diagrams/module_dependency_tree.dark.png b/public/images/docs/diagrams/module_dependency_tree.dark.png new file mode 100644 index 000000000..e8a85f7c0 Binary files /dev/null and b/public/images/docs/diagrams/module_dependency_tree.dark.png differ diff --git a/public/images/docs/diagrams/module_dependency_tree.png b/public/images/docs/diagrams/module_dependency_tree.png new file mode 100644 index 000000000..0dcaaa7aa Binary files /dev/null and b/public/images/docs/diagrams/module_dependency_tree.png differ diff --git a/public/images/docs/diagrams/render_tree.dark.png b/public/images/docs/diagrams/render_tree.dark.png new file mode 100644 index 000000000..117d7ff3e Binary files /dev/null and b/public/images/docs/diagrams/render_tree.dark.png differ diff --git a/public/images/docs/diagrams/render_tree.png b/public/images/docs/diagrams/render_tree.png new file mode 100644 index 000000000..1ea750bb0 Binary files /dev/null and b/public/images/docs/diagrams/render_tree.png differ diff --git a/src/components/Layout/Sidebar/SidebarRouteTree.tsx b/src/components/Layout/Sidebar/SidebarRouteTree.tsx index 9a0dd23f5..a9fa575b5 100644 --- a/src/components/Layout/Sidebar/SidebarRouteTree.tsx +++ b/src/components/Layout/Sidebar/SidebarRouteTree.tsx @@ -7,7 +7,7 @@ import {useRef, useLayoutEffect, Fragment} from 'react'; import cn from 'classnames'; import {useRouter} from 'next/router'; import {SidebarLink} from './SidebarLink'; -import useCollapse from 'react-collapsed'; +import {useCollapse} from 'react-collapsed'; import usePendingRoute from 'hooks/usePendingRoute'; import type {RouteItem} from 'components/Layout/getRouteMeta'; diff --git a/src/components/Seo.tsx b/src/components/Seo.tsx index d0dcaab75..79f19f87c 100644 --- a/src/components/Seo.tsx +++ b/src/components/Seo.tsx @@ -22,6 +22,7 @@ const deployedTranslations = [ 'zh-hans', 'es', 'fr', + 'ja', // We'll add more languages when they have enough content. // Please DO NOT edit this list without a discussion in the reactjs/react.dev repo. // It must be the same between all translations. diff --git a/src/content/blog/2022/06/15/react-labs-what-we-have-been-working-on-june-2022.md b/src/content/blog/2022/06/15/react-labs-what-we-have-been-working-on-june-2022.md index 0d9e8e260..938b75c4b 100644 --- a/src/content/blog/2022/06/15/react-labs-what-we-have-been-working-on-june-2022.md +++ b/src/content/blog/2022/06/15/react-labs-what-we-have-been-working-on-june-2022.md @@ -8,7 +8,7 @@ June 15, 2022 by [Andrew Clark](https://twitter.com/acdlite), [Dan Abramov](http -[React 18](https://reactjs.org/blog/2022/03/29/react-v18) was years in the making, and with it brought valuable lessons for the React team. Its release was the result of many years of research and exploring many paths. Some of those paths were successful; many more were dead-ends that led to new insights. One lesson we’ve learned is that it’s frustrating for the community to wait for new features without having insight into these paths that we’re exploring. +[React 18](https://react.dev/blog/2022/03/29/react-v18) was years in the making, and with it brought valuable lessons for the React team. Its release was the result of many years of research and exploring many paths. Some of those paths were successful; many more were dead-ends that led to new insights. One lesson we’ve learned is that it’s frustrating for the community to wait for new features without having insight into these paths that we’re exploring. diff --git a/src/content/community/conferences.md b/src/content/community/conferences.md index b109a5ff3..4b0954942 100644 --- a/src/content/community/conferences.md +++ b/src/content/community/conferences.md @@ -61,7 +61,6 @@ October 27th 2023. In-person in Verona, Italy and online (hybrid event) [Website](https://2023.reactjsday.it/) - [Twitter](https://twitter.com/reactjsday) - [Facebook](https://www.facebook.com/GrUSP/) - [YouTube](https://www.youtube.com/c/grusp) - ### React Summit US 2023 {/*react-summit-us-2023*/} November 13 & 15, 2023. In-person in New York, US + remote first interactivity (hybrid event) @@ -72,6 +71,11 @@ December 8 & 12, 2023. In-person in Berlin, Germany + remote first interactivity [Website](https://reactday.berlin) - [Twitter](https://twitter.com/reactdayberlin) - [Facebook](https://www.facebook.com/reactdayberlin/) - [Videos](https://portal.gitnation.org/events/react-day-berlin-2023) +### App.js Conf 2024 {/*appjs-conf-2024*/} +May 22 - 24, 2024. In-person in Kraków, Poland + remote + +[Website](https://appjs.co) - [Twitter](https://twitter.com/appjsconf) + ### Render(ATL) 2024 🍑 {/*renderatl-2024-*/} June 12 - June 14, 2024. Atlanta, GA, USA diff --git a/src/content/community/meetups.md b/src/content/community/meetups.md index 680fd323b..644bbcee3 100644 --- a/src/content/community/meetups.md +++ b/src/content/community/meetups.md @@ -48,6 +48,7 @@ Do you have a local React.js meetup? Add it here! (Please keep the list alphabet * [Montreal, QC - React Native](https://www.meetup.com/fr-FR/React-Native-MTL/) * [Vancouver, BC](https://www.meetup.com/ReactJS-Vancouver-Meetup/) * [Ottawa, ON](https://www.meetup.com/Ottawa-ReactJS-Meetup/) +* [Saskatoon, SK](https://www.meetup.com/saskatoon-react-meetup/) * [Toronto, ON](https://www.meetup.com/Toronto-React-Native/events/) ## Chile {/*chile*/} diff --git a/src/content/learn/describing-the-ui.md b/src/content/learn/describing-the-ui.md index 6df4c0475..e8b6440aa 100644 --- a/src/content/learn/describing-the-ui.md +++ b/src/content/learn/describing-the-ui.md @@ -18,6 +18,7 @@ React is a JavaScript library for rendering user interfaces (UI). UI is built fr * [How to conditionally render components](/learn/conditional-rendering) * [How to render multiple components at a time](/learn/rendering-lists) * [How to avoid confusing bugs by keeping components pure](/learn/keeping-components-pure) +* [Why understanding your UI as trees is useful](/learn/understanding-your-ui-as-a-tree) @@ -523,6 +524,29 @@ Read **[Keeping Components Pure](/learn/keeping-components-pure)** to learn how +## Your UI as a tree {/*your-ui-as-a-tree*/} + +React uses trees to model the relationships between components and modules. + +A React render tree is a representation of the parent and child relationship between components. + +An example React render tree. + +Components near the top of the tree, near the root component, are considered top-level components. Components with no child components are leaf components. This categorization of components is useful for understanding data flow and rendering performance. + +Modelling the relationship between JavaScript modules is another useful way to understand your app. We refer to it as a module dependency tree. + +An example module dependency tree. + +A dependency tree is often used by build tools to bundle all the relevant JavaScript code for the client to download and render. A large bundle size regresses user experience for React apps. Understanding the module dependency tree is helpful to debug such issues. + + + +Read **[Your UI as a Tree](/learn/understanding-your-ui-as-a-tree)** to learn how to create a render and module dependency trees for a React app and how they're useful mental models for improving user experience and performance. + + + + ## What's next? {/*whats-next*/} Head over to [Your First Component](/learn/your-first-component) to start reading this chapter page by page! diff --git a/src/content/learn/preserving-and-resetting-state.md b/src/content/learn/preserving-and-resetting-state.md index 1c9c341fb..709d9e3d3 100644 --- a/src/content/learn/preserving-and-resetting-state.md +++ b/src/content/learn/preserving-and-resetting-state.md @@ -10,33 +10,17 @@ State is isolated between components. React keeps track of which state belongs t -* How React "sees" component structures * When React chooses to preserve or reset the state * How to force React to reset component's state * How keys and types affect whether the state is preserved -## The UI tree {/*the-ui-tree*/} +## State is tied to a position in the render tree {/*state-is-tied-to-a-position-in-the-tree*/} -Browsers use many tree structures to model UI. The [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model/Introduction) represents HTML elements, the [CSSOM](https://developer.mozilla.org/docs/Web/API/CSS_Object_Model) does the same for CSS. There's even an [Accessibility tree](https://developer.mozilla.org/docs/Glossary/Accessibility_tree)! - -React also uses tree structures to manage and model the UI you make. React makes **UI trees** from your JSX. Then React DOM updates the browser DOM elements to match that UI tree. (React Native translates these trees into elements specific to mobile platforms.) - - - - - -From components, React creates a UI tree which React DOM uses to render the DOM - - - - - -## State is tied to a position in the tree {/*state-is-tied-to-a-position-in-the-tree*/} - -When you give a component state, you might think the state "lives" inside the component. But the state is actually held inside React. React associates each piece of state it's holding with the correct component by where that component sits in the UI tree. +React builds [render trees](learn/understanding-your-ui-as-a-tree#the-render-tree) for the component structure in your UI. +When you give a component state, you might think the state "lives" inside the component. But the state is actually held inside React. React associates each piece of state it's holding with the correct component by where that component sits in the render tree. Here, there is only one `` JSX tag, but it's rendered at two different positions: @@ -190,7 +174,7 @@ Updating state -React will keep the state around for as long as you render the same component at the same position. To see this, increment both counters, then remove the second component by unchecking "Render the second counter" checkbox, and then add it back by ticking it again: +React will keep the state around for as long as you render the same component at the same position in the tree. To see this, increment both counters, then remove the second component by unchecking "Render the second counter" checkbox, and then add it back by ticking it again: diff --git a/src/content/learn/referencing-values-with-refs.md b/src/content/learn/referencing-values-with-refs.md index da5d864ab..4faf18786 100644 --- a/src/content/learn/referencing-values-with-refs.md +++ b/src/content/learn/referencing-values-with-refs.md @@ -284,7 +284,7 @@ You also don't need to worry about [avoiding mutation](/learn/updating-objects-i ## Refs and the DOM {/*refs-and-the-dom*/} -You can point a ref to any value. However, the most common use case for a ref is to access a DOM element. For example, this is handy if you want to focus an input programmatically. When you pass a ref to a `ref` attribute in JSX, like `
`, React will put the corresponding DOM element into `myRef.current`. You can read more about this in [Manipulating the DOM with Refs.](/learn/manipulating-the-dom-with-refs) +You can point a ref to any value. However, the most common use case for a ref is to access a DOM element. For example, this is handy if you want to focus an input programmatically. When you pass a ref to a `ref` attribute in JSX, like `
`, React will put the corresponding DOM element into `myRef.current`. Once the element is removed from the DOM, React will update `myRef.current` to be `null`. You can read more about this in [Manipulating the DOM with Refs.](/learn/manipulating-the-dom-with-refs) diff --git a/src/content/learn/rendering-lists.md b/src/content/learn/rendering-lists.md index 45b60240b..13ac932fb 100644 --- a/src/content/learn/rendering-lists.md +++ b/src/content/learn/rendering-lists.md @@ -1086,7 +1086,7 @@ Here, `` is a syntax shortcut saying "pass #### List with a separator {/*list-with-a-separator*/} -This example renders a famous haiku by Katsushika Hokusai, with each line wrapped in a `

` tag. Your job is to insert an `


` separator between each paragraph. Your resulting structure should look like this: +This example renders a famous haiku by Tachibana Hokushi, with each line wrapped in a `

` tag. Your job is to insert an `


` separator between each paragraph. Your resulting structure should look like this: ```js
diff --git a/src/content/learn/start-a-new-react-project.md b/src/content/learn/start-a-new-react-project.md index 280a1378e..9c395d3f0 100644 --- a/src/content/learn/start-a-new-react-project.md +++ b/src/content/learn/start-a-new-react-project.md @@ -21,12 +21,12 @@ If you want to build a new app or a new website fully with React, we recommend p **[Next.js](https://nextjs.org/) is a full-stack React framework.** It's versatile and lets you create React apps of any size--from a mostly static blog to a complex dynamic application. To create a new Next.js project, run in your terminal: -npx create-next-app +npx create-next-app@latest If you're new to Next.js, check out the [Next.js tutorial.](https://nextjs.org/learn/foundations/about-nextjs) -Next.js is maintained by [Vercel](https://vercel.com/). You can [deploy a Next.js app](https://nextjs.org/docs/deployment) to any Node.js or serverless hosting, or to your own server. [Fully static Next.js apps](https://nextjs.org/docs/advanced-features/static-html-export) can be deployed to any static hosting. +Next.js is maintained by [Vercel](https://vercel.com/). You can [deploy a Next.js app](https://nextjs.org/docs/app/building-your-application/deploying) to any Node.js or serverless hosting, or to your own server. Next.js also supports a [static export](https://nextjs.org/docs/pages/building-your-application/deploying/static-exports) which doesn't require a server. ### Remix {/*remix*/} @@ -89,14 +89,9 @@ These features are getting closer to being production-ready every day, and we've ### Next.js (App Router) {/*nextjs-app-router*/} -**[Next.js's App Router](https://beta.nextjs.org/docs/getting-started) is a redesign of the Next.js APIs aiming to fulfill the React team’s full-stack architecture vision.** It lets you fetch data in asynchronous components that run on the server or even during the build. +**[Next.js's App Router](https://nextjs.org/docs) is a redesign of the Next.js APIs aiming to fulfill the React team’s full-stack architecture vision.** It lets you fetch data in asynchronous components that run on the server or even during the build. -Next.js is maintained by [Vercel](https://vercel.com/). You can [deploy a Next.js app](https://nextjs.org/docs/deployment) to any Node.js or serverless hosting, or to your own server. Next.js also supports [static export](https://beta.nextjs.org/docs/configuring/static-export) which doesn't require a server. - - -Next.js's App Router is **currently in beta and is not yet recommended for production** (as of Mar 2023). To experiment with it in an existing Next.js project, [follow this incremental migration guide](https://beta.nextjs.org/docs/upgrade-guide#migrating-from-pages-to-app). - - +Next.js is maintained by [Vercel](https://vercel.com/). You can [deploy a Next.js app](https://nextjs.org/docs/app/building-your-application/deploying) to any Node.js or serverless hosting, or to your own server. Next.js also supports [static export](https://nextjs.org/docs/app/building-your-application/deploying/static-exports) which doesn't require a server. diff --git a/src/content/learn/tutorial-tic-tac-toe.md b/src/content/learn/tutorial-tic-tac-toe.md index 7c5adb118..eca388a88 100644 --- a/src/content/learn/tutorial-tic-tac-toe.md +++ b/src/content/learn/tutorial-tic-tac-toe.md @@ -2073,7 +2073,13 @@ export default function Game() { } ``` -You can see what your code should look like below. Note that you should see an error in the developer tools console that says: ``Warning: Each child in an array or iterator should have a unique "key" prop. Check the render method of `Game`.`` You'll fix this error in the next section. +You can see what your code should look like below. Note that you should see an error in the developer tools console that says: + + +Warning: Each child in an array or iterator should have a unique "key" prop. Check the render method of `Game`. + + +You'll fix this error in the next section. diff --git a/src/content/learn/typescript.md b/src/content/learn/typescript.md index 56fa5be5b..727974b1c 100644 --- a/src/content/learn/typescript.md +++ b/src/content/learn/typescript.md @@ -284,7 +284,7 @@ export default App = AppTSX; -This technique works when you have an default value which makes sense - but there are occasionally cases when you do not, and in those cases `null` can feel reasonable as a default value. However, to allow the type-system to understand your code, you need to explicitly set `ContextShape | null` on the `createContext`. +This technique works when you have a default value which makes sense - but there are occasionally cases when you do not, and in those cases `null` can feel reasonable as a default value. However, to allow the type-system to understand your code, you need to explicitly set `ContextShape | null` on the `createContext`. This causes the issue that you need to eliminate the `| null` in the type for context consumers. Our recommendation is to have the hook do a runtime check for it's existence and throw an error when not present: @@ -460,4 +460,4 @@ We recommend the following resources: - [React TypeScript Cheatsheet](https://react-typescript-cheatsheet.netlify.app/) is a community-maintained cheatsheet for using TypeScript with React, covering a lot of useful edge cases and providing more breadth than this document. - - [TypeScript Community Discord](https://discord.com/invite/typescript) is a great place to ask questions and get help with TypeScript and React issues. \ No newline at end of file + - [TypeScript Community Discord](https://discord.com/invite/typescript) is a great place to ask questions and get help with TypeScript and React issues. diff --git a/src/content/learn/understanding-your-ui-as-a-tree.md b/src/content/learn/understanding-your-ui-as-a-tree.md new file mode 100644 index 000000000..2a5a24b85 --- /dev/null +++ b/src/content/learn/understanding-your-ui-as-a-tree.md @@ -0,0 +1,300 @@ +--- +title: Understanding Your UI as a Tree +--- + + + +Your React app is taking shape with many components being nested within each other. How does React keep track of your app's component structure? + +React, and many other UI libraries, model UI as a tree. Thinking of your app as a tree is useful for understanding the relationship between components. This understanding will help you debug future concepts like performance and state management. + + + + + +* How React "sees" component structures +* What a render tree is and what it is useful for +* What a module dependency tree is and what it is useful for + + + +## Your UI as a tree {/*your-ui-as-a-tree*/} + +Trees are a relationship model between items and UI is often represented using tree structures. For example, browsers use tree structures to model HTML ([DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model/Introduction)) and CSS ([CSSOM](https://developer.mozilla.org/docs/Web/API/CSS_Object_Model)). Mobile platforms also use trees to represent their view hierarchy. + + + +React creates a UI tree from your components. In this example, the UI tree is then used to render to the DOM. + + +Like browsers and mobile platforms, React also uses tree structures to manage and model the relationship between components in a React app. These trees are useful tools to understand how data flows through a React app and how to optimize rendering and app size. + +## The Render Tree {/*the-render-tree*/} + +A major feature of components is the ability to compose components of other components. As we [nest components](/learn/your-first-component#nesting-and-organizing-components), we have the concept of parent and child components, where each parent component may itself be a child of another component. + +When we render a React app, we can model this relationship in a tree, known as the render tree. + +Here is a React app that renders inspirational quotes. + + + +```js App.js +import FancyText from './FancyText'; +import InspirationGenerator from './InspirationGenerator'; +import Copyright from './Copyright'; + +export default function App() { + return ( + <> + + + + + + ); +} + +``` + +```js FancyText.js +export default function FancyText({title, text}) { + return title + ?

{text}

+ :

{text}

+} +``` + +```js InspirationGenerator.js +import * as React from 'react'; +import quotes from './quotes'; +import FancyText from './FancyText'; + +export default function InspirationGenerator({children}) { + const [index, setIndex] = React.useState(0); + const quote = quotes[index]; + const next = () => setIndex((index + 1) % quotes.length); + + return ( + <> +

Your inspirational quote is:

+ + + {children} + + ); +} +``` + +```js Copyright.js +export default function Copyright({year}) { + return

©️ {year}

; +} +``` + +```js quotes.js +export default [ + "Don’t let yesterday take up too much of today.” — Will Rogers", + "Ambition is putting a ladder against the sky.", + "A joy that's shared is a joy made double.", + ]; +``` + +```css +.fancy { + font-family: 'Georgia'; +} +.title { + color: #007AA3; + text-decoration: underline; +} +.cursive { + font-style: italic; +} +.small { + font-size: 10px; +} +``` + +
+ + + +React creates a *render tree*, a UI tree, composed of the rendered components. + + + + +From the example app, we can construct the above render tree. + +The tree is composed of nodes, each of which represents a component. `App`, `FancyText`, `Copyright`, to name a few, are all nodes in our tree. + +The root node in a React render tree is the [root component](/learn/importing-and-exporting-components#the-root-component-file) of the app. In this case, the root component is `App` and it is the first component React renders. Each arrow in the tree points from a parent component to a child component. + + + +#### Where are the HTML tags in the render tree? {/*where-are-the-html-elements-in-the-render-tree*/} + +You'll notice in the above render tree, there is no mention of the HTML tags that each component renders. This is because the render tree is only composed of React [components](learn/your-first-component#components-ui-building-blocks). + +React, as a UI framework, is platform agnostic. On react.dev, we showcase examples that render to the web, which uses HTML markup as its UI primitives. But a React app could just as likely render to a mobile or desktop platform, which may use different UI primitives like [UIView](https://developer.apple.com/documentation/uikit/uiview) or [FrameworkElement](https://learn.microsoft.com/en-us/dotnet/api/system.windows.frameworkelement?view=windowsdesktop-7.0). + +These platform UI primitives are not a part of React. React render trees can provide insight to our React app regardless of what platform your app renders to. + + + +A render tree represents a single render pass of a React application. With [conditional rendering](/learn/conditional-rendering), a parent component may render different children depending on the data passed. + +We can update the app to conditionally render either an inspirational quote or color. + + + +```js App.js +import FancyText from './FancyText'; +import InspirationGenerator from './InspirationGenerator'; +import Copyright from './Copyright'; + +export default function App() { + return ( + <> + + + + + + ); +} + +``` + +```js FancyText.js +export default function FancyText({title, text}) { + return title + ?

{text}

+ :

{text}

+} +``` + +```js Color.js +export default function Color({value}) { + return
+} +``` + +```js InspirationGenerator.js +import * as React from 'react'; +import inspirations from './inspirations'; +import FancyText from './FancyText'; +import Color from './Color'; + +export default function InspirationGenerator({children}) { + const [index, setIndex] = React.useState(0); + const inspiration = inspirations[index]; + const next = () => setIndex((index + 1) % inspirations.length); + + return ( + <> +

Your inspirational {inspiration.type} is:

+ {inspiration.type === 'quote' + ? + : } + + + {children} + + ); +} +``` + +```js Copyright.js +export default function Copyright({year}) { + return

©️ {year}

; +} +``` + +```js inspirations.js +export default [ + {type: 'quote', value: "Don’t let yesterday take up too much of today.” — Will Rogers"}, + {type: 'color', value: "#B73636"}, + {type: 'quote', value: "Ambition is putting a ladder against the sky."}, + {type: 'color', value: "#256266"}, + {type: 'quote', value: "A joy that's shared is a joy made double."}, + {type: 'color', value: "#F9F2B4"}, +]; +``` + +```css +.fancy { + font-family: 'Georgia'; +} +.title { + color: #007AA3; + text-decoration: underline; +} +.cursive { + font-style: italic; +} +.small { + font-size: 10px; +} +.colorbox { + height: 100px; + width: 100px; + margin: 8px; +} +``` + + + + +With conditional rendering, across different renders, the render tree may render different components. + + + +In this example, depending on what `inspiration.type` is, we may render `` or ``. The render tree may be different for each render pass. + +Although render trees may differ across render pases, these trees are generally helpful for identifying what the top-level and leaf components are in a React app. Top-level components are the components nearest to the root component and affect the rendering performance of all the components beneath them and often contain the most complexity. Leaf components are near the bottom of the tree and have no child components and are often frequently re-rendered. + +Identifying these categories of components are useful for understanding data flow and performance of your app. + +## The Module Dependency Tree {/*the-module-dependency-tree*/} + +Another relationship in a React app that can be modeled with a tree are an app's module dependencies. As we [break up our components](/learn/importing-and-exporting-components#exporting-and-importing-a-component) and logic into separate files, we create [JS modules](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules) where we may export components, functions, or constants. + +Each node in a module dependency tree is a module and each branch represents an `import` statement in that module. + +If we take the previous Inspirations app, we can build a module dependency tree, or dependency tree for short. + + + +The module dependency tree for the Inspirations app. + + + +The root node of the tree is the root module, also known as the entrypoint file. It often is the module that contains the root component. + +Comparing to the render tree of the same app, there are similar structures but some notable differences: + +* The nodes that make-up the tree represent modules, not components. +* Non-component modules, like `inspirations.js`, are also represented in this tree. The render tree only encapsulates components. +* `Copyright.js` appears under `App.js` but in the render tree, `Copyright`, the component, appears as a child of `InspirationGenerator`. This is because `InspirationGenerator` accepts JSX as [children props](/learn/passing-props-to-a-component#passing-jsx-as-children), so it renders `Copyright` as a child component but does not import the module. + +Dependency trees are useful to determine what modules are necessary to run your React app. When building a React app for production, there is typically a build step that will bundle all the necessary JavaScript to ship to the client. The tool responsible for this is called a [bundler](https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Overview#the_modern_tooling_ecosystem), and bundlers will use the dependency tree to determine what modules should be included. + +As your app grows, often the bundle size does too. Large bundle sizes are expensive for a client to download and run. Large bundle sizes can delay the time for your UI to get drawn. Getting a sense of your app's dependency tree may help with debugging these issues. + +[comment]: <> (perhaps we should also deep dive on conditional imports) + + + +* Trees are a common way to represent the relationship between entities. They are often used to model UI. +* Render trees represent the nested relationship between React components across a single render. +* With conditional rendering, the render tree may change across different renders. With different prop values, components may render different children components. +* Render trees help identify what the top-level and leaf components are. Top-level components affect the rendering performance of all components beneath them and leaf components are often re-rendered frequently. Identifying them is useful for understanding and debugging rendering performance. +* Dependency trees represent the module dependencies in a React app. +* Dependency trees are used by build tools to bundle the necessary code to ship an app. +* Dependency trees are useful for debugging large bundle sizes that slow time to paint and expose opportunities for optimizing what code is bundled. + + + +[TODO]: <> (Add challenges) diff --git a/src/content/reference/react-dom/components/common.md b/src/content/reference/react-dom/components/common.md index 8cf1fa29b..78d2713bc 100644 --- a/src/content/reference/react-dom/components/common.md +++ b/src/content/reference/react-dom/components/common.md @@ -694,7 +694,7 @@ An event handler type for the `onWheel` event. ```js
console.log('onScroll')} + onWheel={e => console.log('onWheel')} /> ``` diff --git a/src/content/reference/react-dom/hooks/index.md b/src/content/reference/react-dom/hooks/index.md new file mode 100644 index 000000000..6490dc111 --- /dev/null +++ b/src/content/reference/react-dom/hooks/index.md @@ -0,0 +1,48 @@ +--- +title: "React DOM Hooks" +--- + + + +The `react-dom` package contains Hooks that are only supported for web applications (which run in the browser DOM environment). These Hooks are not supported in non-browser environments like iOS, Android, or Windows applications. If you are looking for Hooks that are supported in web browsers *and other environments* see [the React Hooks page](/reference/react). This page lists all the Hooks in the `react-dom` package. + + + +--- + +## Form Hooks {/*form-hooks*/} + + + +Form Hooks are currently only available in React's canary and experimental channels. Learn more about [React's release channels here](/community/versioning-policy#all-release-channels). + + + +*Forms* let you create interactive controls for submitting information. To manage forms in your components, use one of these Hooks: + +* [`useFormStatus`](/reference/react-dom/hooks/useFormStatus) allows you to make updates to the UI based on the status of the a form. +* `useFormState` allows you to manage state inside a form. + +```js +function Form({ action }) { + async function increment(n) { + return n + 1; + } + const [count, incrementFormAction] = useFormState(increment, 0); + return ( +
+ +
+ ); +} +``` diff --git a/src/content/reference/react-dom/hooks/useFormStatus.md b/src/content/reference/react-dom/hooks/useFormStatus.md new file mode 100644 index 000000000..abaa9b6f2 --- /dev/null +++ b/src/content/reference/react-dom/hooks/useFormStatus.md @@ -0,0 +1,261 @@ +--- +title: useFormStatus +canary: true +--- + + + +The `useFormStatus` Hook 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). + + + + + +`useFormStatus` is a Hook that gives you status information of the last form submission. + +```js +const { pending, data, method, action } = useFormStatus(); +``` + + + + + +--- + +## Reference {/*reference*/} + +### `useFormStatus()` {/*use-form-status*/} + +The `useFormStatus` Hook provides status information of the last form submission. + +```js {5},[[1, 6, "status.pending"]] +import { useFormStatus } from "react-dom"; +import action from './actions'; + +function Submit() { + const status = useFormStatus(); + return +} + +export default App() { + return ( +
+ + + ); +} +``` + +To get status information, the `Submit` component must be rendered within a `
`. The Hook returns information like the `pending` property which tells you if the form is actively submitting. + +In the above example, `Submit` uses this information to disable ` + ); +} + +function Form({ action }) { + return ( + + + + ); +} + +export default function App() { + return
; +} +``` + +```js actions.js hidden +export async function submitForm(query) { + await new Promise((res) => setTimeout(res, 1000)); +} +``` + +```json package.json hidden +{ + "dependencies": { + "react": "canary", + "react-dom": "canary", + "react-scripts": "^5.0.0" + }, + "main": "/index.js", + "devDependencies": {} +} +``` + + + + +##### `useFormStatus` will not return status information for a `` rendered in the same component. {/*useformstatus-will-not-return-status-information-for-a-form-rendered-in-the-same-component*/} + +The `useFormStatus` Hook only returns status information for a parent `` and not for any `` rendered in the same component calling the Hook, or child components. + +```js +function Form() { + // 🚩 `pending` will never be true + // useFormStatus does not track the form rendered in this component + const { pending } = useFormStatus(); + return ; +} +``` + +Instead call `useFormStatus` from inside a component that is located inside `
`. + +```js +function Submit() { + // ✅ `pending` will be derived from the form that wraps the Submit component + const { pending } = useFormStatus(); + return ; +} + +function Form() { + // This is the `useFormStatus` tracks + return ( + + + + ); +} +``` + +
+ +### Read the form data being submitted {/*read-form-data-being-submitted*/} + +You can use the `data` property of the status information returned from `useFormStatus` to display what data is being submitted by the user. + +Here, we have a form where users can request a username. We can use `useFormStatus` to display a temporary status message confirming what username they have requested. + + + +```js UsernameForm.js active +import {useState, useMemo, useRef} from 'react'; +import {useFormStatus} from 'react-dom'; + +export default function UsernameForm() { + const {pending, data} = useFormStatus(); + + const [showSubmitted, setShowSubmitted] = useState(false); + const submittedUsername = useRef(null); + const timeoutId = useRef(null); + + useMemo(() => { + if (pending) { + submittedUsername.current = data?.get('username'); + if (timeoutId.current != null) { + clearTimeout(timeoutId.current); + } + + timeoutId.current = setTimeout(() => { + timeoutId.current = null; + setShowSubmitted(false); + }, 2000); + setShowSubmitted(true); + } + }, [pending, data]); + + return ( + <> +
+ + + {showSubmitted ? ( +

Submitted request for username: {submittedUsername.current}

+ ) : null} + + ); +} +``` + +```js App.js +import UsernameForm from './UsernameForm'; +import { submitForm } from "./actions.js"; + +export default function App() { + return ( +
+ + + ); +} +``` + +```js actions.js hidden +export async function submitForm(query) { + await new Promise((res) => setTimeout(res, 1000)); +} +``` + +```json package.json hidden +{ + "dependencies": { + "react": "canary", + "react-dom": "canary", + "react-scripts": "^5.0.0" + }, + "main": "/index.js", + "devDependencies": {} +} +``` +
+ +--- + +## Troubleshooting {/*troubleshooting*/} + +### `status.pending` is never `true` {/*pending-is-never-true*/} + +`useFormStatus` will only return status information for a parent `
`. + +If the component that calls `useFormStatus` is not nested in a ``, `status.pending` will always return `false`. Verify `useFormStatus` is called in a component that is a child of a `` element. + +`useFormStatus` will not track the status of a `` rendered in the same component. See [Pitfall](#useformstatus-will-not-return-status-information-for-a-form-rendered-in-the-same-component) for more details. diff --git a/src/content/reference/react-dom/server/renderToPipeableStream.md b/src/content/reference/react-dom/server/renderToPipeableStream.md index 6a9021e02..26422f185 100644 --- a/src/content/reference/react-dom/server/renderToPipeableStream.md +++ b/src/content/reference/react-dom/server/renderToPipeableStream.md @@ -288,6 +288,7 @@ Streaming does not need to wait for React itself to load in the browser, or for - Data fetching with Suspense-enabled frameworks like [Relay](https://relay.dev/docs/guided-tour/rendering/loading-states/) and [Next.js](https://nextjs.org/docs/getting-started/react-essentials) - Lazy-loading component code with [`lazy`](/reference/react/lazy) +- Reading the value of a Promise with [`use`](/reference/react/use) Suspense **does not** detect when data is fetched inside an Effect or event handler. diff --git a/src/content/reference/react-dom/server/renderToReadableStream.md b/src/content/reference/react-dom/server/renderToReadableStream.md index 8ef42aa71..f4ed54ce2 100644 --- a/src/content/reference/react-dom/server/renderToReadableStream.md +++ b/src/content/reference/react-dom/server/renderToReadableStream.md @@ -287,6 +287,7 @@ Streaming does not need to wait for React itself to load in the browser, or for - Data fetching with Suspense-enabled frameworks like [Relay](https://relay.dev/docs/guided-tour/rendering/loading-states/) and [Next.js](https://nextjs.org/docs/getting-started/react-essentials) - Lazy-loading component code with [`lazy`](/reference/react/lazy) +- Reading the value of a Promise with [`use`](/reference/react/use) Suspense **does not** detect when data is fetched inside an Effect or event handler. diff --git a/src/content/reference/react/Profiler.md b/src/content/reference/react/Profiler.md index 8e149634a..502ab0d48 100644 --- a/src/content/reference/react/Profiler.md +++ b/src/content/reference/react/Profiler.md @@ -58,7 +58,7 @@ function onRender(id, phase, actualDuration, baseDuration, startTime, commitTime * `actualDuration`: The number of milliseconds spent rendering the `` and its descendants for the current update. This indicates how well the subtree makes use of memoization (e.g. [`memo`](/reference/react/memo) and [`useMemo`](/reference/react/useMemo)). Ideally this value should decrease significantly after the initial mount as many of the descendants will only need to re-render if their specific props change. * `baseDuration`: The number of milliseconds estimating how much time it would take to re-render the entire `` subtree without any optimizations. It is calculated by summing up the most recent render durations of each component in the tree. This value estimates a worst-case cost of rendering (e.g. the initial mount or a tree with no memoization). Compare `actualDuration` against it to see if memoization is working. * `startTime`: A numeric timestamp for when React began rendering the current update. -* `endTime`: A numeric timestamp for when React committed the current update. This value is shared between all profilers in a commit, enabling them to be grouped if desirable. +* `commitTime`: A numeric timestamp for when React committed the current update. This value is shared between all profilers in a commit, enabling them to be grouped if desirable. --- diff --git a/src/content/reference/react/Suspense.md b/src/content/reference/react/Suspense.md index dd9312055..d94003783 100644 --- a/src/content/reference/react/Suspense.md +++ b/src/content/reference/react/Suspense.md @@ -254,6 +254,7 @@ async function getAlbums() { - Data fetching with Suspense-enabled frameworks like [Relay](https://relay.dev/docs/guided-tour/rendering/loading-states/) and [Next.js](https://nextjs.org/docs/getting-started/react-essentials) - Lazy-loading component code with [`lazy`](/reference/react/lazy) +- Reading the value of a Promise with [`use`](/reference/react/use) Suspense **does not** detect when data is fetched inside an Effect or event handler. diff --git a/src/content/reference/react/cache.md b/src/content/reference/react/cache.md index 72fa4bd36..65d95ab76 100644 --- a/src/content/reference/react/cache.md +++ b/src/content/reference/react/cache.md @@ -252,7 +252,7 @@ function Page({id}) { When rendering `Page`, the component calls `getUser` but note that it doesn't use the returned data. This early `getUser` call kicks off the asynchronous database query that occurs while `Page` is doing other computational work and rendering children. -When rendering `Profile`, we call `getUser` again. If the initial `getUser` call has already returned and cached the user data, when when `Profile` asks and waits for this data, it can simply read from the cache without requiring another remote procedure call. If the initial data request hasn't been completed, preloading data in this pattern reduces delay in data-fetching. +When rendering `Profile`, we call `getUser` again. If the initial `getUser` call has already returned and cached the user data, when `Profile` asks and waits for this data, it can simply read from the cache without requiring another remote procedure call. If the initial data request hasn't been completed, preloading data in this pattern reduces delay in data-fetching. @@ -414,7 +414,7 @@ See prior mentioned pitfalls If none of the above apply, it may be a problem with how React checks if something exists in cache. -If your arguments are not [primatives](https://developer.mozilla.org/en-US/docs/Glossary/Primitive) (ex. objects, functions, arrays), ensure you're passing the same object reference. +If your arguments are not [primitives](https://developer.mozilla.org/en-US/docs/Glossary/Primitive) (ex. objects, functions, arrays), ensure you're passing the same object reference. When calling a memoized function, React will look up the input arguments to see if a result is already cached. React will use shallow equality of the arguments to determine if there is a cache hit. diff --git a/src/content/reference/react/experimental_taintObjectReference.md b/src/content/reference/react/experimental_taintObjectReference.md new file mode 100644 index 000000000..e3fd35a48 --- /dev/null +++ b/src/content/reference/react/experimental_taintObjectReference.md @@ -0,0 +1,153 @@ +--- +title: experimental_taintObjectReference +--- + + + +**This API is experimental and is not available in a stable version of React yet.** + +You can try it by upgrading React packages to the most recent experimental version: + +- `react@experimental` +- `react-dom@experimental` +- `eslint-plugin-react-hooks@experimental` + +Experimental versions of React may contain bugs. Don't use them in production. + +This API is only available inside React Server Components. + + + + + + +`taintObjectReference` lets you prevent a specific object instance from being passed to a Client Component like a `user` object. + +```js +experimental_taintObjectReference(message, object); +``` + +To prevent passing a key, hash or token, see [`taintUniqueValue`](/reference/react/experimental_taintUniqueValue). + + + + + +--- + +## Reference {/*reference*/} + +### `taintObjectReference(message, object)` {/*taintobjectreference*/} + +Call `taintObjectReference` with an object to register it with React as something that should not be allowed to be passed to the Client as is: + +```js +import {experimental_taintObjectReference} from 'react'; + +experimental_taintObjectReference( + 'Do not pass ALL environment variables to the client.', + process.env +); +``` + +[See more examples below.](#usage) + +#### Parameters {/*parameters*/} + +* `message`: The message you want to display if the object gets passed to a Client Component. This message will be displayed as a part of the Error that will be thrown if the object gets passed to a Client Component. + +* `object`: The object to be tainted. Functions and class instances can be passed to `taintObjectReference` as `object`. Functions and classes are already blocked from being passed to Client Components but the React's default error message will be replaced by what you defined in `message`. When a specific instance of a Typed Array is passed to `taintObjectReference` as `object`, any other copies of the Typed Array will not be tainted. + +#### Returns {/*returns*/} + +`experimental_taintObjectReference` returns `undefined`. + +#### Caveats {/*caveats*/} + +- Recreating or cloning a tainted object creates a new untained object which main contain sensetive data. For example, if you have a tainted `user` object, `const userInfo = {name: user.name, ssn: user.ssn}` or `{...user}` will create new objects which are not tainted. `taintObjectReference` only protects against simple mistakes when the object is passed through to a Client Component unchanged. + + + +**Do not rely on just tainting for security.** Tainting an object doesn't prevent leaking of every possible derived value. For example, the clone of a tainted object will create a new untained object. Using data from a tainted object (e.g. `{secret: taintedObj.secret}`) will create a new value or object that is not tainted. Tainting is a layer of protection; a secure app will have multiple layers of protection, well designed APIs, and isolation patterns. + + + +--- + +## Usage {/*usage*/} + +### Prevent user data from unintentionally reaching the client {/*prevent-user-data-from-unintentionally-reaching-the-client*/} + +A Client Component should never accept objects that carry sensitive data. Ideally, the data fetching functions should not expose data that the current user should not have access to. Sometimes mistakes happen during refactoring. To protect against this mistakes happening down the line we can "taint" the user object in our data API. + +```js +import {experimental_taintObjectReference} from 'react'; + +export async function getUser(id) { + const user = await db`SELECT * FROM users WHERE id = ${id}`; + experimental_taintObjectReference( + 'Do not pass the entire user object to the client. ' + + 'Instead, pick off the specific properties you need for this use case.', + user, + ); + return user; +} +``` + +Now whenever anyone tries to pass this object to a Client Component, an error will be thrown with the passed in error message instead. + + + +#### Protecting against leaks in data fetching {/*protecting-against-leaks-in-data-fetching*/} + +If you're running a Server Components environment that has access to sensitive data, you have to be careful not to pass objects straight through: + +```js +// api.js +export async function getUser(id) { + const user = await db`SELECT * FROM users WHERE id = ${id}`; + return user; +} +``` + +```js +import { getUser } from 'api.js'; +import { InfoCard } from 'components.js'; + +export async function Profile(props) { + const user = await getUser(props.userId); + // DO NOT DO THIS + return ; +} +``` + +```js +// components.js +"use client"; + +export async function InfoCard({ user }) { + return
{user.name}
; +} +``` + +Ideally, the `getUser` should not expose data that the current user should not have access to. To prevent passing the `user` object to a Client Component down the line we can "taint" the user object: + + +```js +// api.js +import {experimental_taintObjectReference} from 'react'; + +export async function getUser(id) { + const user = await db`SELECT * FROM users WHERE id = ${id}`; + experimental_taintObjectReference( + 'Do not pass the entire user object to the client. ' + + 'Instead, pick off the specific properties you need for this use case.', + user, + ); + return user; +} +``` + +Now if anyone tries to pass the `user` object to a Client Component, an error will be thrown with the passed in error message. + +
diff --git a/src/content/reference/react/experimental_taintUniqueValue.md b/src/content/reference/react/experimental_taintUniqueValue.md new file mode 100644 index 000000000..a67eebf77 --- /dev/null +++ b/src/content/reference/react/experimental_taintUniqueValue.md @@ -0,0 +1,198 @@ +--- +title: experimental_taintUniqueValue +--- + + + +**This API is experimental and is not available in a stable version of React yet.** + +You can try it by upgrading React packages to the most recent experimental version: + +- `react@experimental` +- `react-dom@experimental` +- `eslint-plugin-react-hooks@experimental` + +Experimental versions of React may contain bugs. Don't use them in production. + +This API is only available inside [React Server Components](/reference/react/use-client). + + + + + + +`taintUniqueValue` lets you prevent unique values from being passed to Client Components like passwords, keys, or tokens. + +```js +taintUniqueValue(errMessage, lifetime, value) +``` + +To prevent passing an object containing sensitive data, see [`taintObjectReference`](/reference/react/experimental_taintObjectReference). + + + + + +--- + +## Reference {/*reference*/} + +### `taintUniqueValue(message, lifetime, value)` {/*taintuniquevalue*/} + +Call `taintUniqueValue` with a password, token, key or hash to register it with React as something that should not be allowed to be passed to the Client as is: + +```js +import {experimental_taintUniqueValue} from 'react'; + +experimental_taintUniqueValue( + 'Do not pass secret keys to the client.', + process, + process.env.SECRET_KEY +); +``` + +[See more examples below.](#usage) + +#### Parameters {/*parameters*/} + +* `message`: The message you want to display if `value` is passed to a Client Component. This message will be displayed as a part of the Error that will be thrown if `value` is passed to a Client Component. + +* `lifetime`: Any object that indicates how long `value` should be tainted. `value` will be blocked from being sent to any Client Component while this object still exists. For example, passing `globalThis` blocks the value for the lifetime of an app. `lifetime` is typically an object whose properties contains `value`. + +* `value`: A string, bigint or TypedArray. `value` must be a unique sequence of characters or bytes with high entropy such as a cryptographic token, private key, hash, or a long password. `value` will be blocked from being sent to any Client Component. + +#### Returns {/*returns*/} + +`experimental_taintUniqueValue` returns `undefined`. + +#### Caveats {/*caveats*/} + +- Deriving new values from tainted values can compromise tainting protection. New values created by uppercasing tainted values, concatenating tainted string values into a larger string, converting tainted values to base64, substringing tainted values, and other similar transformations are not tainted unless you explicity call `taintUniqueValue` on these newly created values. + +--- + +## Usage {/*usage*/} + +### Prevent a token from being passed to Client Components {/*prevent-a-token-from-being-passed-to-client-components*/} + +To ensure that sensitive information such as passwords, session tokens, or other unique values do not inadvertently get passed to Client Components, the `taintUniqueValue` function provides a layer of protection. When a value is tainted, any attempt to pass it to a Client Component will result in an error. + +The `lifetime` argument defines the duration for which the value remains tainted. For values that should remain tainted indefinitely, objects like [`globalThis`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/globalThis) or `process` can serve as the `lifetime` argument. These objects have a lifespan that spans the entire duration of your app's execution. + +```js +import {experimental_taintUniqueValue} from 'react'; + +experimental_taintUniqueValue( + 'Do not pass a user password to the client.', + globalThis, + process.env.SECRET_KEY +); +``` + +If the tainted value's lifespan is tied to a object, the `lifetime` should be the object that encapsulates the value. This ensures the tainted value remains protected for the lifetime of the encapsulating object. + +```js +import {experimental_taintUniqueValue} from 'react'; + +export async function getUser(id) { + const user = await db`SELECT * FROM users WHERE id = ${id}`; + experimental_taintUniqueValue( + 'Do not pass a user session token to the client.', + user, + user.session.token + ); + return user; +} +``` + +In this example, the `user` object serves as the `lifetime` argument. If this object gets stored in a global cache or is accessible by another request, the session token remains tainted. + + + +**Do not rely solely on tainting for security.** Tainting a value doesn't block every possible derived value. For example, creating a new value by upper casing a tainted string will not taint the new value. + + +```js +import {experimental_taintUniqueValue} from 'react'; + +const password = 'correct horse battery staple'; + +experimental_taintUniqueValue( + 'Do not pass the password to the client.', + globalThis, + password +); + +const uppercasePassword = password.toUpperCase() // `uppercasePassword` is not tainted +``` + +In this example, the constant `password` is tainted. Then `password` is used to create a new value `uppercasePassword` by calling the `toUpperCase` method on `password`. The newly created `uppercasePassword` is not tainted. + +Other similar ways of deriving new values from tainted values like concatenating it into a larger string, converting it to base64, or returning a substring create untained values. + +Tainting only protects against simple mistakes like explictly passing secret values to the client. Mistakes in calling the `taintUniqueValue` like using a global store outside of React, without the corresponding lifetime object, can cause the tainted value to become untainted. Tainting is a layer of protection; a secure app will have multiple layers of protection, well designed APIs, and isolation patterns. + + + + + +#### Using `server-only` and `taintUniqueValue` to prevent leaking secrets {/*using-server-only-and-taintuniquevalue-to-prevent-leaking-secrets*/} + +If you're running a Server Components environment that has access to private keys or passwords such as database passwords, you have to be careful not to pass that to a Client Component. + +```js +export async function Dashboard(props) { + // DO NOT DO THIS + return ; +} +``` + +```js +"use client"; + +import {useEffect} from '...' + +export async function Overview({ password }) { + useEffect(() => { + const headers = { Authorization: password }; + fetch(url, { headers }).then(...); + }, [password]); + ... +} +``` + +This example would leak the secret API token to the client. If this API token can be used to access data this particular user shouldn't have access to, it could lead to a data breach. + +[comment]: <> (TODO: Link to `server-only` docs once they are written) + +Ideally, secrets like this are abstracted into a single helper file that can only be imported by trusted data utilities on the server. The helper can even be tagged with [`server-only`](https://www.npmjs.com/package/server-only) to ensure that this file isn't imported on the client. + +```js +import "server-only"; + +export function fetchAPI(url) { + const headers = { Authorization: process.env.API_PASSWORD }; + return fetch(url, { headers }); +} +``` + +Sometimes mistakes happen during refactoring and not all of your colleagues might know about this. +To protect against this mistakes happening down the line we can "taint" the actual password: + +```js +import "server-only"; +import {experimental_taintUniqueValue} from 'react'; + +experimental_taintUniqueValue( + 'Do not pass the API token password to the client. ' + + 'Instead do all fetches on the server.' + process, + process.env.API_PASSWORD +); +``` + +Now whenever anyone tries to pass this password to a Client Component, or send the password to a Client Component with a Server Action, a error will be thrown with message you defined when you called `taintUniqueValue`. + + + +--- diff --git a/src/content/reference/react/useDeferredValue.md b/src/content/reference/react/useDeferredValue.md index f25054542..74fdab8ae 100644 --- a/src/content/reference/react/useDeferredValue.md +++ b/src/content/reference/react/useDeferredValue.md @@ -82,10 +82,11 @@ During updates, the deferred value will "lag behin -This example assumes you use one of Suspense-enabled data sources: +This example assumes you use a Suspense-enabled data source: - Data fetching with Suspense-enabled frameworks like [Relay](https://relay.dev/docs/guided-tour/rendering/loading-states/) and [Next.js](https://nextjs.org/docs/getting-started/react-essentials) - Lazy-loading component code with [`lazy`](/reference/react/lazy) +- Reading the value of a Promise with [`use`](/reference/react/use) [Learn more about Suspense and its limitations.](/reference/react/Suspense) diff --git a/src/content/reference/react/useMemo.md b/src/content/reference/react/useMemo.md index 543c11125..c96c1a942 100644 --- a/src/content/reference/react/useMemo.md +++ b/src/content/reference/react/useMemo.md @@ -647,7 +647,7 @@ In this example, the `List` component is **artificially slowed down** so that yo Switching the tabs feels slow because it forces the slowed down `List` to re-render. That's expected because the `tab` has changed, and so you need to reflect the user's new choice on the screen. -Next, try toggling the theme. **Thanks to `useMemo` together with [`memo`](/reference/react/memo), it’s fast despite the artificial slowdown!** The `List` skipped re-rendering because the `visibleItems` array has not changed since the last render. The `visibleItems` array has not changed because both `todos` and `tab` (which you pass as dependencies to `useMemo`) haven't changed since the last render. +Next, try toggling the theme. **Thanks to `useMemo` together with [`memo`](/reference/react/memo), it’s fast despite the artificial slowdown!** The `List` skipped re-rendering because the `visibleTodos` array has not changed since the last render. The `visibleTodos` array has not changed because both `todos` and `tab` (which you pass as dependencies to `useMemo`) haven't changed since the last render. diff --git a/src/content/reference/react/useSyncExternalStore.md b/src/content/reference/react/useSyncExternalStore.md index 4169e231e..955b751f8 100644 --- a/src/content/reference/react/useSyncExternalStore.md +++ b/src/content/reference/react/useSyncExternalStore.md @@ -57,6 +57,26 @@ The current snapshot of the store which you can use in your rendering logic. * If a different `subscribe` function is passed during a re-render, React will re-subscribe to the store using the newly passed `subscribe` function. You can prevent this by declaring `subscribe` outside the component. +* If the store is mutated during a [non-blocking transition update](/reference/react/useTransition), React will fall back to performing that update as blocking. Specifically, React will call `getSnapshot` a second time just before applying changes to the DOM. If it returns a different value than when it was called originally, React will restart the transition update from scratch, this time applying it as a blocking update, to ensure that every component on screen is reflecting the same version of the store. + +* It's not recommended to _suspend_ a render based on a store value returned by `useSyncExternalStore`. The reason is that mutations to the external store cannot be [marked as non-blocking transition updates](/reference/react/useTransition), so they will trigger the nearest [`Suspense` fallback](/reference/react/Suspense), replacing already-rendered content on screen with a loading spinner, which typically makes a poor UX. + + For example, the following are discouraged: + + ```js + const LazyProductDetailPage = lazy(() => import('./ProductDetailPage.js')); + + function ShoppingApp() { + const selectedProductId = useSyncExternalStore(...); + + // ❌ Calling `use` with a Promise dependent on `selectedProductId` + const data = use(fetchItem(selectedProductId)) + + // ❌ Conditionally rendering a lazy component based on `selectedProductId` + return selectedProductId != null ? : ; + } + ``` + --- ## Usage {/*usage*/} @@ -425,4 +445,4 @@ function ChatIndicator({ userId }) { // ... } -``` \ No newline at end of file +``` diff --git a/src/pages/_document.tsx b/src/pages/_document.tsx index 392fdf88a..d8fb0c621 100644 --- a/src/pages/_document.tsx +++ b/src/pages/_document.tsx @@ -9,6 +9,15 @@ const MyDocument = () => { return ( +