diff --git a/.env.production b/.env.production index d25eb7dd4..e403f96b6 100644 --- a/.env.production +++ b/.env.production @@ -1 +1 @@ -NEXT_PUBLIC_GA_TRACKING_ID = 'UA-41298772-4' \ No newline at end of file +NEXT_PUBLIC_GA_TRACKING_ID = 'G-B1E83PJ3RT' \ No newline at end of file diff --git a/package.json b/package.json index 5ff6adbab..b5e07d70a 100644 --- a/package.json +++ b/package.json @@ -30,13 +30,12 @@ "classnames": "^2.2.6", "date-fns": "^2.16.1", "debounce": "^1.2.1", - "ga-lite": "^2.1.4", "github-slugger": "^1.3.0", "next": "^13.4.1", "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/Feedback.tsx b/src/components/Layout/Feedback.tsx index 2bf9afe57..86fc91350 100644 --- a/src/components/Layout/Feedback.tsx +++ b/src/components/Layout/Feedback.tsx @@ -4,7 +4,6 @@ import {useState} from 'react'; import {useRouter} from 'next/router'; -import {ga} from '../../utils/analytics'; export function Feedback({onSubmit = () => {}}: {onSubmit?: () => void}) { const {asPath} = useRouter(); @@ -48,14 +47,12 @@ const thumbsDownIcon = ( function sendGAEvent(isPositive: boolean) { // Fragile. Don't change unless you've tested the network payload // and verified that the right events actually show up in GA. - ga( - 'send', - 'event', - 'button', - 'feedback', - window.location.pathname, - isPositive ? '1' : '0' - ); + // @ts-ignore + gtag('event', 'feedback', { + event_category: 'button', + event_label: window.location.pathname, + value: isPositive ? 1 : 0, + }); } function SendFeedback({onSubmit}: {onSubmit: () => void}) { diff --git a/src/components/Layout/Page.tsx b/src/components/Layout/Page.tsx index fb771f901..04876bab3 100644 --- a/src/components/Layout/Page.tsx +++ b/src/components/Layout/Page.tsx @@ -28,7 +28,12 @@ interface PageProps { children: React.ReactNode; toc: Array; routeTree: RouteItem; - meta: {title?: string; canary?: boolean; description?: string}; + meta: { + title?: string; + titleForTitleTag?: string; + canary?: boolean; + description?: string; + }; section: 'learn' | 'reference' | 'community' | 'blog' | 'home' | 'unknown'; } @@ -107,6 +112,7 @@ export function Page({children, toc, routeTree, meta, section}: PageProps) { <> { if (lintErrors.length === 0) { diff --git a/src/components/Seo.tsx b/src/components/Seo.tsx index d0dcaab75..5af169e13 100644 --- a/src/components/Seo.tsx +++ b/src/components/Seo.tsx @@ -9,6 +9,7 @@ import {siteConfig} from '../siteConfig'; export interface SeoProps { title: string; + titleForTitleTag: undefined | string; description?: string; image?: string; // jsonld?: JsonLDType | Array; @@ -22,6 +23,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. @@ -35,7 +37,7 @@ function getDomain(languageCode: string): string { export const Seo = withRouter( ({ title, - description = 'The library for web and native user interfaces', + titleForTitleTag, image = '/images/og-default.png', router, children, @@ -46,14 +48,20 @@ export const Seo = withRouter( const canonicalUrl = `https://${siteDomain}${ router.asPath.split(/[\?\#]/)[0] }`; - const pageTitle = isHomePage ? title : title + ' – React'; + // Allow setting a different title for Google results + const pageTitle = + (titleForTitleTag ?? title) + (isHomePage ? '' : ' – React'); // Twitter's meta parser is not very good. const twitterTitle = pageTitle.replace(/[<>]/g, ''); + let description = isHomePage + ? 'React is the library for web and native user interfaces. Build user interfaces out of individual pieces called components written in JavaScript. React is designed to let you seamlessly combine components written by independent people, teams, and organizations.' + : 'The library for web and native user interfaces'; return ( {title != null && {pageTitle}} - {description != null && ( + {isHomePage && ( + // Let Google figure out a good description for each page. )} 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 817d9004b..3a4ee7f65 100644 --- a/src/content/learn/describing-the-ui.md +++ b/src/content/learn/describing-the-ui.md @@ -10,6 +10,7 @@ React ແມ່ນ JavaScript library ສຳລັບສະແດງຜົນໜ +<<<<<<< HEAD * [ວິທີຂຽນ React component ທຳອິດຂອງທ່ານ](/learn/your-first-component) * [ເມືອໃດ ແລະ ວິທີການສ້າງຫຼາຍຟາຍ component](/learn/importing-and-exporting-components) * [ວິທີການເພີ່ມ markup ໃສ່ JavaScript ດ້ວຍ JSX](/learn/writing-markup-with-jsx) @@ -18,6 +19,17 @@ React ແມ່ນ JavaScript library ສຳລັບສະແດງຜົນໜ * [ວິທີການສະແດງ component ແບບມີເງື່ອນໄຂ](/learn/conditional-rendering) * [ວິທີການສະແດງຫຼາຍ component ພ້ອມກັນ](/learn/rendering-lists) * [ວິທີການຫຼີກເວັ້ນຂໍ້ຜິດພາດໂດຍການຮັກສາໃຫ້ component pure ທີ່ສຸດ](/learn/keeping-components-pure) +======= +* [How to write your first React component](/learn/your-first-component) +* [When and how to create multi-component files](/learn/importing-and-exporting-components) +* [How to add markup to JavaScript with JSX](/learn/writing-markup-with-jsx) +* [How to use curly braces with JSX to access JavaScript functionality from your components](/learn/javascript-in-jsx-with-curly-braces) +* [How to configure components with props](/learn/passing-props-to-a-component) +* [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) +>>>>>>> 4bdb87b172a7723d56d03a5630c8a9870f6f03ec @@ -523,7 +535,42 @@ export default function TeaSet() { +<<<<<<< HEAD ## ຕໍ່ໄປແມ່ນຫຍັງ? {/*whats-next*/} +======= +## 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*/} +>>>>>>> 4bdb87b172a7723d56d03a5630c8a9870f6f03ec ໄປທີ່ [Component ທຳອິດຂອງທ່ານ](/learn/your-first-component) ເພື່ອເລີ່ມອ່ານບົດນີ້ເທື່ອລະໜ້າ! 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/reacting-to-input-with-state.md b/src/content/learn/reacting-to-input-with-state.md index 522aa63a1..29f60ca6d 100644 --- a/src/content/learn/reacting-to-input-with-state.md +++ b/src/content/learn/reacting-to-input-with-state.md @@ -84,7 +84,7 @@ function submitForm(answer) { // Pretend it's hitting the network. return new Promise((resolve, reject) => { setTimeout(() => { - if (answer.toLowerCase() == 'istanbul') { + if (answer.toLowerCase() === 'istanbul') { resolve(); } else { reject(new Error('Good guess but a wrong answer. Try again!')); 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..98f60cea8 --- /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 passes, 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/components/form.md b/src/content/reference/react-dom/components/form.md new file mode 100644 index 000000000..7c6023220 --- /dev/null +++ b/src/content/reference/react-dom/components/form.md @@ -0,0 +1,435 @@ +--- +title: "
" +canary: true +--- + + + +React's extensions to `` are currently only available in React's canary and experimental channels. In stable releases of React `` works only as a [built-in browser HTML component](https://react.dev/reference/react-dom/components#all-html-components). Learn more about [React's release channels here](/community/versioning-policy#all-release-channels). + + + + + + +The [built-in browser `` component](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form) lets you create interactive controls for submitting information. + +```js + + + + +``` + +
+ + + +--- + +## Reference {/*reference*/} + +### `
` {/*form*/} + +To create interactive controls for submitting information, render the [built-in browser `` component](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form). + +```js + + + +
+``` + +[See more examples below.](#usage) + +#### Props {/*props*/} + +`
` supports all [common element props.](/reference/react-dom/components/common#props) + +[`action`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form#action): a URL or function. When a URL is passed to `action` the form will behave like the HTML form component. When a function is passed to `action` the function will handle the form submission. The function passed to `action` may be async and will be called with a single argument containing the [form data](https://developer.mozilla.org/en-US/docs/Web/API/FormData) of the submitted form. The `action` prop can be overridden by a `formAction` attribute on a ` +
+ ); +} +``` + +```json package.json hidden +{ + "dependencies": { + "react": "18.3.0-canary-6db7f4209-20231021", + "react-dom": "18.3.0-canary-6db7f4209-20231021", + "react-scripts": "^5.0.0" + }, + "main": "/index.js", + "devDependencies": {} +} +``` + + + +### Handle form submission with a Server Action {/*handle-form-submission-with-a-server-action*/} + +Render a `
` with an input and submit button. Pass a server action (a function marked with [`'use server'`](/reference/react/use-server)) to the `action` prop of form to run the function when the form is submitted. + +Passing a server action to `` allow users to submit forms without JavaScript enabled or before the code has loaded. This is beneficial to users who have a slow connection, device, or have JavaScript disabled and is similar to the way forms work when a URL is passed to the `action` prop. + +You can use hidden form fields to provide data to the ``'s action. The server action will be called with the hidden form field data as an instance of [`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData). + +```jsx +import { updateCart } from './lib.js'; + +function AddToCart({productId}) { + async function addToCart(formData) { + 'use server' + const productId = formData.get('productId') + await updateCart(productId) + } + return ( + + + +
+ + ); +} +``` + +In lieu of using hidden form fields to provide data to the `
`'s action, you can call the `bind` method to supply it with extra arguments. This will bind a new argument (`productId`) to the function in addition to the `formData` that is passed as a argument to the function. + +```jsx [[1, 8, "bind"], [2,8, "productId"], [2,4, "productId"], [3,4, "formData"]] +import { updateCart } from './lib.js'; + +function AddToCart({productId}) { + async function addToCart(productId, formData) { + "use server"; + await updateCart(productId) + } + const addProductToCart = addToCart.bind(null, productId); + return ( + + +
+ ); +} +``` + +When `
` is rendered by a [Server Component](/reference/react/use-client), and a [Server Action](/reference/react/use-server) is passed to the ``'s `action` prop, the form is [progressively enhanced](https://developer.mozilla.org/en-US/docs/Glossary/Progressive_Enhancement). + +### Display a pending state during form submission {/*display-a-pending-state-during-form-submission*/} +To display a pending state when a form is being submitted, you can call the `useFormStatus` Hook in a component rendered in a `` and read the `pending` property returned. + +Here, we use the `pending` property to indicate the form is submitting. + + + +```js App.js +import { useFormStatus } from "react-dom"; +import { submitForm } from "./actions.js"; + +function Submit() { + const { pending } = useFormStatus(); + return ( + + ); +} + +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": {} +} +``` + + +To learn more about the `useFormStatus` Hook see the [reference documentation](/reference/react-dom/hooks/useFormStatus). + +### Optimistically updating form data {/*optimistically-updating-form-data*/} +The `useOptimistic` Hook provides a way to optimistically update the user interface before a background operation, like a network request, completes. In the context of forms, this technique helps to make apps feel more responsive. When a user submits a form, instead of waiting for the server's response to reflect the changes, the interface is immediately updated with the expected outcome. + +For example, when a user types a message into the form and hits the "Send" button, the `useOptimistic` Hook allows the message to immediately appear in the list with a "Sending..." label, even before the message is actually sent to a server. This "optimistic" approach gives the impression of speed and responsiveness. The form then attempts to truly send the message in the background. Once the server confirms the message has been received, the "Sending..." label is removed. + + + + +```js App.js +import { useOptimistic, useState, useRef } from "react"; +import { deliverMessage } from "./actions.js"; + +function Thread({ messages, sendMessage }) { + const formRef = useRef(); + async function formAction(formData) { + addOptimisticMessage(formData.get("message")); + formRef.current.reset(); + await sendMessage(formData); + } + const [optimisticMessages, addOptimisticMessage] = useOptimistic( + messages, + (state, newMessage) => [ + ...state, + { + text: newMessage, + sending: true + } + ] + ); + + return ( + <> + {optimisticMessages.map((message, index) => ( +
+ {message.text} + {!!message.sending && (Sending...)} +
+ ))} + + + + + + ); +} + +export default function App() { + const [messages, setMessages] = useState([ + { text: "Hello there!", sending: false, key: 1 } + ]); + async function sendMessage(formData) { + const sentMessage = await deliverMessage(formData.get("message")); + setMessages([...messages, { text: sentMessage }]); + } + return ; +} +``` + +```js actions.js +export async function deliverMessage(message) { + await new Promise((res) => setTimeout(res, 1000)); + return message; +} +``` + + +```json package.json hidden +{ + "dependencies": { + "react": "18.3.0-canary-6db7f4209-20231021", + "react-dom": "18.3.0-canary-6db7f4209-20231021", + "react-scripts": "^5.0.0" + }, + "main": "/index.js", + "devDependencies": {} +} +``` + +
+ +[//]: # 'Uncomment the next line, and delete this line after the `useOptimisitc` reference documentatino page is published' +[//]: # 'To learn more about the `useOptimistic` Hook see the [reference documentation](/reference/react/hooks/useOptimistic).' + +### Handling form submission errors {/*handling-form-submission-errors*/} + +In some cases the function called by a `
`'s `action` prop throw an error. You can handle these errors by wrapping `` in an Error Boundary. If the function called by a ``'s `action` prop throws an error, the fallback for the error boundary will be displayed. + + + +```js App.js +import { ErrorBoundary } from "react-error-boundary"; + +export default function Search() { + function search() { + throw new Error("search error"); + } + return ( + There was an error while submitting the form

} + > + + + + +
+ ); +} + +``` + +```json package.json hidden +{ + "dependencies": { + "react": "18.3.0-canary-6db7f4209-20231021", + "react-dom": "18.3.0-canary-6db7f4209-20231021", + "react-scripts": "^5.0.0", + "react-error-boundary": "4.0.3" + }, + "main": "/index.js", + "devDependencies": {} +} +``` + +
+ +### Display a form submission error without JavaScript {/*display-a-form-submission-error-without-javascript*/} + +Displaying a form submission error message before the JavaScript bundle loads for progressive enhancement requires that: + +1. `
` be rendered by a [Server Component](/reference/react/use-client) +1. the function passed to the ``'s `action` prop be a [Server Action](/reference/react/use-server) +1. the `useFormState` Hook be used to display the error message + +`useFormState` takes two parameters: a [Server Action](/reference/react/use-server) and an initial state. `useFormState` returns two values, a state variable and an action. The action returned by `useFormState` should be passed to the `action` prop of the form. The state variable returned by `useFormState` can be used to displayed an error message. The value returned by the [Server Action](/reference/react/use-server) passed to `useFormState` will be used to update the state variable. + + + +```js App.js +import { useFormState } from "react-dom"; +import { signUpNewUser } from "./api"; + +export default function Page() { + async function signup(prevState, formData) { + "use server"; + const email = formData.get("email"); + try { + await signUpNewUser(email); + alert(`Added "${email}"`); + } catch (err) { + return err.toString(); + } + } + const [message, formAction] = useFormState(signup, null); + return ( + <> +

Signup for my newsletter

+

Signup with the same email twice to see an error

+ + + + + {!!message &&

{message}

} + + + ); +} +``` + +```js api.js hidden +let emails = []; + +export async function signUpNewUser(newEmail) { + if (emails.includes(newEmail)) { + throw new Error("This email address has already been added"); + } + emails.push(newEmail); +} +``` + +```json package.json hidden +{ + "dependencies": { + "react": "18.3.0-canary-6db7f4209-20231021", + "react-dom": "18.3.0-canary-6db7f4209-20231021", + "react-scripts": "^5.0.0" + }, + "main": "/index.js", + "devDependencies": {} +} +``` + +
+ +Learn more about updating state from a form action with the [`useFormState`](/reference/react-dom/hooks/useFormState) docs + +### Handling multiple submission types {/*handling-multiple-submission-types*/} + +Forms can be designed to handle multiple submission actions based on the button pressed by the user. Each button inside a form can be associated with a distinct action or behavior by setting the `formAction` prop. + +When a user taps a specific button, the form is submitted, and a corresponding action, defined by that button's attributes and action, is executed. For instance, a form might submit an article for review by default but have a separate button with `formAction` set to save the article as a draft. + + + +```js App.js +export default function Search() { + function publish(formData) { + const content = formData.get("content"); + const button = formData.get("button"); + alert(`'${content}' was published with the '${button}' button`); + } + + function save(formData) { + const content = formData.get("content"); + alert(`Your draft of '${content}' has been saved!`); + } + + return ( +
+