diff --git a/.gitignore b/.gitignore index d8bec488b..7bf71dbc5 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,6 @@ yarn-error.log* # external fonts public/fonts/**/Optimistic_*.woff2 + +# rss +public/rss.xml diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0e861af35..4c7e5ec74 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -79,6 +79,7 @@ Ignore this rule if you're specifically describing an experimental proposal. Mak - Use semicolons. - No space between function names and parens (`method() {}` not `method () {}`). - When in doubt, use the default style favored by [Prettier](https://prettier.io/playground/). +- Always capitalize React concepts such as Hooks, Effects, and Transitions. ### Highlighting diff --git a/colors.js b/colors.js index acf8214ee..872f33cac 100644 --- a/colors.js +++ b/colors.js @@ -11,7 +11,7 @@ module.exports = { tertiary: '#5E687E', // gray-50 'tertiary-dark': '#99A1B3', // gray-30 link: '#087EA4', // blue-50 - 'link-dark': '#149ECA', // blue-40 + 'link-dark': '#58C4DC', // blue-40 syntax: '#EBECF0', // gray-10 wash: '#FFFFFF', 'wash-dark': '#23272F', // gray-90 @@ -23,6 +23,8 @@ module.exports = { 'border-dark': '#343A46', // gray-80 'secondary-button': '#EBECF0', // gray-10 'secondary-button-dark': '#404756', // gray-70 + brand: '#087EA4', // blue-40 + 'brand-dark': '#58C4DC', // blue-40 // Gray 'gray-95': '#16181D', diff --git a/package.json b/package.json index 274c2506e..1f63eb121 100644 --- a/package.json +++ b/package.json @@ -15,17 +15,19 @@ "prettier:diff": "yarn nit:source", "lint-heading-ids": "node scripts/headingIdLinter.js", "fix-headings": "node scripts/headingIdLinter.js --fix", - "ci-check": "npm-run-all prettier:diff --parallel lint tsc lint-heading-ids", + "ci-check": "npm-run-all prettier:diff --parallel lint tsc lint-heading-ids rss", "tsc": "tsc --noEmit", "start": "next start", "postinstall": "patch-package && (is-ci || husky install .husky)", - "check-all": "npm-run-all prettier lint:fix tsc" + "check-all": "npm-run-all prettier lint:fix tsc rss", + "rss": "node scripts/generateRss.js" }, "dependencies": { - "@codesandbox/sandpack-react": "2.13.1", + "@codesandbox/sandpack-react": "2.13.5", "@docsearch/css": "3.0.0-alpha.41", "@docsearch/react": "3.0.0-alpha.41", "@headlessui/react": "^1.7.0", + "@radix-ui/react-context-menu": "^2.1.5", "body-scroll-lock": "^3.1.3", "classnames": "^2.2.6", "date-fns": "^2.16.1", @@ -97,7 +99,7 @@ "webpack-bundle-analyzer": "^4.5.0" }, "engines": { - "node": "^16.8.0 || ^18.0.0 || ^19.0.0 || ^20.0.0" + "node": ">=16.8.0" }, "nextBundleAnalysis": { "budget": null, diff --git a/public/android-chrome-192x192.png b/public/android-chrome-192x192.png new file mode 100644 index 000000000..5de701e13 Binary files /dev/null and b/public/android-chrome-192x192.png differ diff --git a/public/android-chrome-384x384.png b/public/android-chrome-384x384.png new file mode 100644 index 000000000..f42a6776e Binary files /dev/null and b/public/android-chrome-384x384.png differ diff --git a/public/android-chrome-512x512.png b/public/android-chrome-512x512.png new file mode 100644 index 000000000..2fdbf6902 Binary files /dev/null and b/public/android-chrome-512x512.png differ diff --git a/public/apple-touch-icon.png b/public/apple-touch-icon.png new file mode 100644 index 000000000..baf1332a3 Binary files /dev/null and b/public/apple-touch-icon.png differ diff --git a/public/browserconfig.xml b/public/browserconfig.xml new file mode 100644 index 000000000..f9c2e67fe --- /dev/null +++ b/public/browserconfig.xml @@ -0,0 +1,9 @@ + + + + + + #2b5797 + + + diff --git a/public/favicon-16x16.png b/public/favicon-16x16.png new file mode 100644 index 000000000..d24cb4f76 Binary files /dev/null and b/public/favicon-16x16.png differ diff --git a/public/favicon-32x32.png b/public/favicon-32x32.png new file mode 100644 index 000000000..953ae4cc3 Binary files /dev/null and b/public/favicon-32x32.png differ diff --git a/public/favicon.ico b/public/favicon.ico index 38fd8641c..519b939a0 100644 Binary files a/public/favicon.ico and b/public/favicon.ico differ diff --git a/public/favicon_old.ico b/public/favicon_old.ico new file mode 100644 index 000000000..20b59d440 Binary files /dev/null and b/public/favicon_old.ico differ diff --git a/public/images/brand/logo_dark.svg b/public/images/brand/logo_dark.svg new file mode 100644 index 000000000..265777fa3 --- /dev/null +++ b/public/images/brand/logo_dark.svg @@ -0,0 +1,12 @@ + + + React-Logo-Filled (1) + + + + + + + + + \ No newline at end of file diff --git a/public/images/brand/logo_light.svg b/public/images/brand/logo_light.svg new file mode 100644 index 000000000..bbe5a8994 --- /dev/null +++ b/public/images/brand/logo_light.svg @@ -0,0 +1,10 @@ + + + React-Logo-Filled (1) + + + + + + + \ No newline at end of file diff --git a/public/images/brand/wordmark_dark.svg b/public/images/brand/wordmark_dark.svg new file mode 100644 index 000000000..ec028ae21 --- /dev/null +++ b/public/images/brand/wordmark_dark.svg @@ -0,0 +1,15 @@ + + + Group + + + + + + + + + + + + \ No newline at end of file diff --git a/public/images/brand/wordmark_light.svg b/public/images/brand/wordmark_light.svg new file mode 100644 index 000000000..2de8f3cc7 --- /dev/null +++ b/public/images/brand/wordmark_light.svg @@ -0,0 +1,11 @@ + + + Group + + + + + + + + \ No newline at end of file diff --git a/public/images/team/jack-pope.jpg b/public/images/team/jack-pope.jpg new file mode 100644 index 000000000..601e5840e Binary files /dev/null and b/public/images/team/jack-pope.jpg differ diff --git a/public/images/team/lauren.jpg b/public/images/team/lauren.jpg index 1485cf8ff..cb08b9725 100644 Binary files a/public/images/team/lauren.jpg and b/public/images/team/lauren.jpg differ diff --git a/public/images/team/lesiutin.jpg b/public/images/team/lesiutin.jpg new file mode 100644 index 000000000..edfc942e0 Binary files /dev/null and b/public/images/team/lesiutin.jpg differ diff --git a/public/images/uwu.png b/public/images/uwu.png new file mode 100644 index 000000000..a09d245ea Binary files /dev/null and b/public/images/uwu.png differ diff --git a/public/mstile-150x150.png b/public/mstile-150x150.png new file mode 100644 index 000000000..d36e7ee9e Binary files /dev/null and b/public/mstile-150x150.png differ diff --git a/public/safari-pinned-tab.svg b/public/safari-pinned-tab.svg new file mode 100644 index 000000000..7e4874b2f --- /dev/null +++ b/public/safari-pinned-tab.svg @@ -0,0 +1,60 @@ + + + + +Created by potrace 1.14, written by Peter Selinger 2001-2017 + + + + + + diff --git a/public/site.webmanifest b/public/site.webmanifest new file mode 100644 index 000000000..337446d52 --- /dev/null +++ b/public/site.webmanifest @@ -0,0 +1,19 @@ +{ + "name": "React", + "short_name": "React", + "icons": [ + { + "src": "/android-chrome-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "/android-chrome-384x384.png", + "sizes": "384x384", + "type": "image/png" + } + ], + "theme_color": "#23272f", + "background_color": "#23272f", + "display": "standalone" +} diff --git a/scripts/generateRss.js b/scripts/generateRss.js new file mode 100644 index 000000000..e0f3d5561 --- /dev/null +++ b/scripts/generateRss.js @@ -0,0 +1,6 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ +const {generateRssFeed} = require('../src/utils/rss'); + +generateRssFeed(); diff --git a/src/components/ButtonLink.tsx b/src/components/ButtonLink.tsx index 15ab83f2b..23c971756 100644 --- a/src/components/ButtonLink.tsx +++ b/src/components/ButtonLink.tsx @@ -26,7 +26,8 @@ function ButtonLink({ className, 'active:scale-[.98] transition-transform inline-flex font-bold items-center outline-none focus:outline-none focus-visible:outline focus-visible:outline-link focus:outline-offset-2 focus-visible:dark:focus:outline-link-dark leading-snug', { - 'bg-link text-white hover:bg-opacity-80': type === 'primary', + 'bg-link text-white dark:bg-brand-dark dark:text-secondary hover:bg-opacity-80': + type === 'primary', 'text-primary dark:text-primary-dark shadow-secondary-button-stroke dark:shadow-secondary-button-stroke-dark hover:bg-gray-40/5 active:bg-gray-40/10 hover:dark:bg-gray-60/5 active:dark:bg-gray-60/10': type === 'secondary', 'text-lg py-3 rounded-full px-4 sm:px-6': size === 'lg', diff --git a/src/components/Icon/IconCanary.tsx b/src/components/Icon/IconCanary.tsx index a7782b141..7f584fed7 100644 --- a/src/components/Icon/IconCanary.tsx +++ b/src/components/Icon/IconCanary.tsx @@ -4,29 +4,35 @@ import {memo} from 'react'; -export const IconCanary = memo( - function IconCanary({className, title}) { - return ( - - {title && {title}} - - - - - - - ); +export const IconCanary = memo< + JSX.IntrinsicElements['svg'] & {title?: string; size?: 's' | 'md'} +>(function IconCanary( + {className, title, size} = { + className: undefined, + title: undefined, + size: 'md', } -); +) { + return ( + + {title && {title}} + + + + + + + ); +}); diff --git a/src/components/Layout/Footer.tsx b/src/components/Layout/Footer.tsx index 26bdf6711..5bcf9df98 100644 --- a/src/components/Layout/Footer.tsx +++ b/src/components/Layout/Footer.tsx @@ -285,6 +285,30 @@ export function Footer() { dir="ltr"> ©{new Date().getFullYear()} +
{ + // @ts-ignore + window.__setUwu(false); + }}> + no uwu plz +
+
{ + // @ts-ignore + window.__setUwu(true); + }}> + uwu? +
+
+ Logo by + + @sawaratsuki1004 + +
diff --git a/src/components/Layout/HomeContent.js b/src/components/Layout/HomeContent.js index e1fab6d71..72ab36884 100644 --- a/src/components/Layout/HomeContent.js +++ b/src/components/Layout/HomeContent.js @@ -26,6 +26,8 @@ import Link from 'components/MDX/Link'; import CodeBlock from 'components/MDX/CodeBlock'; import {ExternalLink} from 'components/ExternalLink'; import sidebarBlog from '../../sidebarBlog.json'; +import * as React from 'react'; +import Image from 'next/image'; function Section({children, background = null}) { return ( @@ -115,12 +117,22 @@ export function HomeContent() { <>
+
+ logo by @sawaratsuki1004 +
-

+

React

@@ -489,7 +501,15 @@ export function HomeContent() {

- +
+ logo by @sawaratsuki1004 +
+
Welcome to the
React community @@ -1620,7 +1640,7 @@ function Thumbnail({video}) {
- + React Conf
diff --git a/src/components/Layout/Page.tsx b/src/components/Layout/Page.tsx index ee3c899d0..24d379589 100644 --- a/src/components/Layout/Page.tsx +++ b/src/components/Layout/Page.tsx @@ -8,19 +8,19 @@ import {useRouter} from 'next/router'; import {SidebarNav} from './SidebarNav'; import {Footer} from './Footer'; import {Toc} from './Toc'; -import SocialBanner from '../SocialBanner'; +// import SocialBanner from '../SocialBanner'; import {DocsPageFooter} from 'components/DocsFooter'; import {Seo} from 'components/Seo'; -import ButtonLink from 'components/ButtonLink'; -import {IconNavArrow} from 'components/Icon/IconNavArrow'; import PageHeading from 'components/PageHeading'; import {getRouteMeta} from './getRouteMeta'; import {TocContext} from '../MDX/TocContext'; +import {Languages, LanguagesContext} from '../MDX/LanguagesContext'; import type {TocItem} from 'components/MDX/TocContext'; import type {RouteItem} from 'components/Layout/getRouteMeta'; import {HomeContent} from './HomeContent'; import {TopNav} from './TopNav'; import cn from 'classnames'; +import Head from 'next/head'; import(/* webpackPrefetch: true */ '../MDX/CodeBlock/CodeBlock'); @@ -35,9 +35,17 @@ interface PageProps { description?: string; }; section: 'learn' | 'reference' | 'community' | 'blog' | 'home' | 'unknown'; + languages?: Languages | null; } -export function Page({children, toc, routeTree, meta, section}: PageProps) { +export function Page({ + children, + toc, + routeTree, + meta, + section, + languages = null, +}: PageProps) { const {asPath} = useRouter(); const cleanedPath = asPath.split(/[\?\#]/)[0]; const {route, nextRoute, prevRoute, breadcrumbs, order} = getRouteMeta( @@ -74,7 +82,11 @@ export function Page({children, toc, routeTree, meta, section}: PageProps) { 'max-w-7xl mx-auto', section === 'blog' && 'lg:flex lg:flex-col lg:items-center' )}> - {children} + + + {children} + +
{!isBlogIndex && ( - + {(isHomePage || isBlogIndex) && ( + + + + )} + {/**/} {!isHomePage && (
- { -
- } - {showSurvey && ( - <> -
-

- How do you like these docs? -

-
- - Take our survey! - - -
-
-
- - )} +
)}
+ {index !== 0 && (
  • - {sectionHeader} + {sectionHeaderText} ); diff --git a/src/components/Layout/TopNav/BrandMenu.tsx b/src/components/Layout/TopNav/BrandMenu.tsx new file mode 100644 index 000000000..3bd8776f2 --- /dev/null +++ b/src/components/Layout/TopNav/BrandMenu.tsx @@ -0,0 +1,145 @@ +import * as ContextMenu from '@radix-ui/react-context-menu'; +import {IconCopy} from 'components/Icon/IconCopy'; +import {IconDownload} from 'components/Icon/IconDownload'; +import {IconNewPage} from 'components/Icon/IconNewPage'; +import {ExternalLink} from 'components/ExternalLink'; +import {IconClose} from '../../Icon/IconClose'; + +function MenuItem({ + children, + onSelect, +}: { + children: React.ReactNode; + onSelect?: () => void; +}) { + return ( + + {children} + + ); +} + +function DownloadMenuItem({ + fileName, + href, + children, +}: { + fileName: string; + href: string; + children: React.ReactNode; +}) { + return ( + + {children} + + ); +} + +export default function BrandMenu({children}: {children: React.ReactNode}) { + return ( + + + {children} + + + + + Dark Mode + + + + + + Logo SVG + + + + + + Wordmark SVG + + { + await navigator.clipboard.writeText('#58C4DC'); + }}> + + + + Copy dark mode color + + + Light Mode + + + + + + Logo SVG + + + + + + Wordmark SVG + + { + await navigator.clipboard.writeText('#087EA4'); + }}> + + + + Copy light mode color + +
    + + + uwu + + { + // @ts-ignore + window.__setUwu(false); + }}> + + + + Turn off + + + + + + Logo PNG + + + + + + + + Logo by @sawaratsuki1004 + + +
    +
    +
    +
    + ); +} diff --git a/src/components/Layout/TopNav/TopNav.tsx b/src/components/Layout/TopNav/TopNav.tsx index b6e276ff7..cc5c654e3 100644 --- a/src/components/Layout/TopNav/TopNav.tsx +++ b/src/components/Layout/TopNav/TopNav.tsx @@ -10,6 +10,7 @@ import { startTransition, Suspense, } from 'react'; +import Image from 'next/image'; import * as React from 'react'; import cn from 'classnames'; import NextLink from 'next/link'; @@ -24,6 +25,8 @@ import {Logo} from '../../Logo'; import {Feedback} from '../Feedback'; import {SidebarRouteTree} from '../Sidebar'; import type {RouteItem} from '../getRouteMeta'; +import {siteConfig} from 'siteConfig'; +import BrandMenu from './BrandMenu'; declare global { interface Window { @@ -77,6 +80,19 @@ const lightIcon = ( ); +const languageIcon = ( + + + +); + const githubIcon = ( (null); const {asPath} = useRouter(); - const [isScrolled, setIsScrolled] = useState(false); // HACK. Fix up the data structures instead. if ((routeTree as any).routes.length === 1) { @@ -154,18 +171,18 @@ export default function TopNav({ // While the overlay is open, disable body scroll. useEffect(() => { - if (isOpen) { + if (isMenuOpen) { const preferredScrollParent = scrollParentRef.current!; disableBodyScroll(preferredScrollParent); return () => enableBodyScroll(preferredScrollParent); } else { return undefined; } - }, [isOpen]); + }, [isMenuOpen]); // Close the overlay on any navigation. useEffect(() => { - setIsOpen(false); + setIsMenuOpen(false); }, [asPath]); // Also close the overlay if the window gets resized past mobile layout. @@ -175,7 +192,7 @@ export default function TopNav({ function closeIfNeeded() { if (!media.matches) { - setIsOpen(false); + setIsMenuOpen(false); } } @@ -204,7 +221,6 @@ export default function TopNav({ return () => observer.disconnect(); }, []); - const [showSearch, setShowSearch] = useState(false); const onOpenSearch = useCallback(() => { startTransition(() => { setShowSearch(true); @@ -224,39 +240,63 @@ export default function TopNav({
    - {isOpen && ( + {isMenuOpen && (
  • ); } @@ -304,7 +307,7 @@ export default function PackingList() { function Item({ name, isPacked }) { return (
  • - {name} {isPacked ? '✔' : '❌'} + {name} {isPacked ? '✅' : '❌'}
  • ); } @@ -610,7 +613,7 @@ We hope that this approach will make the API reference useful not only as a way ## What's next? {/*whats-next*/} -That's a wrap for our little tour! Have a look around the new website, see what you like or don't like, and keep the feedback coming in the [anonymous survey](https://www.surveymonkey.co.uk/r/PYRPF3X) or in our [issue tracker](https://github.com/reactjs/reactjs.org/issues). +That's a wrap for our little tour! Have a look around the new website, see what you like or don't like, and keep the feedback coming in our [issue tracker](https://github.com/reactjs/react.dev/issues). We acknowledge this project has taken a long time to ship. We wanted to maintain a high quality bar that the React community deserves. While writing these docs and creating all of the examples, we found mistakes in some of our own explanations, bugs in React, and even gaps in the React design that we are now working to address. We hope that the new documentation will help us hold React itself to a higher bar in the future. diff --git a/src/content/blog/2023/03/22/react-labs-what-we-have-been-working-on-march-2023.md b/src/content/blog/2023/03/22/react-labs-what-we-have-been-working-on-march-2023.md index 1f6b911e1..aeb677f31 100644 --- a/src/content/blog/2023/03/22/react-labs-what-we-have-been-working-on-march-2023.md +++ b/src/content/blog/2023/03/22/react-labs-what-we-have-been-working-on-march-2023.md @@ -1,5 +1,8 @@ --- title: "React Labs: What We've Been Working On – March 2023" +author: Joseph Savona, Josh Story, Lauren Tan, Mengdi Chen, Samuel Susla, Sathya Gunasekaran, Sebastian Markbage, and Andrew Clark +date: 2023/03/22 +description: In React Labs posts, we write about projects in active research and development. We've made significant progress on them since our last update, and we'd like to share what we learned. --- March 22, 2023 by [Joseph Savona](https://twitter.com/en_JS), [Josh Story](https://twitter.com/joshcstory), [Lauren Tan](https://twitter.com/potetotes), [Mengdi Chen](https://twitter.com/mengdi_en), [Samuel Susla](https://twitter.com/SamuelSusla), [Sathya Gunasekaran](https://twitter.com/_gsathya), [Sebastian Markbåge](https://twitter.com/sebmarkbage), and [Andrew Clark](https://twitter.com/acdlite) diff --git a/src/content/blog/2023/05/03/react-canaries.md b/src/content/blog/2023/05/03/react-canaries.md index 81da3fd00..19d9960b0 100644 --- a/src/content/blog/2023/05/03/react-canaries.md +++ b/src/content/blog/2023/05/03/react-canaries.md @@ -1,5 +1,8 @@ --- title: "React Canaries: Enabling Incremental Feature Rollout Outside Meta" +author: Dan Abramov, Sophie Alpert, Rick Hanlon, Sebastian Markbage, and Andrew Clark +date: 2023/05/03 +description: We'd like to offer the React community an option to adopt individual new features as soon as their design is close to final, before they're released in a stable version--similar to how Meta has long used bleeding-edge versions of React internally. We are introducing a new officially supported [Canary release channel](/community/versioning-policy#canary-channel). It lets curated setups like frameworks decouple adoption of individual React features from the React release schedule. --- May 3, 2023 by [Dan Abramov](https://twitter.com/dan_abramov), [Sophie Alpert](https://twitter.com/sophiebits), [Rick Hanlon](https://twitter.com/rickhanlonii), [Sebastian Markbåge](https://twitter.com/sebmarkbage), and [Andrew Clark](https://twitter.com/acdlite) diff --git a/src/content/blog/2024/02/15/react-labs-what-we-have-been-working-on-february-2024.md b/src/content/blog/2024/02/15/react-labs-what-we-have-been-working-on-february-2024.md index 03fc85c37..fee21f4ec 100644 --- a/src/content/blog/2024/02/15/react-labs-what-we-have-been-working-on-february-2024.md +++ b/src/content/blog/2024/02/15/react-labs-what-we-have-been-working-on-february-2024.md @@ -1,5 +1,8 @@ --- title: "React Labs: What We've Been Working On – February 2024" +author: Joseph Savona, Ricky Hanlon, Andrew Clark, Matt Carroll, and Dan Abramov +date: 2024/02/15 +description: In React Labs posts, we write about projects in active research and development. We’ve made significant progress since our last update, and we’d like to share our progress. --- February 15, 2024 by [Joseph Savona](https://twitter.com/en_JS), [Ricky Hanlon](https://twitter.com/rickhanlonii), [Andrew Clark](https://twitter.com/acdlite), [Matt Carroll](https://twitter.com/mattcarrollcode), and [Dan Abramov](https://twitter.com/dan_abramov). @@ -52,7 +55,7 @@ We refer to this broader collection of features as simply "Actions". Actions all ``` -The `action` function can operate synchronously or asynchronously. You can define them on the client side using standard JavaScript or on the server with the [`'use server'`](/reference/react/use-server) directive. When using an action, React will manage the life cycle of the data submission for you, providing hooks like [`useFormStatus`](/reference/react-dom/hooks/useFormStatus), and [`useFormState`](/reference/react-dom/hooks/useFormState) to access the current state and response of the form action. +The `action` function can operate synchronously or asynchronously. You can define them on the client side using standard JavaScript or on the server with the [`'use server'`](/reference/rsc/use-server) directive. When using an action, React will manage the life cycle of the data submission for you, providing hooks like [`useFormStatus`](/reference/react-dom/hooks/useFormStatus), and [`useActionState`](/reference/react/useActionState) to access the current state and response of the form action. By default, Actions are submitted within a [transition](/reference/react/useTransition), keeping the current page interactive while the action is processing. Since Actions support async functions, we've also added the ability to use `async/await` in transitions. This allows you to show pending UI with the `isPending` state of a transition when an async request like `fetch` starts, and show the pending UI all the way through the update being applied. @@ -72,13 +75,13 @@ Canaries are a change to the way we develop React. Previously, features would be React Server Components, Asset Loading, Document Metadata, and Actions have all landed in the React Canary, and we've added docs for these features on react.dev: -- **Directives**: [`"use client"`](/reference/react/use-client) and [`"use server"`](/reference/react/use-server) are bundler features designed for full-stack React frameworks. They mark the "split points" between the two environments: `"use client"` instructs the bundler to generate a ` +``` + +### Libraries depending on React internals may block upgrades {/*libraries-depending-on-react-internals-may-block-upgrades*/} + +This release includes changes to React internals that may impact libraries that ignore our pleas to not use internals like `SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED`. These changes are necessary to land improvements in React 19, and will not break libraries that follow our guidelines. + +Based on our [Versioning Policy](https://react.dev/community/versioning-policy#what-counts-as-a-breaking-change), these updates are not listed as breaking changes, and we are not including docs for how to upgrade them. The recommendation is to remove any code that depends on internals. + +To reflect the impact of using internals, we have renamed the `SECRET_INTERNALS` suffix to: + +`_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE` + +In the future we will more aggressively block accessing internals from React to discourage usage and ensure users are not blocked from upgrading. + +## TypeScript changes {/*typescript-changes*/} + +### Removed deprecated TypeScript types {/*removed-deprecated-typescript-types*/} + +We've cleaned up the TypeScript types based on the removed APIs in React 19. Some of the removed have types been moved to more relevant packages, and others are no longer needed to describe React's behavior. + + +We've published [`types-react-codemod`](https://github.com/eps1lon/types-react-codemod/) to migrate most type related breaking changes: + +```bash +npx types-react-codemod@latest preset-19 ./path-to-app +``` + +If you have a lot of unsound access to `element.props`, you can run this additional codemod: + +```bash +npx types-react-codemod@latest react-element-default-any-props ./path-to-your-react-ts-files +``` + + + +Check out [`types-react-codemod`](https://github.com/eps1lon/types-react-codemod/) for a list of supported replacements. If you feel a codemod is missing, it can be tracked in the [list of missing React 19 codemods](https://github.com/eps1lon/types-react-codemod/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22React+19%22+label%3Aenhancement). + + +### `ref` cleanups required {/*ref-cleanup-required*/} + +_This change is included in the `react-19` codemod preset as [`no-implicit-ref-callback-return +`](https://github.com/eps1lon/types-react-codemod/#no-implicit-ref-callback-return)._ + +Due to the introduction of ref cleanup functions, returning anything else from a ref callback will now be rejected by TypeScript. The fix is usually to stop using implicit returns: + +```diff [[1, 1, "("], [1, 1, ")"], [2, 2, "{", 15], [2, 2, "}", 1]] +-
    (instance = current)} /> ++
    {instance = current}} /> +``` + +The original code returned the instance of the `HTMLDivElement` and TypeScript wouldn't know if this was supposed to be a cleanup function or not. + +### `useRef` requires an argument {/*useref-requires-argument*/} + +_This change is included in the `react-19` codemod preset as [`refobject-defaults`](https://github.com/eps1lon/types-react-codemod/#refobject-defaults)._ + +A long-time complaint of how TypeScript and React work has been `useRef`. We've changed the types so that `useRef` now requires an argument. This significantly simplifies its type signature. It'll now behave more like `createContext`. + +```ts +// @ts-expect-error: Expected 1 argument but saw none +useRef(); +// Passes +useRef(undefined); +// @ts-expect-error: Expected 1 argument but saw none +createContext(); +// Passes +createContext(undefined); +``` + +This now also means that all refs are mutable. You'll no longer hit the issue where you can't mutate a ref because you initialised it with `null`: + +```ts +const ref = useRef(null); + +// Cannot assign to 'current' because it is a read-only property +ref.current = 1; +``` + +`MutableRef` is now deprecated in favor of a single `RefObject` type which `useRef` will always return: + +```ts +interface RefObject { + current: T +} + +declare function useRef: RefObject +``` + +`useRef` still has a convenience overload for `useRef(null)` that automatically returns `RefObject`. To ease migration due to the required argument for `useRef`, a convenience overload for `useRef(undefined)` was added that automatically returns `RefObject`. + +Check out [[RFC] Make all refs mutable](https://github.com/DefinitelyTyped/DefinitelyTyped/pull/64772) for prior discussions about this change. + +### Changes to the `ReactElement` TypeScript type {/*changes-to-the-reactelement-typescript-type*/} + +_This change is included in the [`react-element-default-any-props`](https://github.com/eps1lon/types-react-codemod#react-element-default-any-props) codemod._ + +The `props` of React elements now default to `unknown` instead of `any` if the element is typed as `ReactElement`. This does not affect you if you pass a type argument to `ReactElement`: + +```ts +type Example2 = ReactElement<{ id: string }>["props"]; +// ^? { id: string } +``` + +But if you relied on the default, you now have to handle `unknown`: + +```ts +type Example = ReactElement["props"]; +// ^? Before, was 'any', now 'unknown' +``` + +You should only need it if you have a lot of legacy code relying on unsound access of element props. Element introspection only exists as an escape hatch, and you should make it explicit that your props access is unsound via an explicit `any`. + +### The JSX namespace in TypeScript {/*the-jsx-namespace-in-typescript*/} +This change is included in the `react-19` codemod preset as [`scoped-jsx`](https://github.com/eps1lon/types-react-codemod#scoped-jsx) + +A long-time request is to remove the global `JSX` namespace from our types in favor of `React.JSX`. This helps prevent pollution of global types which prevents conflicts between different UI libraries that leverage JSX. + +You'll now need to wrap module augmentation of the JSX namespace in `declare module "....": + +```diff +// global.d.ts ++ declare module "react" { + namespace JSX { + interface IntrinsicElements { + "my-element": { + myElementProps: string; + }; + } + } ++ } +``` + +The exact module specifier depends on the JSX runtime you specified in the `compilerOptions` of your `tsconfig.json`: + +- For `"jsx": "react-jsx"` it would be `react/jsx-runtime`. +- For `"jsx": "react-jsxdev"` it would be `react/jsx-dev-runtime`. +- For `"jsx": "react"` and `"jsx": "preserve"` it would be `react`. + +### Better `useReducer` typings {/*better-usereducer-typings*/} + +`useReducer` now has improved type inference thanks to [@mfp22](https://github.com/mfp22). + +However, this required a breaking change where `useReducer` doesn't accept the full reducer type as a type parameter but instead either needs none (and rely on contextual typing) or needs both the state and action type. + +The new best practice is _not_ to pass type arguments to `useReducer`. +```diff +- useReducer>(reducer) ++ useReducer(reducer) +``` +This may not work in edge cases where you can explicitly type the state and action, by passing in the `Action` in a tuple: +```diff +- useReducer>(reducer) ++ useReducer(reducer) +``` +If you define the reducer inline, we encourage to annotate the function parameters instead: +```diff +- useReducer>((state, action) => state) ++ useReducer((state: State, action: Action) => state) +``` +This is also what you'd also have to do if you move the reducer outside of the `useReducer` call: + +```ts +const reducer = (state: State, action: Action) => state; +``` + +## Changelog {/*changelog*/} + +### Other breaking changes {/*other-breaking-changes*/} + +- **react-dom**: Error for javascript URLs in src/href [#26507](https://github.com/facebook/react/pull/26507) +- **react-dom**: Remove `errorInfo.digest` from `onRecoverableError` [#28222](https://github.com/facebook/react/pull/28222) +- **react-dom**: Remove `unstable_flushControlled` [#26397](https://github.com/facebook/react/pull/26397) +- **react-dom**: Remove `unstable_createEventHandle` [#28271](https://github.com/facebook/react/pull/28271) +- **react-dom**: Remove `unstable_renderSubtreeIntoContainer` [#28271](https://github.com/facebook/react/pull/28271) +- **react-dom**: Remove `unstable_runWithPrioirty` [#28271](https://github.com/facebook/react/pull/28271) +- **react-is**: Remove deprecated methods from `react-is` [28224](https://github.com/facebook/react/pull/28224) + +### Other notable changes {/*other-notable-changes*/} + +- **react**: Batch sync, default and continuous lanes [#25700](https://github.com/facebook/react/pull/25700) +- **react**: Don't prerender siblings of suspended component [#26380](https://github.com/facebook/react/pull/26380) +- **react**: Detect infinite update loops caused by render phase updates [#26625](https://github.com/facebook/react/pull/26625) +- **react-dom**: Transitions in popstate are now synchronous [#26025](https://github.com/facebook/react/pull/26025) +- **react-dom**: Remove layout effect warning during SSR [#26395](https://github.com/facebook/react/pull/26395) +- **react-dom**: Warn and don’t set empty string for src/href (except anchor tags) [#28124](https://github.com/facebook/react/pull/28124) + +We'll publish the full changelog with the stable release of React 19. + +--- + +Thanks to [Andrew Clark](https://twitter.com/acdlite), [Eli White](https://twitter.com/Eli_White), [Jack Pope](https://github.com/jackpope), [Jan Kassens](https://github.com/kassens), [Josh Story](https://twitter.com/joshcstory), [Matt Carroll](https://twitter.com/mattcarrollcode), [Noah Lemen](https://twitter.com/noahlemen), [Sophie Alpert](https://twitter.com/sophiebits), and [Sebastian Silbermann](https://twitter.com/sebsilbermann) for reviewing and editing this post. diff --git a/src/content/blog/2024/04/25/react-19.md b/src/content/blog/2024/04/25/react-19.md new file mode 100644 index 000000000..1b19c3546 --- /dev/null +++ b/src/content/blog/2024/04/25/react-19.md @@ -0,0 +1,775 @@ +--- +title: "React 19 RC" +author: The React Team +date: 2024/04/25 +description: React 19 RC is now available on npm! In this post, we'll give an overview of the new features in React 19, and how you can adopt them. +--- + +April 25, 2024 by [The React Team](/community/team) + +--- + + + +React 19 RC is now available on npm! + + + +In our [React 19 RC Upgrade Guide](/blog/2024/04/25/react-19-upgrade-guide), we shared step-by-step instructions for upgrading your app to React 19. In this post, we'll give an overview of the new features in React 19, and how you can adopt them. + +- [What's new in React 19](#whats-new-in-react-19) +- [Improvements in React 19](#improvements-in-react-19) +- [How to upgrade](#how-to-upgrade) + +For a list of breaking changes, see the [Upgrade Guide](/blog/2024/04/25/react-19-upgrade-guide). + +--- + +## What's new in React 19 {/*whats-new-in-react-19*/} + +### Actions {/*actions*/} + +A common use case in React apps is to perform a data mutation and then update state in response. For example, when a user submits a form to change their name, you will make an API request, and then handle the response. In the past, you would need to handle pending states, errors, optimistic updates, and sequential requests manually. + +For example, you could handle the pending and error state in `useState`: + +```js +// Before Actions +function UpdateName({}) { + const [name, setName] = useState(""); + const [error, setError] = useState(null); + const [isPending, setIsPending] = useState(false); + + const handleSubmit = async () => { + setIsPending(true); + const error = await updateName(name); + setIsPending(false); + if (error) { + setError(error); + return; + } + redirect("/path"); + }; + + return ( +
    + setName(event.target.value)} /> + + {error &&

    {error}

    } +
    + ); +} +``` + +In React 19, we're adding support for using async functions in transitions to handle pending states, errors, forms, and optimistic updates automatically. + +For example, you can use `useTransition` to handle the pending state for you: + +```js +// Using pending state from Actions +function UpdateName({}) { + const [name, setName] = useState(""); + const [error, setError] = useState(null); + const [isPending, startTransition] = useTransition(); + + const handleSubmit = () => { + startTransition(async () => { + const error = await updateName(name); + if (error) { + setError(error); + return; + } + redirect("/path"); + }) + }; + + return ( +
    + setName(event.target.value)} /> + + {error &&

    {error}

    } +
    + ); +} +``` + +The async transition will immediately set the `isPending` state to true, make the async request(s), and switch `isPending` to false after any transitions. This allows you to keep the current UI responsive and interactive while the data is changing. + + + +#### By convention, functions that use async transitions are called "Actions". {/*by-convention-functions-that-use-async-transitions-are-called-actions*/} + +Actions automatically manage submitting data for you: + +- **Pending state**: Actions provide a pending state that starts at the beginning of a request and automatically resets when the final state update is committed. +- **Optimistic updates**: Actions support the new [`useOptimistic`](#new-hook-optimistic-updates) hook so you can show users instant feedback while the requests are submitting. +- **Error handling**: Actions provide error handling so you can display Error Boundaries when a request fails, and revert optimistic updates to their original value automatically. +- **Forms**: `
    ` elements now support passing functions to the `action` and `formAction` props. Passing functions to the `action` props use Actions by default and reset the form automatically after submission. + + + +Building on top of Actions, React 19 introduces [`useOptimistic`](#new-hook-optimistic-updates) to manage optimistic updates, and a new hook [`React.useActionState`](#new-hook-useactionstate) to handle common cases for Actions. In `react-dom` we're adding [`` Actions](#form-actions) to manage forms automatically and [`useFormStatus`](#new-hook-useformstatus) to support the common cases for Actions in forms. + +In React 19, the above example can be simplified to: + +```js +// Using Actions and useActionState +function ChangeName({ name, setName }) { + const [error, submitAction, isPending] = useActionState( + async (previousState, formData) => { + const error = await updateName(formData.get("name")); + if (error) { + return error; + } + redirect("/path"); + return null; + }, + null, + ); + + return ( + + + + {error &&

    {error}

    } +
    + ); +} +``` + +In the next section, we'll break down each of the new Action features in React 19. + +### New hook: `useActionState` {/*new-hook-useactionstate*/} + +To make the common cases easier for Actions, we've added a new hook called `useActionState`: + +```js +const [error, submitAction, isPending] = useActionState( + async (previousState, newName) => { + const error = await updateName(newName); + if (error) { + // You can return any result of the action. + // Here, we return only the error. + return error; + } + + // handle success + return null; + }, + null, +); +``` + +`useActionState` accepts a function (the "Action"), and returns a wrapped Action to call. This works because Actions compose. When the wrapped Action is called, `useActionState` will return the last result of the Action as `data`, and the pending state of the Action as `pending`. + + + +`React.useActionState` was previously called `ReactDOM.useFormState` in the Canary releases, but we've renamed it and deprecated `useFormState`. + +See [#28491](https://github.com/facebook/react/pull/28491) for more info. + + + +For more information, see the docs for [`useActionState`](/reference/react/useActionState). + +### React DOM: `
    ` Actions {/*form-actions*/} + +Actions are also integrated with React 19's new `` features for `react-dom`. We've added support for passing functions as the `action` and `formAction` props of ``, ``, and `
    }> + + + ) +} +``` + + + +#### `use` does not support promises created in render. {/*use-does-not-support-promises-created-in-render*/} + +If you try to pass a promise created in render to `use`, React will warn: + + + + + +A component was suspended by an uncached promise. Creating promises inside a Client Component or hook is not yet supported, except via a Suspense-compatible library or framework. + + + + + +To fix, you need to pass a promise from a suspense powered library or framework that supports caching for promises. In the future we plan to ship features to make it easier to cache promises in render. + + + +You can also read context with `use`, allowing you to read Context conditionally such as after early returns: + +```js {1,11} +import {use} from 'react'; +import ThemeContext from './ThemeContext' + +function Heading({children}) { + if (children == null) { + return null; + } + + // This would not work with useContext + // because of the early return. + const theme = use(ThemeContext); + return ( +

    + {children} +

    + ); +} +``` + +The `use` API can only be called in render, similar to hooks. Unlike hooks, `use` can be called conditionally. In the future we plan to support more ways to consume resources in render with `use`. + +For more information, see the docs for [`use`](/reference/react/use). + + +## React Server Components {/*react-server-components*/} + +### Server Components {/*server-components*/} + +Server Components are a new option that allows rendering components ahead of time, before bundling, in an environment separate from your client application or SSR server. This separate environment is the "server" in React Server Components. Server Components can run once at build time on your CI server, or they can be run for each request using a web server. + +React 19 includes all of the React Server Components features included from the Canary channel. This means libraries that ship with Server Components can now target React 19 as a peer dependency with a `react-server` [export condition](https://github.com/reactjs/rfcs/blob/main/text/0227-server-module-conventions.md#react-server-conditional-exports) for use in frameworks that support the [Full-stack React Architecture](/learn/start-a-new-react-project#which-features-make-up-the-react-teams-full-stack-architecture-vision). + + + + +#### How do I build support for Server Components? {/*how-do-i-build-support-for-server-components*/} + +While React Server Components in React 19 are stable and will not break between major versions, the underlying APIs used to implement a React Server Components bundler or framework do not follow semver and may break between minors in React 19.x. + +To support React Server Components as a bundler or framework, we recommend pinning to a specific React version, or using the Canary release. We will continue working with bundlers and frameworks to stabilize the APIs used to implement React Server Components in the future. + + + + +For more, see the docs for [React Server Components](/reference/rsc/server-components). + +### Server Actions {/*server-actions*/} + +Server Actions allow Client Components to call async functions executed on the server. + +When a Server Action is defined with the `"use server"` directive, your framework will automatically create a reference to the server function, and pass that reference to the Client Component. When that function is called on the client, React will send a request to the server to execute the function, and return the result. + + + +#### There is no directive for Server Components. {/*there-is-no-directive-for-server-components*/} + +A common misunderstanding is that Server Components are denoted by `"use server"`, but there is no directive for Server Components. The `"use server"` directive is used for Server Actions. + +For more info, see the docs for [Directives](/reference/rsc/directives). + + + +Server Actions can be created in Server Components and passed as props to Client Components, or they can be imported and used in Client Components. + +For more, see the docs for [React Server Actions](/reference/rsc/server-actions). + +## Improvements in React 19 {/*improvements-in-react-19*/} + +### `ref` as a prop {/*ref-as-a-prop*/} + +Starting in React 19, you can now access `ref` as a prop for function components: + +```js [[1, 1, "ref"], [1, 2, "ref", 45], [1, 6, "ref", 14]] +function MyInput({placeholder, ref}) { + return +} + +//... + +``` + +New function components will no longer need `forwardRef`, and we will be publishing a codemod to automatically update your components to use the new `ref` prop. In future versions we will deprecate and remove `forwardRef`. + + + +`refs` passed to classes are not passed as props since they reference the component instance. + + + +### Diffs for hydration errors {/*diffs-for-hydration-errors*/} + +We also improved error reporting for hydration errors in `react-dom`. For example, instead of logging multiple errors in DEV without any information about the mismatch: + + + + + +Warning: Text content did not match. Server: "Server" Client: "Client" +{' '}at span +{' '}at App + + + + + +Warning: An error occurred during hydration. The server HTML was replaced with client content in \. + + + + + +Warning: Text content did not match. Server: "Server" Client: "Client" +{' '}at span +{' '}at App + + + + + +Warning: An error occurred during hydration. The server HTML was replaced with client content in \. + + + + + +Uncaught Error: Text content does not match server-rendered HTML. +{' '}at checkForUnmatchedText +{' '}... + + + + + +We now log a single message with a diff of the mismatch: + + + + + + +Uncaught Error: Hydration failed because the server rendered HTML didn't match the client. As a result this tree will be regenerated on the client. This can happen if an SSR-ed Client Component used:{'\n'} +\- A server/client branch `if (typeof window !== 'undefined')`. +\- Variable input such as `Date.now()` or `Math.random()` which changes each time it's called. +\- Date formatting in a user's locale which doesn't match the server. +\- External changing data without sending a snapshot of it along with the HTML. +\- Invalid HTML tag nesting.{'\n'} +It can also happen if the client has a browser extension installed which messes with the HTML before React loaded.{'\n'} +https://react.dev/link/hydration-mismatch {'\n'} +{' '}\ +{' '}\ +{'+ '}Client +{'- '}Server{'\n'} +{' '}at throwOnHydrationMismatch +{' '}... + + + + + +### `` as a provider {/*context-as-a-provider*/} + +In React 19, you can render `` as a provider instead of ``: + + +```js {5,7} +const ThemeContext = createContext(''); + +function App({children}) { + return ( + + {children} + + ); +} +``` + +New Context providers can use `` and we will be publishing a codemod to convert existing providers. In future versions we will deprecate ``. + +### Cleanup functions for refs {/*cleanup-functions-for-refs*/} + +We now support returning a cleanup function from `ref` callbacks: + +```js {7-9} + { + // ref created + + // NEW: return a cleanup function to reset + // the ref when element is removed from DOM. + return () => { + // ref cleanup + }; + }} +/> +``` + +When the component unmounts, React will call the cleanup function returned from the `ref` callback. This works for DOM refs, refs to class components, and `useImperativeHandle`. + + + +Previously, React would call `ref` functions with `null` when unmounting the component. If your `ref` returns a cleanup function, React will now skip this step. + +In future versions, we will deprecate calling refs with `null` when unmounting components. + + + +Due to the introduction of ref cleanup functions, returning anything else from a `ref` callback will now be rejected by TypeScript. The fix is usually to stop using implicit returns, for example: + +```diff [[1, 1, "("], [1, 1, ")"], [2, 2, "{", 15], [2, 2, "}", 1]] +-
    (instance = current)} /> ++
    {instance = current}} /> +``` + +The original code returned the instance of the `HTMLDivElement` and TypeScript wouldn't know if this was _supposed_ to be a cleanup function or if you didn't want to return a cleanup function. + +You can codemod this pattern with [`no-implicit-ref-callback-return`](https://github.com/eps1lon/types-react-codemod/#no-implicit-ref-callback-return). + +### `useDeferredValue` initial value {/*use-deferred-value-initial-value*/} + +We've added an `initialValue` option to `useDeferredValue`: + +```js [[1, 1, "deferredValue"], [1, 4, "deferredValue"], [2, 4, "''"]] +function Search({deferredValue}) { + // On initial render the value is ''. + // Then a re-render is scheduled with the deferredValue. + const value = useDeferredValue(deferredValue, ''); + + return ( + + ); +} +```` + +When initialValue is provided, `useDeferredValue` will return it as `value` for the initial render of the component, and schedules a re-render in the background with the deferredValue returned. + +For more, see [`useDeferredValue`](/reference/react/useDeferredValue). + +### Support for Document Metadata {/*support-for-metadata-tags*/} + +In HTML, document metadata tags like ``, `<link>`, and `<meta>` are reserved for placement in the `<head>` section of the document. In React, the component that decides what metadata is appropriate for the app may be very far from the place where you render the `<head>` or React does not render the `<head>` at all. In the past, these elements would need to be inserted manually in an effect, or by libraries like [`react-helmet`](https://github.com/nfl/react-helmet), and required careful handling when server rendering a React application. + +In React 19, we're adding support for rendering document metadata tags in components natively: + +```js {5-8} +function BlogPost({post}) { + return ( + <article> + <h1>{post.title}</h1> + <title>{post.title} + + + +

    + Eee equals em-see-squared... +

    + + ); +} +``` + +When React renders this component, it will see the `` `<link>` and `<meta>` tags, and automatically hoist them to the `<head>` section of document. By supporting these metadata tags natively, we're able to ensure they work with client-only apps, streaming SSR, and Server Components. + +<Note> + +#### You may still want a Metadata library {/*you-may-still-want-a-metadata-library*/} + +For simple use cases, rendering Document Metadata as tags may be suitable, but libraries can offer more powerful features like overriding generic metadata with specific metadata based on the current route. These features make it easier for frameworks and libraries like [`react-helmet`](https://github.com/nfl/react-helmet) to support metadata tags, rather than replace them. + +</Note> + +For more info, see the docs for [`<title>`](/reference/react-dom/components/title), [`<link>`](/reference/react-dom/components/link), and [`<meta>`](/reference/react-dom/components/meta). + +### Support for stylesheets {/*support-for-stylesheets*/} + +Stylesheets, both externally linked (`<link rel="stylesheet" href="...">`) and inline (`<style>...</style>`), require careful positioning in the DOM due to style precedence rules. Building a stylesheet capability that allows for composability within components is hard, so users often end up either loading all of their styles far from the components that may depend on them, or they use a style library which encapsulates this complexity. + +In React 19, we're addressing this complexity and providing even deeper integration into Concurrent Rendering on the Client and Streaming Rendering on the Server with built in support for stylesheets. If you tell React the `precedence` of your stylesheet it will manage the insertion order of the stylesheet in the DOM and ensure that the stylesheet (if external) is loaded before revealing content that depends on those style rules. + +```js {4,5,17} +function ComponentOne() { + return ( + <Suspense fallback="loading..."> + <link rel="stylesheet" href="foo" precedence="default" /> + <link rel="stylesheet" href="bar" precedence="high" /> + <article class="foo-class bar-class"> + {...} + </article> + </Suspense> + ) +} + +function ComponentTwo() { + return ( + <div> + <p>{...}</p> + <link rel="stylesheet" href="baz" precedence="default" /> <-- will be inserted between foo & bar + </div> + ) +} +``` + +During Server Side Rendering React will include the stylesheet in the `<head>`, which ensures that the browser will not paint until it has loaded. If the stylesheet is discovered late after we've already started streaming, React will ensure that the stylesheet is inserted into the `<head>` on the client before revealing the content of a Suspense boundary that depends on that stylesheet. + +During Client Side Rendering React will wait for newly rendered stylesheets to load before committing the render. If you render this component from multiple places within your application React will only include the stylesheet once in the document: + +```js {5} +function App() { + return <> + <ComponentOne /> + ... + <ComponentOne /> // won't lead to a duplicate stylesheet link in the DOM + </> +} +``` + +For users accustomed to loading stylesheets manually this is an opportunity to locate those stylesheets alongside the components that depend on them allowing for better local reasoning and an easier time ensuring you only load the stylesheets that you actually depend on. + +Style libraries and style integrations with bundlers can also adopt this new capability so even if you don't directly render your own stylesheets, you can still benefit as your tools are upgraded to use this feature. + +For more details, read the docs for [`<link>`](/reference/react-dom/components/link) and [`<style>`](/reference/react-dom/components/style). + +### Support for async scripts {/*support-for-async-scripts*/} + +In HTML normal scripts (`<script src="...">`) and deferred scripts (`<script defer="" src="...">`) load in document order which makes rendering these kinds of scripts deep within your component tree challenging. Async scripts (`<script async="" src="...">`) however will load in arbitrary order. + +In React 19 we've included better support for async scripts by allowing you to render them anywhere in your component tree, inside the components that actually depend on the script, without having to manage relocating and deduplicating script instances. + +```js {4,15} +function MyComponent() { + return ( + <div> + <script async={true} src="..." /> + Hello World + </div> + ) +} + +function App() { + <html> + <body> + <MyComponent> + ... + <MyComponent> // won't lead to duplicate script in the DOM + </body> + </html> +} +``` + +In all rendering environments, async scripts will be deduplicated so that React will only load and execute the script once even if it is rendered by multiple different components. + +In Server Side Rendering, async scripts will be included in the `<head>` and prioritized behind more critical resources that block paint such as stylesheets, fonts, and image preloads. + +For more details, read the docs for [`<script>`](/reference/react-dom/components/script). + +### Support for preloading resources {/*support-for-preloading-resources*/} + +During initial document load and on client side updates, telling the Browser about resources that it will likely need to load as early as possible can have a dramatic effect on page performance. + +React 19 includes a number of new APIs for loading and preloading Browser resources to make it as easy as possible to build great experiences that aren't held back by inefficient resource loading. + +```js +import { prefetchDNS, preconnect, preload, preinit } from 'react-dom' +function MyComponent() { + preinit('https://.../path/to/some/script.js', {as: 'script' }) // loads and executes this script eagerly + preload('https://.../path/to/font.woff', { as: 'font' }) // preloads this font + preload('https://.../path/to/stylesheet.css', { as: 'style' }) // preloads this stylesheet + prefetchDNS('https://...') // when you may not actually request anything from this host + preconnect('https://...') // when you will request something but aren't sure what +} +``` +```html +<!-- the above would result in the following DOM/HTML --> +<html> + <head> + <!-- links/scripts are prioritized by their utility to early loading, not call order --> + <link rel="prefetch-dns" href="https://..."> + <link rel="preconnect" href="https://..."> + <link rel="preload" as="font" href="https://.../path/to/font.woff"> + <link rel="preload" as="style" href="https://.../path/to/stylesheet.css"> + <script async="" src="https://.../path/to/some/script.js"></script> + </head> + <body> + ... + </body> +</html> +``` + +These APIs can be used to optimize initial page loads by moving discovery of additional resources like fonts out of stylesheet loading. They can also make client updates faster by prefetching a list of resources used by an anticipated navigation and then eagerly preloading those resources on click or even on hover. + +For more details see [Resource Preloading APIs](/reference/react-dom#resource-preloading-apis). + +### Compatibility with third-party scripts and extensions {/*compatibility-with-third-party-scripts-and-extensions*/} + +We've improved hydration to account for third-party scripts and browser extensions. + +When hydrating, if an element that renders on the client doesn't match the element found in the HTML from the server, React will force a client re-render to fix up the content. Previously, if an element was inserted by third-party scripts or browser extensions, it would trigger a mismatch error and client render. + +In React 19, unexpected tags in the `<head>` and `<body>` will be skipped over, avoiding the mismatch errors. If React needs to re-render the entire document due to an unrelated hydration mismatch, it will leave in place stylesheets inserted by third-party scripts and browser extensions. + +### Better error reporting {/*error-handling*/} + +We improved error handling in React 19 to remove duplication and provide options for handling caught and uncaught errors. For example, when there's an error in render caught by an Error Boundary, previously React would throw the error twice (once for the original error, then again after failing to automatically recover), and then call `console.error` with info about where the error occurred. + +This resulted in three errors for every caught error: + +<ConsoleBlockMulti> + +<ConsoleLogLine level="error"> + +Uncaught Error: hit +{' '}at Throws +{' '}at renderWithHooks +{' '}... + +</ConsoleLogLine> + +<ConsoleLogLine level="error"> + +Uncaught Error: hit<span className="ms-2 text-gray-30">{' <--'} Duplicate</span> +{' '}at Throws +{' '}at renderWithHooks +{' '}... + +</ConsoleLogLine> + +<ConsoleLogLine level="error"> + +The above error occurred in the Throws component: +{' '}at Throws +{' '}at ErrorBoundary +{' '}at App{'\n'} +React will try to recreate this component tree from scratch using the error boundary you provided, ErrorBoundary. + +</ConsoleLogLine> + +</ConsoleBlockMulti> + +In React 19, we log a single error with all the error information included: + +<ConsoleBlockMulti> + +<ConsoleLogLine level="error"> + +Error: hit +{' '}at Throws +{' '}at renderWithHooks +{' '}...{'\n'} +The above error occurred in the Throws component: +{' '}at Throws +{' '}at ErrorBoundary +{' '}at App{'\n'} +React will try to recreate this component tree from scratch using the error boundary you provided, ErrorBoundary. +{' '}at ErrorBoundary +{' '}at App + +</ConsoleLogLine> + +</ConsoleBlockMulti> + +Additionally, we've added two new root options to complement `onRecoverableError`: + +- `onCaughtError`: called when React catches an error in an Error Boundary. +- `onUncaughtError`: called when an error is thrown and not caught by an Error Boundary. +- `onRecoverableError`: called when an error is thrown and automatically recovered. + +For more info and examples, see the docs for [`createRoot`](/reference/react-dom/client/createRoot) and [`hydrateRoot`](/reference/react-dom/client/hydrateRoot). + +### Support for Custom Elements {/*support-for-custom-elements*/} + +React 19 adds full support for custom elements and passes all tests on [Custom Elements Everywhere](https://custom-elements-everywhere.com/). + +In past versions, using Custom Elements in React has been difficult because React treated unrecognized props as attributes rather than properties. In React 19, we've added support for properties that works on the client and during SSR with the following strategy: + +- **Server Side Rendering**: props passed to a custom element will render as attributes if their type is a primitive value like `string`, `number`, or the value is `true`. Props with non-primitive types like `object`, `symbol`, `function`, or value `false` will be omitted. +- **Client Side Rendering**: props that match a property on the Custom Element instance will be assigned as properties, otherwise they will be assigned as attributes. + +Thanks to [Joey Arhar](https://github.com/josepharhar) for driving the design and implementation of Custom Element support in React. + + +#### How to upgrade {/*how-to-upgrade*/} +See the [React 19 Upgrade Guide](/blog/2024/04/25/react-19-upgrade-guide) for step-by-step instructions and a full list of breaking and notable changes. + + + diff --git a/src/content/blog/2024/05/22/react-conf-2024-recap.md b/src/content/blog/2024/05/22/react-conf-2024-recap.md new file mode 100644 index 000000000..96417fd8b --- /dev/null +++ b/src/content/blog/2024/05/22/react-conf-2024-recap.md @@ -0,0 +1,124 @@ +--- +title: "React Conf 2024 Recap" +author: Ricky Hanlon +date: 2024/05/22 +description: Last week we hosted React Conf 2024, a two-day conference in Henderson, Nevada where 700+ attendees gathered in-person to discuss the latest in UI engineering. In this post, we'll summarize the talks and announcements from the event. +--- + +May 22, 2024 by [Ricky Hanlon](https://twitter.com/rickhanlonii). + +--- + +<Intro> + +Last week we hosted React Conf 2024, a two-day conference in Henderson, Nevada where 700+ attendees gathered in-person to discuss the latest in UI engineering. This was our first in-person conference since 2019, and we were thrilled to be able to bring the community together again. + +</Intro> + +--- + +At React Conf 2024, we announced the [React 19 RC](/blog/2024/04/25/react-19), the [React Native New Architecture Beta](https://github.com/reactwg/react-native-new-architecture/discussions/189), and an experimental release of the [React Compiler](/learn/react-compiler). The community also took the stage to announce [React Router v7](https://remix.run/blog/merging-remix-and-react-router), [Universal Server Components](https://www.youtube.com/watch?v=T8TZQ6k4SLE&t=20765s) in Expo Router, React Server Components in [RedwoodJS](https://redwoodjs.com/blog/rsc-now-in-redwoodjs), and much more. + +The entire [day 1](https://www.youtube.com/watch?v=T8TZQ6k4SLE) and [day 2](https://www.youtube.com/watch?v=0ckOUBiuxVY) streams are available online. In this post, we'll summarize the talks and announcements from the event. + +## Day 1 {/*day-1*/} + +_[Watch the full day 1 stream here.](https://www.youtube.com/watch?v=T8TZQ6k4SLE&t=973s)_ + +To kick off day 1, Meta CTO [Andrew "Boz" Bosworth](https://www.threads.net/@boztank) shared a welcome message followed by an introduction by [Seth Webster](https://twitter.com/sethwebster), who manages the React Org at Meta, and our MC [Ashley Narcisse](https://twitter.com/_darkfadr). + +In the day 1 keynote, [Joe Savona](https://twitter.com/en_JS) shared our goals and vision for React to make it easy for anyone to build great user experiences. [Lauren Tan](https://twitter.com/potetotes) followed with a State of React, where she shared that React was downloaded over 1 billion times in 2023, and that 37% of new developers learn to program with React. Finally, she highlighted the work of the React community to make React, React. + +For more, check out these talks from the community later in the conference: + +- [Vanilla React](https://www.youtube.com/watch?v=T8TZQ6k4SLE&t=5542s) by [Ryan Florence](https://twitter.com/ryanflorence) +- [React Rhythm & Blues](https://www.youtube.com/watch?v=0ckOUBiuxVY&t=12728s) by [Lee Robinson](https://twitter.com/leeerob) +- [RedwoodJS, now with React Server Components](https://www.youtube.com/watch?v=T8TZQ6k4SLE&t=26815s) by [Amy Dutton](https://twitter.com/selfteachme) +- [Introducing Universal React Server Components in Expo Router](https://www.youtube.com/watch?v=T8TZQ6k4SLE&t=20765s) by [Evan Bacon](https://twitter.com/Baconbrix) + +Next in the keynote, [Josh Story](https://twitter.com/joshcstory) and [Andrew Clark](https://twitter.com/acdlite) shared new features coming in React 19, and announced the React 19 RC which is ready for testing in production. Check out all the features in the [React 19 release post](/blog/2024/04/25/react-19), and see these talks for deep dives on the new features: + +- [What's new in React 19](https://www.youtube.com/watch?v=T8TZQ6k4SLE&t=8880s) by [Lydia Hallie](https://twitter.com/lydiahallie) +- [React Unpacked: A Roadmap to React 19](https://www.youtube.com/watch?v=T8TZQ6k4SLE&t=10112s) by [Sam Selikoff](https://twitter.com/samselikoff) +- [React 19 Deep Dive: Coordinating HTML](https://www.youtube.com/watch?v=T8TZQ6k4SLE&t=24916s) by [Josh Story](https://twitter.com/joshcstory) +- [Enhancing Forms with React Server Components](https://www.youtube.com/watch?v=0ckOUBiuxVY&t=25280s) by [Aurora Walberg Scharff](https://twitter.com/aurorascharff) +- [React for Two Computers](https://www.youtube.com/watch?v=T8TZQ6k4SLE&t=18825s) by [Dan Abramov](https://twitter.com/dan_abramov2) +- [And Now You Understand React Server Components](https://www.youtube.com/watch?v=0ckOUBiuxVY&t=11256s) by [Kent C. Dodds](https://twitter.com/kentcdodds) + +Finally, we ended the keynote with [Joe Savona](https://twitter.com/en_JS), [Sathya Gunasekaran](https://twitter.com/_gsathya), and [Mofei Zhang](https://twitter.com/zmofei) announcing that the React Compiler is now [Open Source](https://github.com/facebook/react/pull/29061), and sharing an experimental version of the React Compiler to try out. + +For more information on using the Compiler and how it works, check out [the docs](/learn/react-compiler) and these talks: + +- [Forget About Memo](https://www.youtube.com/watch?v=T8TZQ6k4SLE&t=12020s) by [Lauren Tan](https://twitter.com/potetotes) +- [React Compiler Deep Dive](https://www.youtube.com/watch?v=0ckOUBiuxVY&t=9313s) by [Sathya Gunasekaran](https://twitter.com/_gsathya) and [Mofei Zhang](https://twitter.com/zmofei) + +Watch the full day 1 keynote here: + +<YouTubeIframe src="https://www.youtube.com/embed/T8TZQ6k4SLE?t=973s" /> + +## Day 2 {/*day-2*/} + +_[Watch the full day 2 stream here.](https://www.youtube.com/watch?v=0ckOUBiuxVY&t=1720s)_ + +To kick off day 2, [Seth Webster](https://twitter.com/sethwebster) shared a welcome message, followed by a Thank You from [Eli White](https://x.com/Eli_White) and an introduction by our Chief Vibes Officer [Ashley Narcisse](https://twitter.com/_darkfadr). + +In the day 2 keynote, [Nicola Corti](https://twitter.com/cortinico) shared the State of React Native, including 78 million downloads in 2023. He also highlighted apps using React Native including 2000+ screens used inside of Meta; the product details page in Facebook Marketplace, which is visited more than 2 billion times per day; and part of the Microsoft Windows Start Menu and some features in almost every Microsoft Office product across mobile and desktop. + +Nicola also highlighted all the work the community does to support React Native including libraries, frameworks, and multiple platforms. For more, check out these talks from the community: + +- [Extending React Native beyond Mobile and Desktop Apps](https://www.youtube.com/watch?v=0ckOUBiuxVY&t=5798s) by [Chris Traganos](https://twitter.com/chris_trag) and [Anisha Malde](https://twitter.com/anisha_malde) +- [Spatial computing with React](https://www.youtube.com/watch?v=0ckOUBiuxVY&t=22525s) by [Michał Pierzchała](https://twitter.com/thymikee) + +[Riccardo Cipolleschi](https://twitter.com/cipolleschir) continued the day 2 keynote by announcing that the React Native New Architecture is now in Beta and ready for apps to adopt in production. He shared new features and improvements in the new architecture, and shared the roadmap for the future of React Native. For more check out: + +- [Cross Platform React](https://www.youtube.com/watch?v=0ckOUBiuxVY&t=26569s) by [Olga Zinoveva](https://github.com/SlyCaptainFlint) and [Naman Goel](https://twitter.com/naman34) + +Next in the keynote, Nicola announced that we are now recommending starting with a framework like Expo for all new apps created with React Native. With the change, he also announced a new React Native homepage and new Getting Started docs. You can view the new Getting Started guide in the [React Native docs](https://reactnative.dev/docs/next/environment-setup). + +Finally, to end the keynote, [Kadi Kraman](https://twitter.com/kadikraman) shared the latest features and improvements in Expo, and how to get started developing with React Native using Expo. + +Watch the full day 2 keynote here: + +<YouTubeIframe src="https://www.youtube.com/embed/0ckOUBiuxVY?t=1720s" /> + +## Q&A {/*q-and-a*/} + +The React and React Native teams also ended each day with a Q&A session: + +- [React Q&A](https://www.youtube.com/watch?v=T8TZQ6k4SLE&t=27518s) hosted by [Michael Chan](https://twitter.com/chantastic) +- [React Native Q&A](https://www.youtube.com/watch?v=0ckOUBiuxVY&t=27935s) hosted by [Jamon Holmgren](https://twitter.com/jamonholmgren) + +## And more... {/*and-more*/} + +We also heard talks on accessibility, error reporting, css, and more: + +- [Demystifying accessibility in React apps](https://www.youtube.com/watch?v=0ckOUBiuxVY&t=20655s) by [Kateryna Porshnieva](https://twitter.com/krambertech) +- [Pigment CSS, CSS in the server component age](https://www.youtube.com/watch?v=0ckOUBiuxVY&t=21696s) by [Olivier Tassinari](https://twitter.com/olivtassinari) +- [Real-time React Server Components](https://www.youtube.com/watch?v=T8TZQ6k4SLE&t=24070s) by [Sunil Pai](https://twitter.com/threepointone) +- [Let's break React Rules](https://www.youtube.com/watch?v=T8TZQ6k4SLE&t=25862s) by [Charlotte Isambert](https://twitter.com/c_isambert) +- [Solve 100% of your errors](https://www.youtube.com/watch?v=0ckOUBiuxVY&t=19881s) by [Ryan Albrecht](https://github.com/ryan953) + +## Thank you {/*thank-you*/} + +Thank you to all the staff, speakers, and participants who made React Conf 2024 possible. There are too many to list, but we want to thank a few in particular. + +Thank you to [Barbara Markiewicz](https://twitter.com/barbara_markie), the team at [Callstack](https://www.callstack.com/), and our React Team Developer Advocate [Matt Carroll](https://twitter.com/mattcarrollcode) for helping to plan the entire event; and to [Sunny Leggett](https://zeroslopeevents.com/about) and everyone from [Zero Slope](https://zeroslopeevents.com) for helping to organize the event. + +Thank you [Ashley Narcisse](https://twitter.com/_darkfadr) for being our MC and Chief Vibes Officer; and to [Michael Chan](https://twitter.com/chantastic) and [Jamon Holmgren](https://twitter.com/jamonholmgren) for hosting the Q&A sessions. + +Thank you [Seth Webster](https://twitter.com/sethwebster) and [Eli White](https://x.com/Eli_White) for welcoming us each day and providing direction on structure and content; and to [Tom Occhino](https://twitter.com/tomocchino) for joining us with a special message during the after-party. + +Thank you [Ricky Hanlon](https://www.youtube.com/watch?v=FxTZL2U-uKg&t=1263s) for providing detailed feedback on talks, working on slide designs, and generally filling in the gaps to sweat the details. + +Thank you [Callstack](https://www.callstack.com/) for building the conference website; and to [Kadi Kraman](https://twitter.com/kadikraman) and the [Expo](https://expo.dev/) team for building the conference mobile app. + +Thank you to all the sponsors who made the event possible: [Remix](https://remix.run/), [Amazon](https://developer.amazon.com/apps-and-games?cmp=US_2024_05_3P_React-Conf-2024&ch=prtnr&chlast=prtnr&pub=ref&publast=ref&type=org&typelast=org), [MUI](https://mui.com/), [Sentry](https://sentry.io/for/react/?utm_source=sponsored-conf&utm_medium=sponsored-event&utm_campaign=frontend-fy25q2-evergreen&utm_content=logo-reactconf2024-learnmore), [Abbott](https://www.jobs.abbott/software), [Expo](https://expo.dev/), [RedwoodJS](https://redwoodjs.com/), and [Vercel](https://vercel.com). + +Thank you to the AV Team for the visuals, stage, and sound; and to the Westin Hotel for hosting us. + +Thank you to all the speakers who shared their knowledge and experiences with the community. + +Finally, thank you to everyone who attended in person and online to show what makes React, React. React is more than a library, it is a community, and it was inspiring to see everyone come together to share and learn together. + +See you next time! + diff --git a/src/content/blog/index.md b/src/content/blog/index.md index 409f33701..4a1a165a3 100644 --- a/src/content/blog/index.md +++ b/src/content/blog/index.md @@ -10,6 +10,24 @@ This blog is the official source for the updates from the React team. Anything i <div className="sm:-mx-5 flex flex-col gap-5 mt-12"> +<BlogCard title="React Conf 2024 Recap" date="May 22, 2024" url="/blog/2024/05/22/react-conf-2024-recap"> + +Last week we hosted React Conf 2024, a two-day conference in Henderson, Nevada where 700+ attendees gathered in-person to discuss the latest in UI engineering. This was our first in-person conference since 2019, and we were thrilled to be able to bring the community together again ... + +</BlogCard> + +<BlogCard title="React 19 RC " date="April 25, 2024" url="/blog/2024/04/25/react-19"> + +In the React 19 RC Upgrade Guide, we shared step-by-step instructions for upgrading your app to React 19. In this post, we'll give an overview of the new features in React 19, and how you can adopt them ... + +</BlogCard> + +<BlogCard title="React 19 RC Upgrade Guide" date="April 25, 2024" url="/blog/2024/04/25/react-19-upgrade-guide"> + +The improvements added to React 19 require some breaking changes, but we've worked to make the upgrade as smooth as possible, and we don't expect the changes to impact most apps. In this post, we will guide you through the steps for upgrading libraries to React 19 ... + +</BlogCard> + <BlogCard title="React Labs: What We've Been Working On – February 2024" date="February 15, 2024" url="/blog/2024/02/15/react-labs-what-we-have-been-working-on-february-2024"> In React Labs posts, we write about projects in active research and development. Since our last update, we've made significant progress on React Compiler, new features, and React 19, and we'd like to share what we learned. diff --git a/src/content/community/acknowledgements.md b/src/content/community/acknowledgements.md index c87a92979..aeb0787ef 100644 --- a/src/content/community/acknowledgements.md +++ b/src/content/community/acknowledgements.md @@ -16,6 +16,7 @@ We'd like to recognize a few people who have made significant contributions to R * [Andreas Svensson](https://github.com/syranide) * [Alex Krolick](https://github.com/alexkrolick) * [Alexey Pyltsyn](https://github.com/lex111) +* [Andrey Lunyov](https://github.com/alunyov) * [Brandon Dail](https://github.com/aweary) * [Brian Vaughn](https://github.com/bvaughn) * [Caleb Meredith](https://github.com/calebmer) diff --git a/src/content/community/conferences.md b/src/content/community/conferences.md index a28d60848..5070fbc41 100644 --- a/src/content/community/conferences.md +++ b/src/content/community/conferences.md @@ -10,88 +10,107 @@ Do you know of a local React.js conference? Add it here! (Please keep the list c ## Upcoming Conferences {/*upcoming-conferences*/} -### React Paris 2024 {/*react-paris-2024*/} -March 22, 2024. In-person in Paris, France + Remote (hybrid) +### React Nexus 2024 {/*react-nexus-2024*/} +July 04 & 05, 2024. Bangalore, India (In-person event) -[Website](https://react.paris/) - [Twitter](https://twitter.com/BeJS_) - [LinkedIn](https://www.linkedin.com/events/7150816372074192900/comments/) +[Website](https://reactnexus.com/) - [Twitter](https://twitter.com/ReactNexus) - [Linkedin](https://www.linkedin.com/company/react-nexus) - [YouTube](https://www.youtube.com/reactify_in) -### Epic Web Conf 2024 {/*epic-web-2024*/} -April 10 - 11, 2024. In-person in Park City, UT, USA +### Chain React 2024 {/*chain-react-2024*/} +July 17-19, 2024. In-person in Portland, OR, USA -[Website](https://www.epicweb.dev/conf) - [YouTube](https://www.youtube.com/@EpicWebDev) +[Website](https://chainreactconf.com) - [Twitter](https://twitter.com/ChainReactConf) -### React Miami 2024 {/*react-miami-2024*/} -April 19 - 20, 2024. In-person in Miami, FL, USA +### The Geek Conf 2024 {/*the-geek-conf-2024*/} +July 25, 2024. In-person in Berlin, Germany + remote (hybrid event) -[Website](https://reactmiami.com/) - [Twitter](https://twitter.com/ReactMiamiConf) +[Website](https://thegeekconf.com) - [Twitter](https://twitter.com/thegeekconf) -### React Connection 2024 {/*react-connection-2024*/} -April 22, 2024. In-person in Paris, France +### React Rally 2024 🐙 {/*react-rally-2024*/} +August 12-13, 2024. Park City, UT, USA -[Website](https://reactconnection.io/) - [Twitter](https://twitter.com/ReactConn) +[Website](https://reactrally.com) - [Twitter](https://twitter.com/ReactRally) - [YouTube](https://www.youtube.com/channel/UCXBhQ05nu3L1abBUGeQ0ahw) -### React Native Connection 2024 {/*react-native-connection-2024*/} -April 23, 2024. In-person in Paris, France +### React Universe Conf 2024 {/*react-universe-conf-2024*/} +September 5-6, 2024. Wrocław, Poland. -[Website](https://reactnativeconnection.io/) - [Twitter](https://twitter.com/ReactNativeConn) +[Website](https://www.reactuniverseconf.com/) - [Twitter](https://twitter.com/react_native_eu) - [LinkedIn](https://www.linkedin.com/events/reactuniverseconf7163919537074118657/) -### React Conf 2024 {/*react-conf-2024*/} -May 15 - 16, 2024. In-person in Henderson, NV, USA + remote +### React Alicante 2024 {/*react-alicante-2024*/} +September 19-21, 2024. Alicante, Spain. -[Website](https://conf.react.dev) - [Twitter](https://twitter.com/reactjs) +[Website](https://reactalicante.es/) - [Twitter](https://twitter.com/ReactAlicante) - [YouTube](https://www.youtube.com/channel/UCaSdUaITU1Cz6PvC97A7e0w) -### App.js Conf 2024 {/*appjs-conf-2024*/} -May 22 - 24, 2024. In-person in Kraków, Poland + remote +### RenderCon Kenya 2024 {/*rendercon-kenya-2024*/} +October 04 - 05, 2024. Nairobi, Kenya -[Website](https://appjs.co) - [Twitter](https://twitter.com/appjsconf) +[Website](https://rendercon.org/) - [Twitter](https://twitter.com/renderconke) - [LinkedIn](https://www.linkedin.com/company/renderconke/) - [YouTube](https://www.youtube.com/channel/UC0bCcG8gHUL4njDOpQGcMIA) + +### React India 2024 {/*react-india-2024*/} +October 17 - 19, 2024. In-person in Goa, India (hybrid event) + Oct 15 2024 - remote day + +[Website](https://www.reactindia.io) - [Twitter](https://twitter.com/react_india) - [Facebook](https://www.facebook.com/ReactJSIndia) - [Youtube](https://www.youtube.com/channel/UCaFbHCBkPvVv1bWs_jwYt3w) + +### React Brussels 2024 {/*react-brussels-2024*/} +October 18, 2024. In-person in Brussels, Belgium (hybrid event) + +[Website](https://www.react.brussels/) - [Twitter](https://x.com/BrusselsReact) + +### React Africa 2024 {/*react-africa-2024*/} +November 29, 2024. In-person in Casablanca, Morocco (hybrid event) + +[Website](https://react-africa.com/) - [Twitter](https://x.com/BeJS_) + +## Past Conferences {/*past-conferences*/} ### React Summit 2024 {/*react-summit-2024*/} June 14 & 18, 2024. In-person in Amsterdam, Netherlands + remote (hybrid event) [Website](https://reactsummit.com/) - [Twitter](https://twitter.com/reactsummit) - [Videos](https://portal.gitnation.org/) -### Render(ATL) 2024 🍑 {/*renderatl-2024-*/} -June 12 - June 14, 2024. Atlanta, GA, USA - -[Website](https://renderatl.com) - [Discord](https://www.renderatl.com/discord) - [Twitter](https://twitter.com/renderATL) - [Instagram](https://www.instagram.com/renderatl/) - [Facebook](https://www.facebook.com/renderatl/) - [LinkedIn](https://www.linkedin.com/company/renderatl) - [Podcast](https://www.renderatl.com/culture-and-code#/) - ### React Norway 2024 {/*react-norway-2024*/} June 14, 2024. In-person at Farris Bad Hotel in Larvik, Norway and online (hybrid event). [Website](https://reactnorway.com/) - [Twitter](https://twitter.com/ReactNorway) -### React Nexus 2024 {/*react-nexus-2024*/} -July 04 & 05, 2024. Bangalore, India (In-person event) +### Render(ATL) 2024 🍑 {/*renderatl-2024-*/} +June 12 - June 14, 2024. Atlanta, GA, USA -[Website](https://reactnexus.com/) - [Twitter](https://twitter.com/ReactNexus) - [Linkedin](https://www.linkedin.com/company/react-nexus) - [YouTube](https://www.youtube.com/reactify_in) +[Website](https://renderatl.com) - [Discord](https://www.renderatl.com/discord) - [Twitter](https://twitter.com/renderATL) - [Instagram](https://www.instagram.com/renderatl/) - [Facebook](https://www.facebook.com/renderatl/) - [LinkedIn](https://www.linkedin.com/company/renderatl) - [Podcast](https://www.renderatl.com/culture-and-code#/) -### Chain React 2024 {/*chain-react-2024*/} -July 17-19, 2024. In-person in Portland, OR, USA +### Frontend Nation 2024 {/*frontend-nation-2024*/} +June 4 - 7, 2024. Online -[Website](https://chainreactconf.com) - [Twitter](https://twitter.com/ChainReactConf) +[Website](https://frontendnation.com/) - [Twitter](https://twitter.com/frontendnation) -### The Geek Conf 2024 {/*the-geek-conf-2024*/} -July 25, 2024. In-person in Berlin, Germany + remote (hybrid event) +### App.js Conf 2024 {/*appjs-conf-2024*/} +May 22 - 24, 2024. In-person in Kraków, Poland + remote -[Website](https://thegeekconf.com) - [Twitter](https://twitter.com/thegeekconf) +[Website](https://appjs.co) - [Twitter](https://twitter.com/appjsconf) -### React Universe Conf 2024 {/*react-universe-conf-2024*/} -September 5-6, 2024. Wrocław, Poland. +### React Conf 2024 {/*react-conf-2024*/} +May 15 - 16, 2024. In-person in Henderson, NV, USA + remote -[Website](https://www.reactuniverseconf.com/) - [Twitter](https://twitter.com/react_native_eu) - [LinkedIn](https://www.linkedin.com/events/reactuniverseconf7163919537074118657/) +[Website](https://conf.react.dev) - [Twitter](https://twitter.com/reactjs) -### React Alicante 2024 {/*react-alicante-2024*/} -September 19-21, 2024. Alicante, Spain. +### React Native Connection 2024 {/*react-native-connection-2024*/} +April 23, 2024. In-person in Paris, France -[Website](https://reactalicante.es/) - [Twitter](https://twitter.com/ReactAlicante) - [YouTube](https://www.youtube.com/channel/UCaSdUaITU1Cz6PvC97A7e0w) +[Website](https://reactnativeconnection.io/) - [Twitter](https://twitter.com/ReactNativeConn) +### React Miami 2024 {/*react-miami-2024*/} +April 19 - 20, 2024. In-person in Miami, FL, USA -### React India 2024 {/*react-india-2024*/} -October 17 - 19, 2024. In-person in Goa, India (hybrid event) + Oct 15 2024 - remote day +[Website](https://reactmiami.com/) - [Twitter](https://twitter.com/ReactMiamiConf) -[Website](https://www.reactindia.io) - [Twitter](https://twitter.com/react_india) - [Facebook](https://www.facebook.com/ReactJSIndia) - [Youtube](https://www.youtube.com/channel/UCaFbHCBkPvVv1bWs_jwYt3w) +### Epic Web Conf 2024 {/*epic-web-2024*/} +April 10 - 11, 2024. In-person in Park City, UT, USA -## Past Conferences {/*past-conferences*/} +[Website](https://www.epicweb.dev/conf) - [YouTube](https://www.youtube.com/@EpicWebDev) + +### React Paris 2024 {/*react-paris-2024*/} +March 22, 2024. In-person in Paris, France + Remote (hybrid) + +[Website](https://react.paris/) - [Twitter](https://twitter.com/BeJS_) - [LinkedIn](https://www.linkedin.com/events/7150816372074192900/comments/) - [Videos](https://www.youtube.com/playlist?list=PL53Z0yyYnpWhUzgvr2Nys3kZBBLcY0TA7) ### React Day Berlin 2023 {/*react-day-berlin-2023*/} December 8 & 12, 2023. In-person in Berlin, Germany + remote first interactivity (hybrid event) diff --git a/src/content/community/docs-contributors.md b/src/content/community/docs-contributors.md index cbdbf7d7f..0f9d002d6 100644 --- a/src/content/community/docs-contributors.md +++ b/src/content/community/docs-contributors.md @@ -4,7 +4,7 @@ title: Docs Contributors <Intro> -React documentation is written and maintained by the [React team](/community/team) and [external contributors.](https://github.com/reactjs/reactjs.org/graphs/contributors) On this page, we'd like to thank a few people who've made significant contributions to this site. +React documentation is written and maintained by the [React team](/community/team) and [external contributors.](https://github.com/reactjs/react.dev/graphs/contributors) On this page, we'd like to thank a few people who've made significant contributions to this site. </Intro> diff --git a/src/content/community/meetups.md b/src/content/community/meetups.md index a12a5349c..d8887c3de 100644 --- a/src/content/community/meetups.md +++ b/src/content/community/meetups.md @@ -100,7 +100,7 @@ Do you have a local React.js meetup? Add it here! (Please keep the list alphabet * [Ahmedabad](https://www.meetup.com/react-ahmedabad/) * [Bangalore (React)](https://www.meetup.com/ReactJS-Bangalore/) * [Bangalore (React Native)](https://www.meetup.com/React-Native-Bangalore-Meetup) -* [Chennai](https://www.meetup.com/React-Chennai/) +* [Chennai](https://www.linkedin.com/company/chennaireact) * [Delhi NCR](https://www.meetup.com/React-Delhi-NCR/) * [Mumbai](https://reactmumbai.dev) * [Pune](https://www.meetup.com/ReactJS-and-Friends/) diff --git a/src/content/community/team.md b/src/content/community/team.md index 9ef95d64a..6004476e2 100644 --- a/src/content/community/team.md +++ b/src/content/community/team.md @@ -18,11 +18,7 @@ Current members of the React team are listed in alphabetical order below. Andrew got started with web development by making sites with WordPress, and eventually tricked himself into doing JavaScript. His favorite pastime is karaoke. Andrew is either a Disney villain or a Disney princess, depending on the day. </TeamMember> -<TeamMember name="Andrey Lunyov" permalink="andrey-lunyov" photo="/images/team/andrey-lunyov.jpg" github="alunyov" twitter="alunyov" threads="alunyov" title="Engineer at Meta"> - Andrey started his career as a designer and then gradually transitioned into web development. After joining the React Data team at Meta he worked on adding an incremental JavaScript compiler to Relay, and then later on, worked on removing the same compiler from Relay. Outside of work, Andrey likes to play music and engage in various sports. -</TeamMember> - -<TeamMember name="Dan Abramov" permalink="dan-abramov" photo="/images/team/gaearon.jpg" github="gaearon" twitter="dan_abramov" title="Independent Engineer"> +<TeamMember name="Dan Abramov" permalink="dan-abramov" photo="/images/team/gaearon.jpg" github="gaearon" twitter="dan_abramov2" title="Independent Engineer"> Dan got into programming after he accidentally discovered Visual Basic inside Microsoft PowerPoint. He has found his true calling in turning [Sebastian](#sebastian-markbåge)'s tweets into long-form blog posts. Dan occasionally wins at Fortnite by hiding in a bush until the game ends. </TeamMember> @@ -30,8 +26,12 @@ Current members of the React team are listed in alphabetical order below. Eli got into programming after he got suspended from middle school for hacking. He has been working on React and React Native since 2017. He enjoys eating treats, especially ice cream and apple pie. You can find Eli trying quirky activities like parkour, indoor skydiving, and aerial silks. </TeamMember> +<TeamMember name="Jack Pope" permalink="jack-pope" photo="/images/team/jack-pope.jpg" github="jackpope" personal="jackpope.me" title="Engineer at Meta"> + Shortly after being introduced to AutoHotkey, Jack had written scripts to automate everything he could think of. When reaching limitations there, he dove headfirst into web app development and hasn't looked back. Most recently, Jack worked on the web platform at Instagram before moving to React. His favorite programming language is JSX. +</TeamMember> + <TeamMember name="Jason Bonta" permalink="jason-bonta" photo="/images/team/jasonbonta.jpg" threads="someextent" title="Engineering Manager at Meta"> - Jason likes having large volumes of Amazon packages delivered to the office so that he can build forts. Despite literally walling himself off from his team at times and not understanding how for-of loops work, we appreciate him for the unique qualities he brings to his work. + Jason abandoned embedded C for a career in front-end engineering and never looked back. Armed with esoteric CSS knowledge and a passion for beautiful UI, Jason joined Facebook in 2010, where he now feels privileged to have seen JavaScript development come of age. Though he may not understand how `for...of` loops work, he loves getting to work with brilliant people on projects that enable amazing UX. </TeamMember> <TeamMember name="Joe Savona" permalink="joe-savona" photo="/images/team/joe.jpg" github="josephsavona" twitter="en_JS" threads="joesavona" title="Engineer at Meta"> @@ -43,11 +43,11 @@ Current members of the React team are listed in alphabetical order below. </TeamMember> <TeamMember name="Lauren Tan" permalink="lauren-tan" photo="/images/team/lauren.jpg" github="poteto" twitter="potetotes" threads="potetotes" personal="no.lol" title="Engineer at Meta"> - Lauren’s programming career peaked when she first discovered the `<marquee>` tag. She’s been chasing that high ever since. When she’s not adding bugs into React, she enjoys dropping cheeky memes in chat, and playing all too many video games with her partner, and her dog Zelda. + Lauren's programming career peaked when she first discovered the `<marquee>` tag. She’s been chasing that high ever since. She studied Finance instead of CS in college, so she learned to code using Excel instead of Java. Lauren enjoys dropping cheeky memes in chat, playing video games with her partner, and petting her dog Zelda. </TeamMember> <TeamMember name="Luna Wei" permalink="luna-wei" photo="/images/team/luna-wei.jpg" github="lunaleaps" twitter="lunaleaps" threads="lunaleaps" title="Engineer at Meta"> - Luna first learnt the fundamentals of python at the age of 6 from her father. Since then, she has been unstoppable. Luna aspires to be a gen z, and the road to success is paved with environmental advocacy, urban gardening and lots of quality time with her Voo-Doo’d (as pictured). + Luna first learnt the fundamentals of python at the age of 6 from her father. Since then, she has been unstoppable. Luna aspires to be a gen z, and the road to success is paved with environmental advocacy, urban gardening and lots of quality time with her Voo-Doo’d (as pictured). </TeamMember> <TeamMember name="Matt Carroll" permalink="matt-carroll" photo="/images/team/matt-carroll.png" github="mattcarrollcode" twitter="mattcarrollcode" threads="mattcarrollcode" title="Developer Advocate at Meta"> @@ -66,6 +66,10 @@ Current members of the React team are listed in alphabetical order below. Ricky majored in theoretical math and somehow found himself on the React Native team for a couple years before joining the React team. When he's not programming you can find him snowboarding, biking, climbing, golfing, or closing GitHub issues that do not match the issue template. </TeamMember> +<TeamMember name="Ruslan Lesiutin" permalink="ruslan-lesiutin" photo="/images/team/lesiutin.jpg" github="hoxyq" twitter="ruslanlesiutin" threads="lesiutin" title="Engineer at Meta"> + Ruslan's introduction to UI programming started when he was a kid by manually editing HTML templates for his custom gaming forums. Somehow, he ended up majoring in Computer Science. He enjoys music, games, and memes. Mostly memes. +</TeamMember> + <TeamMember name="Sathya Gunasekaran " permalink="sathya-gunasekaran" photo="/images/team/sathya.jpg" github="gsathya" twitter="_gsathya" threads="gsathya.03" title="Engineer at Meta"> Sathya hated the Dragon Book in school but somehow ended up working on compilers all his career. When he's not compiling React components, he's either drinking coffee or eating yet another Dosa. </TeamMember> @@ -74,7 +78,7 @@ Current members of the React team are listed in alphabetical order below. Sebastian majored in psychology. He's usually quiet. Even when he says something, it often doesn't make sense to the rest of us until a few months later. The correct way to pronounce his surname is "mark-boa-geh" but he settled for "mark-beige" out of pragmatism -- and that's how he approaches React. </TeamMember> -<TeamMember name="Sebastian Silbermann" permalink="sebastian-silbermann" photo="/images/team/sebsilbermann.jpg" github="eps1lon" twitter="sebsilbermann" threads="sebsilbermann" title="Independent Engineer"> +<TeamMember name="Sebastian Silbermann" permalink="sebastian-silbermann" photo="/images/team/sebsilbermann.jpg" github="eps1lon" twitter="sebsilbermann" threads="sebsilbermann" title="Engineer at Vercel"> Sebastian learned programming to make the browser games he played during class more enjoyable. Eventually this lead to contributing to as much open source code as possible. Outside of coding he's busy making sure people don't confuse him with the other Sebastians and Zilberman of the React community. </TeamMember> diff --git a/src/content/community/translations.md b/src/content/community/translations.md new file mode 100644 index 000000000..4c07e6a1e --- /dev/null +++ b/src/content/community/translations.md @@ -0,0 +1,35 @@ +--- +title: Translations +--- + +<Intro> + +React docs are translated by the global community into many languages all over the world. + +</Intro> + +## Source site {/*main-site*/} + +All translations are provided from the canonical source docs: + +- [English](https://react.dev/) — [Contribute](https://github.com/reactjs/react.dev/) + +## Full translations {/*full-translations*/} + +{/* If you are a language maintainer and want to add your language here, finish the "Core" translations and edit `deployedTranslations` under `src/utils`. */} + +<LanguageList progress="complete" /> + +## In-progress translations {/*in-progress-translations*/} + +For the progress of each translation, see: [Is React Translated Yet?](https://translations.react.dev/) + +<LanguageList progress="in-progress" /> + +## How to contribute {/*how-to-contribute*/} + +You can contribute to the translation efforts! + +The community conducts the translation work for the React docs on each language-specific fork of react.dev. Typical translation work involves directly translating a Markdown file and creating a pull request. Click the "contribute" link above to the GitHub repository for your language, and follow the instructions there to help with the translation effort. + +If you want to start a new translation for your language, visit: [translations.react.dev](https://github.com/reactjs/translations.react.dev) \ No newline at end of file diff --git a/src/content/community/versioning-policy.md b/src/content/community/versioning-policy.md index fad926c57..7aa71efd2 100644 --- a/src/content/community/versioning-policy.md +++ b/src/content/community/versioning-policy.md @@ -8,6 +8,8 @@ All stable builds of React go through a high level of testing and follow semanti </Intro> +For a list of previous releases, see the [Versions](/versions) page. + ## Stable releases {/*stable-releases*/} Stable React releases (also known as "Latest" release channel) follow [semantic versioning (semver)](https://semver.org/) principles. diff --git a/src/content/learn/adding-interactivity.md b/src/content/learn/adding-interactivity.md index 0d4a3b23f..5c87a3e79 100644 --- a/src/content/learn/adding-interactivity.md +++ b/src/content/learn/adding-interactivity.md @@ -265,7 +265,7 @@ setCount(count + 1); // Request a re-render with 1 console.log(count); // Still 0! ``` -This behavior help you avoid subtle bugs. Here is a little chat app. Try to guess what happens if you press "Send" first and *then* change the recipient to Bob. Whose name will appear in the `alert` five seconds later? +This behavior helps you avoid subtle bugs. Here is a little chat app. Try to guess what happens if you press "Send" first and *then* change the recipient to Bob. Whose name will appear in the `alert` five seconds later? <Sandpack> diff --git a/src/content/learn/conditional-rendering.md b/src/content/learn/conditional-rendering.md index 895d610d3..95be5d2e0 100644 --- a/src/content/learn/conditional-rendering.md +++ b/src/content/learn/conditional-rendering.md @@ -52,13 +52,13 @@ export default function PackingList() { </Sandpack> -Notice that some of the `Item` components have their `isPacked` prop set to `true` instead of `false`. You want to add a checkmark (✔) to packed items if `isPacked={true}`. +Notice that some of the `Item` components have their `isPacked` prop set to `true` instead of `false`. You want to add a checkmark (✅) to packed items if `isPacked={true}`. You can write this as an [`if`/`else` statement](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/if...else) like so: ```js if (isPacked) { - return <li className="item">{name} ✔</li>; + return <li className="item">{name} ✅</li>; } return <li className="item">{name}</li>; ``` @@ -70,7 +70,7 @@ If the `isPacked` prop is `true`, this code **returns a different JSX tree.** Wi ```js function Item({ name, isPacked }) { if (isPacked) { - return <li className="item">{name} ✔</li>; + return <li className="item">{name} ✅</li>; } return <li className="item">{name}</li>; } @@ -159,7 +159,7 @@ In practice, returning `null` from a component isn't common because it might sur In the previous example, you controlled which (if any!) JSX tree would be returned by the component. You may already have noticed some duplication in the render output: ```js -<li className="item">{name} ✔</li> +<li className="item">{name} ✅</li> ``` is very similar to @@ -172,7 +172,7 @@ Both of the conditional branches return `<li className="item">...</li>`: ```js if (isPacked) { - return <li className="item">{name} ✔</li>; + return <li className="item">{name} ✅</li>; } return <li className="item">{name}</li>; ``` @@ -187,7 +187,7 @@ Instead of this: ```js if (isPacked) { - return <li className="item">{name} ✔</li>; + return <li className="item">{name} ✅</li>; } return <li className="item">{name}</li>; ``` @@ -197,12 +197,12 @@ You can write this: ```js return ( <li className="item"> - {isPacked ? name + ' ✔' : name} + {isPacked ? name + ' ✅' : name} </li> ); ``` -You can read it as *"if `isPacked` is true, then (`?`) render `name + ' ✔'`, otherwise (`:`) render `name`"*. +You can read it as *"if `isPacked` is true, then (`?`) render `name + ' ✅'`, otherwise (`:`) render `name`"*. <DeepDive> @@ -222,7 +222,7 @@ function Item({ name, isPacked }) { <li className="item"> {isPacked ? ( <del> - {name + ' ✔'} + {name + ' ✅'} </del> ) : ( name @@ -265,7 +265,7 @@ Another common shortcut you'll encounter is the [JavaScript logical AND (`&&`) o ```js return ( <li className="item"> - {name} {isPacked && '✔'} + {name} {isPacked && '✅'} </li> ); ``` @@ -280,7 +280,7 @@ Here it is in action: function Item({ name, isPacked }) { return ( <li className="item"> - {name} {isPacked && '✔'} + {name} {isPacked && '✅'} </li> ); } @@ -337,7 +337,7 @@ Use an `if` statement to reassign a JSX expression to `itemContent` if `isPacked ```js if (isPacked) { - itemContent = name + " ✔"; + itemContent = name + " ✅"; } ``` @@ -357,7 +357,7 @@ This style is the most verbose, but it's also the most flexible. Here it is in a function Item({ name, isPacked }) { let itemContent = name; if (isPacked) { - itemContent = name + " ✔"; + itemContent = name + " ✅"; } return ( <li className="item"> @@ -401,7 +401,7 @@ function Item({ name, isPacked }) { if (isPacked) { itemContent = ( <del> - {name + " ✔"} + {name + " ✅"} </del> ); } @@ -464,7 +464,7 @@ Use the conditional operator (`cond ? a : b`) to render a ❌ if `isPacked` isn function Item({ name, isPacked }) { return ( <li className="item"> - {name} {isPacked && '✔'} + {name} {isPacked && '✅'} </li> ); } @@ -502,7 +502,7 @@ export default function PackingList() { function Item({ name, isPacked }) { return ( <li className="item"> - {name} {isPacked ? '✔' : '❌'} + {name} {isPacked ? '✅' : '❌'} </li> ); } diff --git a/src/content/learn/describing-the-ui.md b/src/content/learn/describing-the-ui.md index ce49b85c8..34ee0c01a 100644 --- a/src/content/learn/describing-the-ui.md +++ b/src/content/learn/describing-the-ui.md @@ -327,7 +327,7 @@ In this example, the JavaScript `&&` operator is used to conditionally render a function Item({ name, isPacked }) { return ( <li className="item"> - {name} {isPacked && '✔'} + {name} {isPacked && '✅'} </li> ); } diff --git a/src/content/learn/editor-setup.md b/src/content/learn/editor-setup.md index 0a12fa5b4..946805e8f 100644 --- a/src/content/learn/editor-setup.md +++ b/src/content/learn/editor-setup.md @@ -40,7 +40,11 @@ title: התקנת עורך קוד ### Formatting {/*formatting*/} +<<<<<<< HEAD הדרך האחרון שאתה רוצה כשאתה משתף קוד עם מפתח אחר הוא להיכנס לוויכוח [tabs vs spaces](https://www.google.com/search?q=tabs+vs+spaces). למזלנו, [prettier](https://prettier.io/)) ינקה את הקוד שלך על ידי התאמתו לחוקים קיימים. בעת הרצת prettier, כל ה-tabs יהפכו ל-spaces, וגרשיים כפולים או יחידים ישונו בהתאם לקונפיגורציה. בסביבת פיתוח אידיאלית, Prettier ירוץ בעת שמירת קובץ, ויעשה את כל השינויים הללו אוטומטית. +======= +The last thing you want to do when sharing your code with another contributor is get into a discussion about [tabs vs spaces](https://www.google.com/search?q=tabs+vs+spaces)! Fortunately, [Prettier](https://prettier.io/) will clean up your code by reformatting it to conform to preset, configurable rules. Run Prettier, and all your tabs will be converted to spaces—and your indentation, quotes, etc will also all be changed to conform to the configuration. In the ideal setup, Prettier will run when you save your file, quickly making these edits for you. +>>>>>>> 7d50c3ffd4df2dc7903f4e41069653a456a9c223 ניתן להתקין את Prettier ב[VS Code](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) בעזרת הצעדים הבאים: diff --git a/src/content/learn/installation.md b/src/content/learn/installation.md index c5426ea94..7251fc31b 100644 --- a/src/content/learn/installation.md +++ b/src/content/learn/installation.md @@ -37,7 +37,7 @@ export default function App() { You can edit it directly or open it in a new tab by pressing the "Fork" button in the upper right corner. -Most pages in the React documentation contain sandboxes like this. Outside of the React documentation, there are many online sandboxes that support React: for example, [CodeSandbox](https://codesandbox.io/s/new), [StackBlitz](https://stackblitz.com/fork/react), or [CodePen.](https://codepen.io/pen?&editors=0010&layout=left&prefill_data_id=3f4569d1-1b11-4bce-bd46-89090eed5ddb) +Most pages in the React documentation contain sandboxes like this. Outside of the React documentation, there are many online sandboxes that support React: for example, [CodeSandbox](https://codesandbox.io/s/new), [StackBlitz](https://stackblitz.com/fork/react), or [CodePen.](https://codepen.io/pen?template=QWYVwWN) ### Try React locally {/*try-react-locally*/} diff --git a/src/content/learn/manipulating-the-dom-with-refs.md b/src/content/learn/manipulating-the-dom-with-refs.md index 3d5cbfd1d..2d44d7353 100644 --- a/src/content/learn/manipulating-the-dom-with-refs.md +++ b/src/content/learn/manipulating-the-dom-with-refs.md @@ -218,18 +218,19 @@ This example shows how you can use this approach to scroll to an arbitrary node <Sandpack> ```js -import { useRef } from 'react'; +import { useRef, useState } from "react"; export default function CatFriends() { const itemsRef = useRef(null); + const [catList, setCatList] = useState(setupCatList); - function scrollToId(itemId) { + function scrollToCat(cat) { const map = getMap(); - const node = map.get(itemId); + const node = map.get(cat); node.scrollIntoView({ - behavior: 'smooth', - block: 'nearest', - inline: 'center' + behavior: "smooth", + block: "nearest", + inline: "center", }); } @@ -244,34 +245,25 @@ export default function CatFriends() { return ( <> <nav> - <button onClick={() => scrollToId(0)}> - Tom - </button> - <button onClick={() => scrollToId(5)}> - Maru - </button> - <button onClick={() => scrollToId(9)}> - Jellylorum - </button> + <button onClick={() => scrollToCat(catList[0])}>Tom</button> + <button onClick={() => scrollToCat(catList[5])}>Maru</button> + <button onClick={() => scrollToCat(catList[9])}>Jellylorum</button> </nav> <div> <ul> - {catList.map(cat => ( + {catList.map((cat) => ( <li - key={cat.id} + key={cat} ref={(node) => { const map = getMap(); if (node) { - map.set(cat.id, node); + map.set(cat, node); } else { - map.delete(cat.id); + map.delete(cat); } }} > - <img - src={cat.imageUrl} - alt={'Cat #' + cat.id} - /> + <img src={cat} /> </li> ))} </ul> @@ -280,12 +272,13 @@ export default function CatFriends() { ); } -const catList = []; -for (let i = 0; i < 10; i++) { - catList.push({ - id: i, - imageUrl: 'https://placekitten.com/250/200?image=' + i - }); +function setupCatList() { + const catList = []; + for (let i = 0; i < 10; i++) { + catList.push("https://loremflickr.com/320/240/cat?lock=" + i); + } + + return catList; } ``` @@ -316,6 +309,16 @@ li { } ``` +```json package.json hidden +{ + "dependencies": { + "react": "canary", + "react-dom": "canary", + "react-scripts": "^5.0.0" + } +} +``` + </Sandpack> In this example, `itemsRef` doesn't hold a single DOM node. Instead, it holds a [Map](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Map) from item ID to a DOM node. ([Refs can hold any values!](/learn/referencing-values-with-refs)) The [`ref` callback](/reference/react-dom/components/common#ref-callback) on every list item takes care to update the Map: @@ -327,10 +330,10 @@ In this example, `itemsRef` doesn't hold a single DOM node. Instead, it holds a const map = getMap(); if (node) { // Add to the Map - map.set(cat.id, node); + map.set(cat, node); } else { // Remove from the Map - map.delete(cat.id); + map.delete(cat); } }} > @@ -338,6 +341,28 @@ In this example, `itemsRef` doesn't hold a single DOM node. Instead, it holds a This lets you read individual DOM nodes from the Map later. +<Canary> + +This example shows another approach for managing the Map with a `ref` callback cleanup function. + +```js +<li + key={cat.id} + ref={node => { + const map = getMap(); + // Add to the Map + map.set(cat, node); + + return () => { + // Remove from the Map + map.delete(cat); + }; + }} +> +``` + +</Canary> + </DeepDive> ## Accessing another component's DOM nodes {/*accessing-another-components-dom-nodes*/} @@ -493,7 +518,7 @@ In general, you [don't want](/learn/referencing-values-with-refs#best-practices- React sets `ref.current` during the commit. Before updating the DOM, React sets the affected `ref.current` values to `null`. After updating the DOM, React immediately sets them to the corresponding DOM nodes. -**Usually, you will access refs from event handlers.** If you want to do something with a ref, but there is no particular event to do it in, you might need an Effect. We will discuss effects on the next pages. +**Usually, you will access refs from event handlers.** If you want to do something with a ref, but there is no particular event to do it in, you might need an Effect. We will discuss Effects on the next pages. <DeepDive> diff --git a/src/content/learn/react-compiler.md b/src/content/learn/react-compiler.md new file mode 100644 index 000000000..2920e8643 --- /dev/null +++ b/src/content/learn/react-compiler.md @@ -0,0 +1,410 @@ +--- +title: React Compiler +--- + +<Intro> +This page will give you an introduction to the new experimental React Compiler and how to try it out successfully. +</Intro> + +<Wip> +These docs are still a work in progress. More documentation is available in the [React Compiler Working Group repo](https://github.com/reactwg/react-compiler/discussions), and will be upstreamed into these docs when they are more stable. +</Wip> + +<YouWillLearn> + +* Getting started with the compiler +* Installing the compiler and eslint plugin +* Troubleshooting + +</YouWillLearn> + +<Note> +React Compiler is a new experimental compiler that we've open sourced to get early feedback from the community. It still has rough edges and is not yet fully ready for production. + +React Compiler requires React 19 RC. If you are unable to upgrade to React 19, you may try a userspace implementation of the cache function as described in the [Working Group](https://github.com/reactwg/react-compiler/discussions/6). However, please note that this is not recommended and you should upgrade to React 19 when possible. +</Note> + +React Compiler is a new experimental compiler that we've open sourced to get early feedback from the community. It is a build-time only tool that automatically optimizes your React app. It works with plain JavaScript, and understands the [Rules of React](/reference/rules), so you don't need to rewrite any code to use it. + +The compiler also includes an [eslint plugin](#installing-eslint-plugin-react-compiler) that surfaces the analysis from the compiler right in your editor. The plugin runs independently of the compiler and can be used even if you aren't using the compiler in your app. We recommend all React developers to use this eslint plugin to help improve the quality of your codebase. + +### What does the compiler do? {/*what-does-the-compiler-do*/} + +In order to optimize applications, React Compiler automatically memoizes your code. You may be familiar today with memoization through APIs such as `useMemo`, `useCallback`, and `React.memo`. With these APIs you can tell React that certain parts of your application don't need to recompute if their inputs haven't changed, reducing work on updates. While powerful, it's easy to forget to apply memoization or apply them incorrectly. This can lead to inefficient updates as React has to check parts of your UI that don't have any _meaningful_ changes. + +The compiler uses its knowledge of JavaScript and React's rules to automatically memoize values or groups of values within your components and hooks. If it detects breakages of the rules, it will automatically skip over just those components or hooks, and continue safely compiling other code. + +If your codebase is already very well-memoized, you might not expect to see major performance improvements with the compiler. However, in practice memoizing the correct dependencies that cause performance issues is tricky to get right by hand. + +<DeepDive> +#### What kind of memoization does React Compiler add? {/*what-kind-of-memoization-does-react-compiler-add*/} + +The initial release of React Compiler is primarily focused on **improving update performance** (re-rendering existing components), so it focuses on these two use cases: + +1. **Skipping cascading re-rendering of components** + * Re-rendering `<Parent />` causes many components in its component tree to re-render, even though only `<Parent />` has changed +1. **Skipping expensive calculations from outside of React** + * For example, calling `expensivelyProcessAReallyLargeArrayOfObjects()` inside of your component or hook that needs that data + +#### Optimizing Re-renders {/*optimizing-re-renders*/} + +React lets you express your UI as a function of their current state (more concretely: their props, state, and context). In its current implementation, when a component's state changes, React will re-render that component _and all of its children_ — unless you have applied some form of manual memoization with `useMemo()`, `useCallback()`, or `React.memo()`. For example, in the following example, `<MessageButton>` will re-render whenever `<FriendList>`'s state changes: + +```javascript +function FriendList({ friends }) { + const onlineCount = useFriendOnlineCount(); + if (friends.length === 0) { + return <NoFriends />; + } + return ( + <div> + <span>{onlineCount} online</span> + {friends.map((friend) => ( + <FriendListCard key={friend.id} friend={friend} /> + ))} + <MessageButton /> + </div> + ); +} +``` +[_See this example in the React Compiler Playground_](https://playground.react.dev/#N4Igzg9grgTgxgUxALhAMygOzgFwJYSYAEAYjHgpgCYAyeYOAFMEWuZVWEQL4CURwADrEicQgyKEANnkwIAwtEw4iAXiJQwCMhWoB5TDLmKsTXgG5hRInjRFGbXZwB0UygHMcACzWr1ABn4hEWsYBBxYYgAeADkIHQ4uAHoAPksRbisiMIiYYkYs6yiqPAA3FMLrIiiwAAcAQ0wU4GlZBSUcbklDNqikusaKkKrgR0TnAFt62sYHdmp+VRT7SqrqhOo6Bnl6mCoiAGsEAE9VUfmqZzwqLrHqM7ubolTVol5eTOGigFkEMDB6u4EAAhKA4HCEZ5DNZ9ErlLIWYTcEDcIA) + +React Compiler automatically applies the equivalent of manual memoization, ensuring that only the relevant parts of an app re-render as state changes, which is sometimes referred to as "fine-grained reactivity". In the above example, React Compiler determines that the return value of `<FriendListCard />` can be reused even as `friends` changes, and can avoid recreating this JSX _and_ avoid re-rendering `<MessageButton>` as the count changes. + +#### Expensive calculations also get memoized {/*expensive-calculations-also-get-memoized*/} + +The compiler can also automatically memoize for expensive calculations used during rendering: + +```js +// **Not** memoized by React Compiler, since this is not a component or hook +function expensivelyProcessAReallyLargeArrayOfObjects() { /* ... */ } + +// Memoized by React Compiler since this is a component +function TableContainer({ items }) { + // This function call would be memoized: + const data = expensivelyProcessAReallyLargeArrayOfObjects(items); + // ... +} +``` +[_See this example in the React Compiler Playground_](https://playground.react.dev/#N4Igzg9grgTgxgUxALhAejQAgFTYHIQAuumAtgqRAJYBeCAJpgEYCemASggIZyGYDCEUgAcqAGwQwANJjBUAdokyEAFlTCZ1meUUxdMcIcIjyE8vhBiYVECAGsAOvIBmURYSonMCAB7CzcgBuCGIsAAowEIhgYACCnFxioQAyXDAA5gixMDBcLADyzvlMAFYIvGAAFACUmMCYaNiYAHStOFgAvk5OGJgAshTUdIysHNy8AkbikrIKSqpaWvqGIiZmhE6u7p7ymAAqXEwSguZcCpKV9VSEFBodtcBOmAYmYHz0XIT6ALzefgFUYKhCJRBAxeLcJIsVIZLI5PKFYplCqVa63aoAbm6u0wMAQhFguwAPPRAQA+YAfL4dIloUmBMlODogDpAA) + +However, if `expensivelyProcessAReallyLargeArrayOfObjects` is truly an expensive function, you may want to consider implementing its own memoization outside of React, because: + +- React Compiler only memoizes React components and hooks, not every function +- React Compiler's memoization is not shared across multiple components or hooks + +So if `expensivelyProcessAReallyLargeArrayOfObjects` was used in many different components, even if the same exact items were passed down, that expensive calculation would be run repeatedly. We recommend [profiling](https://react.dev/reference/react/useMemo#how-to-tell-if-a-calculation-is-expensive) first to see if it really is that expensive before making code more complicated. +</DeepDive> + +### What does the compiler assume? {/*what-does-the-compiler-assume*/} + +React Compiler assumes that your code: + +1. Is valid, semantic JavaScript +2. Tests that nullable/optional values and properties are defined before accessing them (for example, by enabling [`strictNullChecks`](https://www.typescriptlang.org/tsconfig/#strictNullChecks) if using TypeScript), i.e., `if (object.nullableProperty) { object.nullableProperty.foo }` or with optional-chaining `object.nullableProperty?.foo` +3. Follows the [Rules of React](https://react.dev/reference/rules) + +React Compiler can verify many of the Rules of React statically, and will safely skip compilation when it detects an error. To see the errors we recommend also installing [eslint-plugin-react-compiler](https://www.npmjs.com/package/eslint-plugin-react-compiler). + +### Should I try out the compiler? {/*should-i-try-out-the-compiler*/} + +Please note that the compiler is still experimental and has many rough edges. While it has been used in production at companies like Meta, rolling out the compiler to production for your app will depend on the health of your codebase and how well you've followed the [Rules of React](/reference/rules). + +**You don't have to rush into using the compiler now. It's okay to wait until it reaches a stable release before adopting it.** However, we do appreciate trying it out in small experiments in your apps so that you can [provide feedback](#reporting-issues) to us to help make the compiler better. + +## Getting Started {/*getting-started*/} + +In addition to these docs, we recommend checking the [React Compiler Working Group](https://github.com/reactwg/react-compiler) for additional information and discussion about the compiler. + +### Checking compatibility {/*checking-compatibility*/} + +Prior to installing the compiler, you can first check to see if your codebase is compatible: + +<TerminalBlock> +npx react-compiler-healthcheck@experimental +</TerminalBlock> + +This script will: + +- Check how many components can be successfully optimized: higher is better +- Check for `<StrictMode>` usage: having this enabled and followed means a higher chance that the [Rules of React](/reference/rules) are followed +- Check for incompatible library usage: known libraries that are incompatible with the compiler + +As an example: + +<TerminalBlock> +Successfully compiled 8 out of 9 components. +StrictMode usage not found. +Found no usage of incompatible libraries. +</TerminalBlock> + +### Installing eslint-plugin-react-compiler {/*installing-eslint-plugin-react-compiler*/} + +React Compiler also powers an eslint plugin. The eslint plugin can be used **independently** of the compiler, meaning you can use the eslint plugin even if you don't use the compiler. + +<TerminalBlock> +npm install eslint-plugin-react-compiler@experimental +</TerminalBlock> + +Then, add it to your eslint config: + +```js +module.exports = { + plugins: [ + 'eslint-plugin-react-compiler', + ], + rules: { + 'react-compiler/react-compiler': "error", + }, +} +``` + +The eslint plugin will display any violations of the rules of React in your editor. When it does this, it means that the compiler has skipped over optimizing that component or hook. This is perfectly okay, and the compiler can recover and continue optimizing other components in your codebase. + +**You don't have to fix all eslint violations straight away.** You can address them at your own pace to increase the amount of components and hooks being optimized, but it is not required to fix everything before you can use the compiler. + +### Rolling out the compiler to your codebase {/*using-the-compiler-effectively*/} + +#### Existing projects {/*existing-projects*/} +The compiler is designed to compile functional components and hooks that follow the [Rules of React](/reference/rules). It can also handle code that breaks those rules by bailing out (skipping over) those components or hooks. However, due to the flexible nature of JavaScript, the compiler cannot catch every possible violation and may compile with false negatives: that is, the compiler may accidentally compile a component/hook that breaks the Rules of React which can lead to undefined behavior. + +For this reason, to adopt the compiler successfully on existing projects, we recommend running it on a small directory in your product code first. You can do this by configuring the compiler to only run on a specific set of directories: + +```js {3} +const ReactCompilerConfig = { + sources: (filename) => { + return filename.indexOf('src/path/to/dir') !== -1; + }, +}; +``` + +In rare cases, you can also configure the compiler to run in "opt-in" mode using the `compilationMode: "annotation"` option. This makes it so the compiler will only compile components and hooks annotated with a `"use memo"` directive. Please note that the `annotation` mode is a temporary one to aid early adopters, and that we don't intend for the `"use memo"` directive to be used for the long term. + +```js {2,7} +const ReactCompilerConfig = { + compilationMode: "annotation", +}; + +// src/app.jsx +export default function App() { + "use memo"; + // ... +} +``` + +When you have more confidence with rolling out the compiler, you can expand coverage to other directories as well and slowly roll it out to your whole app. + +#### New projects {/*new-projects*/} + +If you're starting a new project, you can enable the compiler on your entire codebase, which is the default behavior. + +## Usage {/*installation*/} + +### Babel {/*usage-with-babel*/} + +<TerminalBlock> +npm install babel-plugin-react-compiler@experimental +</TerminalBlock> + +The compiler includes a Babel plugin which you can use in your build pipeline to run the compiler. + +After installing, add it to your Babel config. Please note that it's critical that the compiler run **first** in the pipeline: + +```js {7} +// babel.config.js +const ReactCompilerConfig = { /* ... */ }; + +module.exports = function () { + return { + plugins: [ + ['babel-plugin-react-compiler', ReactCompilerConfig], // must run first! + // ... + ], + }; +}; +``` + +`babel-plugin-react-compiler` should run first before other Babel plugins as the compiler requires the input source information for sound analysis. + +### Vite {/*usage-with-vite*/} + +If you use Vite, you can add the plugin to vite-plugin-react: + +```js {10} +// vite.config.js +const ReactCompilerConfig = { /* ... */ }; + +export default defineConfig(() => { + return { + plugins: [ + react({ + babel: { + plugins: [ + ["babel-plugin-react-compiler", ReactCompilerConfig], + ], + }, + }), + ], + // ... + }; +}); +``` + +### Next.js {/*usage-with-nextjs*/} + +Next.js has an experimental configuration to enable the React Compiler. It automatically ensures Babel is set up with `babel-plugin-react-compiler`. + +- Install Next.js canary, which uses React 19 Release Candidate +- Install `babel-plugin-react-compiler` + +<TerminalBlock> +npm install next@canary babel-plugin-react-compiler@experimental +</TerminalBlock> + +Then configure the experimental option in `next.config.js`: + +```js {4,5,6} +// next.config.js +/** @type {import('next').NextConfig} */ +const nextConfig = { + experimental: { + reactCompiler: true, + }, +}; + +module.exports = nextConfig; +``` + +Using the experimental option ensures support for the React Compiler in: + +- App Router +- Pages Router +- Webpack (default) +- Turbopack (opt-in through `--turbo`) + + +### Remix {/*usage-with-remix*/} +Install `vite-plugin-babel`, and add the compiler's Babel plugin to it: + +<TerminalBlock> +npm install vite-plugin-babel +</TerminalBlock> + +```js {2,14} +// vite.config.js +import babel from "vite-plugin-babel"; + +const ReactCompilerConfig = { /* ... */ }; + +export default defineConfig({ + plugins: [ + remix({ /* ... */}), + babel({ + filter: /\.[jt]sx?$/, + babelConfig: { + presets: ["@babel/preset-typescript"], // if you use TypeScript + plugins: [ + ["babel-plugin-react-compiler", ReactCompilerConfig], + ], + }, + }), + ], +}); +``` + +### Webpack {/*usage-with-webpack*/} + +You can create your own loader for React Compiler, like so: + +```js +const ReactCompilerConfig = { /* ... */ }; +const BabelPluginReactCompiler = require('babel-plugin-react-compiler'); + +function reactCompilerLoader(sourceCode, sourceMap) { + // ... + const result = transformSync(sourceCode, { + // ... + plugins: [ + [BabelPluginReactCompiler, ReactCompilerConfig], + ], + // ... + }); + + if (result === null) { + this.callback( + Error( + `Failed to transform "${options.filename}"` + ) + ); + return; + } + + this.callback( + null, + result.code, + result.map === null ? undefined : result.map + ); +} + +module.exports = reactCompilerLoader; +``` + +### Expo {/*usage-with-expo*/} + +Please refer to [Expo's docs](https://docs.expo.dev/preview/react-compiler/) to enable and use the React Compiler in Expo apps. + +### Metro (React Native) {/*usage-with-react-native-metro*/} + +React Native uses Babel via Metro, so refer to the [Usage with Babel](#usage-with-babel) section for installation instructions. + +### Rspack {/*usage-with-rspack*/} + +Please refer to [Rspack's docs](https://rspack.dev/guide/tech/react#react-compiler) to enable and use the React Compiler in Rspack apps. + +### Rsbuild {/*usage-with-rsbuild*/} + +Please refer to [Rsbuild's docs](https://rsbuild.dev/guide/framework/react#react-compiler) to enable and use the React Compiler in Rsbuild apps. + +## Troubleshooting {/*troubleshooting*/} + +To report issues, please first create a minimal repro on the [React Compiler Playground](https://playground.react.dev/) and include it in your bug report. You can open issues in the [facebook/react](https://github.com/facebook/react/issues) repo. + +You can also provide feedback in the React Compiler Working Group by applying to be a member. Please see [the README for more details on joining](https://github.com/reactwg/react-compiler). + +### `(0 , _c) is not a function` error {/*0--_c-is-not-a-function-error*/} + +This occurs if you are not using React 19 RC and up. To fix this, [upgrade your app to React 19 RC](https://react.dev/blog/2024/04/25/react-19-upgrade-guide) first. + +If you are unable to upgrade to React 19, you may try a userspace implementation of the cache function as described in the [Working Group](https://github.com/reactwg/react-compiler/discussions/6). However, please note that this is not recommended and you should upgrade to React 19 when possible. + +### How do I know my components have been optimized? {/*how-do-i-know-my-components-have-been-optimized*/} + +[React Devtools](/learn/react-developer-tools) (v5.0+) has built-in support for React Compiler and will display a "Memo ✨" badge next to components that have been optimized by the compiler. + +### Something is not working after compilation {/*something-is-not-working-after-compilation*/} +If you have eslint-plugin-react-compiler installed, the compiler will display any violations of the rules of React in your editor. When it does this, it means that the compiler has skipped over optimizing that component or hook. This is perfectly okay, and the compiler can recover and continue optimizing other components in your codebase. **You don't have to fix all eslint violations straight away.** You can address them at your own pace to increase the amount of components and hooks being optimized. + +Due to the flexible and dynamic nature of JavaScript however, it's not possible to comprehensively detect all cases. Bugs and undefined behavior such as infinite loops may occur in those cases. + +If your app doesn't work properly after compilation and you aren't seeing any eslint errors, the compiler may be incorrectly compiling your code. To confirm this, try to make the issue go away by aggressively opting out any component or hook you think might be related via the [`"use no memo"` directive](#opt-out-of-the-compiler-for-a-component). + +```js {2} +function SuspiciousComponent() { + "use no memo"; // opts out this component from being compiled by React Compiler + // ... +} +``` + +<Note> +#### `"use no memo"` {/*use-no-memo*/} + +`"use no memo"` is a _temporary_ escape hatch that lets you opt-out components and hooks from being compiled by the React Compiler. This directive is not meant to be long lived the same way as eg [`"use client"`](/reference/rsc/use-client) is. + +It is not recommended to reach for this directive unless it's strictly necessary. Once you opt-out a component or hook, it is opted-out forever until the directive is removed. This means that even if you fix the code, the compiler will still skip over compiling it unless you remove the directive. +</Note> + +When you make the error go away, confirm that removing the opt out directive makes the issue come back. Then share a bug report with us (you can try to reduce it to a small repro, or if it's open source code you can also just paste the entire source) using the [React Compiler Playground](https://playground.react.dev) so we can identify and help fix the issue. + +### Other issues {/*other-issues*/} + +Please see https://github.com/reactwg/react-compiler/discussions/7. diff --git a/src/content/learn/rendering-lists.md b/src/content/learn/rendering-lists.md index 108e394e2..32f81c447 100644 --- a/src/content/learn/rendering-lists.md +++ b/src/content/learn/rendering-lists.md @@ -113,9 +113,11 @@ const people = [{ name: 'Mohammad Abdus Salam', profession: 'physicist', }, { + id: 3, name: 'Percy Lavon Julian', profession: 'chemist', }, { + id: 4, name: 'Subrahmanyan Chandrasekhar', profession: 'astrophysicist', }]; diff --git a/src/content/learn/reusing-logic-with-custom-hooks.md b/src/content/learn/reusing-logic-with-custom-hooks.md index 13a556c7b..67de5e97f 100644 --- a/src/content/learn/reusing-logic-with-custom-hooks.md +++ b/src/content/learn/reusing-logic-with-custom-hooks.md @@ -1899,7 +1899,7 @@ export default function Counter() { } ``` -You'll need to write your custom Hook in `useCounter.js` and import it into the `Counter.js` file. +You'll need to write your custom Hook in `useCounter.js` and import it into the `App.js` file. <Sandpack> diff --git a/src/content/learn/separating-events-from-effects.md b/src/content/learn/separating-events-from-effects.md index ac65d2b60..21276c287 100644 --- a/src/content/learn/separating-events-from-effects.md +++ b/src/content/learn/separating-events-from-effects.md @@ -44,7 +44,7 @@ function ChatRoom({ roomId }) { return ( <> <input value={message} onChange={e => setMessage(e.target.value)} /> - <button onClick={handleSendClick}>Send</button>; + <button onClick={handleSendClick}>Send</button> </> ); } diff --git a/src/content/learn/synchronizing-with-effects.md b/src/content/learn/synchronizing-with-effects.md index f1aa98438..48e99cc27 100644 --- a/src/content/learn/synchronizing-with-effects.md +++ b/src/content/learn/synchronizing-with-effects.md @@ -45,7 +45,7 @@ Here and later in this text, capitalized "Effect" refers to the React-specific d To write an Effect, follow these three steps: -1. **Declare an Effect.** By default, your Effect will run after every render. +1. **Declare an Effect.** By default, your Effect will run after every [commit](/learn/render-and-commit). 2. **Specify the Effect dependencies.** Most Effects should only re-run *when needed* rather than after every render. For example, a fade-in animation should only trigger when a component appears. Connecting and disconnecting to a chat room should only happen when the component appears and disappears, or when the chat room changes. You will learn how to control this by specifying *dependencies.* 3. **Add cleanup if needed.** Some Effects need to specify how to stop, undo, or clean up whatever they were doing. For example, "connect" needs "disconnect", "subscribe" needs "unsubscribe", and "fetch" needs either "cancel" or "ignore". You will learn how to do this by returning a *cleanup function*. @@ -598,6 +598,33 @@ Usually, the answer is to implement the cleanup function. The cleanup function Most of the Effects you'll write will fit into one of the common patterns below. +<Pitfall> + +#### Don't use refs to prevent Effects from firing {/*dont-use-refs-to-prevent-effects-from-firing*/} + +A common pitfall for preventing Effects firing twice in development is to use a `ref` to prevent the Effect from running more than once. For example, you could "fix" the above bug with a `useRef`: + +```js {1,3-4} + const connectionRef = useRef(null); + useEffect(() => { + // 🚩 This wont fix the bug!!! + if (!connectionRef.current) { + connectionRef.current = createConnection(); + connectionRef.current.connect(); + } + }, []); +``` + +This makes it so you only see `"✅ Connecting..."` once in development, but it doesn't fix the bug. + +When the user navigates away, the connection still isn't closed and when they navigate back, a new connection is created. As the user navigates across the app, the connections would keep piling up, the same as it would before the "fix". + +To fix the bug, it is not enough to just make the Effect run once. The effect needs to work after re-mounting, which means the connection needs to be cleaned up like in the solution above. + +See the examples below for how to handle common patterns. + +</Pitfall> + ### Controlling non-React widgets {/*controlling-non-react-widgets*/} Sometimes you need to add UI widgets that aren't written to React. For example, let's say you're adding a map component to your page. It has a `setZoomLevel()` method, and you'd like to keep the zoom level in sync with a `zoomLevel` state variable in your React code. Your Effect would look similar to this: @@ -1573,7 +1600,7 @@ Each render's Effect has its own `ignore` variable. Initially, the `ignore` vari - Fetching `'Bob'` completes - The Effect from the `'Bob'` render **does not do anything because its `ignore` flag was set to `true`** -In addition to ignoring the result of an outdated API call, you can also use [`AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) to cancel the requests that are no longer needed. However, by itself this is not enough to protect against race conditions. More asynchronous steps could be chained after the fetch, so using an explicit flag like `ignore` is the most reliable way to fix this type of problems. +In addition to ignoring the result of an outdated API call, you can also use [`AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) to cancel the requests that are no longer needed. However, by itself this is not enough to protect against race conditions. More asynchronous steps could be chained after the fetch, so using an explicit flag like `ignore` is the most reliable way to fix this type of problem. </Solution> diff --git a/src/content/learn/tutorial-tic-tac-toe.md b/src/content/learn/tutorial-tic-tac-toe.md index d37791456..f18ec4939 100644 --- a/src/content/learn/tutorial-tic-tac-toe.md +++ b/src/content/learn/tutorial-tic-tac-toe.md @@ -2915,4 +2915,4 @@ If you have extra time or want to practice your new React skills, here are some 1. When someone wins, highlight the three squares that caused the win (and when no one wins, display a message about the result being a draw). 1. Display the location for each move in the format (row, col) in the move history list. -Throughout this tutorial, you've touched on React concepts including elements, components, props, and state. Now that you've seen how these concepts work when building a game, check out [Thinking in React](/learn/thinking-in-react) to see how the same React concepts work when build an app's UI. +Throughout this tutorial, you've touched on React concepts including elements, components, props, and state. Now that you've seen how these concepts work when building a game, check out [Thinking in React](/learn/thinking-in-react) to see how the same React concepts work when building an app's UI. diff --git a/src/content/learn/typescript.md b/src/content/learn/typescript.md index 034ac0d46..7edf8eb6e 100644 --- a/src/content/learn/typescript.md +++ b/src/content/learn/typescript.md @@ -22,7 +22,7 @@ TypeScript is a popular way to add type definitions to JavaScript codebases. Out All [production-grade React frameworks](/learn/start-a-new-react-project#production-grade-react-frameworks) offer support for using TypeScript. Follow the framework specific guide for installation: -- [Next.js](https://nextjs.org/docs/pages/building-your-application/configuring/typescript) +- [Next.js](https://nextjs.org/docs/app/building-your-application/configuring/typescript) - [Remix](https://remix.run/docs/en/1.19.2/guides/typescript) - [Gatsby](https://www.gatsbyjs.com/docs/how-to/custom-configuration/typescript/) - [Expo](https://docs.expo.dev/guides/typescript/) @@ -137,7 +137,7 @@ The [`useState` Hook](/reference/react/useState) will re-use the value passed in const [enabled, setEnabled] = useState(false); ``` -Will assign the type of `boolean` to `enabled`, and `setEnabled` will be a function accepting either a `boolean` argument, or a function that returns a `boolean`. If you want to explicitly provide a type for the state, you can do so by providing a type argument to the `useState` call: +This will assign the type of `boolean` to `enabled`, and `setEnabled` will be a function accepting either a `boolean` argument, or a function that returns a `boolean`. If you want to explicitly provide a type for the state, you can do so by providing a type argument to the `useState` call: ```ts // Explicitly set the type to "boolean" @@ -435,7 +435,7 @@ interface ModalRendererProps { Note, that you cannot use TypeScript to describe that the children are a certain type of JSX elements, so you cannot use the type-system to describe a component which only accepts `<li>` children. -You can see all an example of both `React.ReactNode` and `React.ReactElement` with the type-checker in [this TypeScript playground](https://www.typescriptlang.org/play?#code/JYWwDg9gTgLgBAJQKYEMDG8BmUIjgIilQ3wChSB6CxYmAOmXRgDkIATJOdNJMGAZzgwAFpxAR+8YADswAVwGkZMJFEzpOjDKw4AFHGEEBvUnDhphwADZsi0gFw0mDWjqQBuUgF9yaCNMlENzgAXjgACjADfkctFnYkfQhDAEpQgD44AB42YAA3dKMo5P46C2tbJGkvLIpcgt9-QLi3AEEwMFCItJDMrPTTbIQ3dKywdIB5aU4kKyQQKpha8drhhIGzLLWODbNs3b3s8YAxKBQAcwXpAThMaGWDvbH0gFloGbmrgQfBzYpd1YjQZbEYARkB6zMwO2SHSAAlZlYIBCdtCRkZpHIrFYahQYQD8UYYFA5EhcfjyGYqHAXnJAsIUHlOOUbHYhMIIHJzsI0Qk4P9SLUBuRqXEXEwAKKfRZcNA8PiCfxWACecAAUgBlAAacFm80W-CU11U6h4TgwUv11yShjgJjMLMqDnN9Dilq+nh8pD8AXgCHdMrCkWisVoAet0R6fXqhWKhjKllZVVxMcavpd4Zg7U6Qaj+2hmdG4zeRF10uu-Aeq0LBfLMEe-V+T2L7zLVu+FBWLdLeq+lc7DYFf39deFVOotMCACNOCh1dq219a+30uC8YWoZsRyuEdjkevR8uvoVMdjyTWt4WiSSydXD4NqZP4AymeZE072ZzuUeZQKheQgA). +You can see an example of both `React.ReactNode` and `React.ReactElement` with the type-checker in [this TypeScript playground](https://www.typescriptlang.org/play?#code/JYWwDg9gTgLgBAJQKYEMDG8BmUIjgIilQ3wChSB6CxYmAOmXRgDkIATJOdNJMGAZzgwAFpxAR+8YADswAVwGkZMJFEzpOjDKw4AFHGEEBvUnDhphwADZsi0gFw0mDWjqQBuUgF9yaCNMlENzgAXjgACjADfkctFnYkfQhDAEpQgD44AB42YAA3dKMo5P46C2tbJGkvLIpcgt9-QLi3AEEwMFCItJDMrPTTbIQ3dKywdIB5aU4kKyQQKpha8drhhIGzLLWODbNs3b3s8YAxKBQAcwXpAThMaGWDvbH0gFloGbmrgQfBzYpd1YjQZbEYARkB6zMwO2SHSAAlZlYIBCdtCRkZpHIrFYahQYQD8UYYFA5EhcfjyGYqHAXnJAsIUHlOOUbHYhMIIHJzsI0Qk4P9SLUBuRqXEXEwAKKfRZcNA8PiCfxWACecAAUgBlAAacFm80W-CU11U6h4TgwUv11yShjgJjMLMqDnN9Dilq+nh8pD8AXgCHdMrCkWisVoAet0R6fXqhWKhjKllZVVxMcavpd4Zg7U6Qaj+2hmdG4zeRF10uu-Aeq0LBfLMEe-V+T2L7zLVu+FBWLdLeq+lc7DYFf39deFVOotMCACNOCh1dq219a+30uC8YWoZsRyuEdjkevR8uvoVMdjyTWt4WiSSydXD4NqZP4AymeZE072ZzuUeZQKheQgA). ### Style Props {/*typing-style-props*/} @@ -456,7 +456,7 @@ We recommend the following resources: - [The TypeScript handbook](https://www.typescriptlang.org/docs/handbook/) is the official documentation for TypeScript, and covers most key language features. - - [The TypeScript release notes](https://devblogs.microsoft.com/typescript/) covers a each new features in-depth. + - [The TypeScript release notes](https://devblogs.microsoft.com/typescript/) cover new features in depth. - [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. diff --git a/src/content/learn/you-might-not-need-an-effect.md b/src/content/learn/you-might-not-need-an-effect.md index 66cdc3117..27031720d 100644 --- a/src/content/learn/you-might-not-need-an-effect.md +++ b/src/content/learn/you-might-not-need-an-effect.md @@ -408,9 +408,9 @@ function Game() { There are two problems with this code. -One problem is that it is very inefficient: the component (and its children) have to re-render between each `set` call in the chain. In the example above, in the worst case (`setCard` → render → `setGoldCardCount` → render → `setRound` → render → `setIsGameOver` → render) there are three unnecessary re-renders of the tree below. +First problem is that it is very inefficient: the component (and its children) have to re-render between each `set` call in the chain. In the example above, in the worst case (`setCard` → render → `setGoldCardCount` → render → `setRound` → render → `setIsGameOver` → render) there are three unnecessary re-renders of the tree below. -Even if it weren't slow, as your code evolves, you will run into cases where the "chain" you wrote doesn't fit the new requirements. Imagine you are adding a way to step through the history of the game moves. You'd do it by updating each state variable to a value from the past. However, setting the `card` state to a value from the past would trigger the Effect chain again and change the data you're showing. Such code is often rigid and fragile. +The second problem is that even if it weren't slow, as your code evolves, you will run into cases where the "chain" you wrote doesn't fit the new requirements. Imagine you are adding a way to step through the history of the game moves. You'd do it by updating each state variable to a value from the past. However, setting the `card` state to a value from the past would trigger the Effect chain again and change the data you're showing. Such code is often rigid and fragile. In this case, it's better to calculate what you can during rendering, and adjust the state in the event handler: diff --git a/src/content/reference/react-dom/client/createRoot.md b/src/content/reference/react-dom/client/createRoot.md index afddb4177..b336b6e5e 100644 --- a/src/content/reference/react-dom/client/createRoot.md +++ b/src/content/reference/react-dom/client/createRoot.md @@ -45,7 +45,9 @@ An app fully built with React will usually only have one `createRoot` call for i * **optional** `options`: An object with options for this React root. - * **optional** `onRecoverableError`: Callback called when React automatically recovers from errors. + * <CanaryBadge title="This feature is only available in the Canary channel" /> **optional** `onCaughtError`: Callback called when React catches an error in an Error Boundary. Called with the `error` caught by the Error Boundary, and an `errorInfo` object containing the `componentStack`. + * <CanaryBadge title="This feature is only available in the Canary channel" /> **optional** `onUncaughtError`: Callback called when an error is thrown and not caught by an Error Boundary. Called with the `error` that was thrown, and an `errorInfo` object containing the `componentStack`. + * **optional** `onRecoverableError`: Callback called when React automatically recovers from errors. Called with an `error` React throws, and an `errorInfo` object containing the `componentStack`. Some recoverable errors may include the original error cause as `error.cause`. * **optional** `identifierPrefix`: A string prefix React uses for IDs generated by [`useId`.](/reference/react/useId) Useful to avoid conflicts when using multiple roots on the same page. #### Returns {/*returns*/} @@ -342,6 +344,797 @@ export default function App({counter}) { It is uncommon to call `render` multiple times. Usually, your components will [update state](/reference/react/useState) instead. +### Show a dialog for uncaught errors {/*show-a-dialog-for-uncaught-errors*/} + +<Canary> + +`onUncaughtError` is only available in the latest React Canary release. + +</Canary> + +By default, React will log all uncaught errors to the console. To implement your own error reporting, you can provide the optional `onUncaughtError` root option: + +```js [[1, 6, "onUncaughtError"], [2, 6, "error", 1], [3, 6, "errorInfo"], [4, 10, "componentStack"]] +import { createRoot } from 'react-dom/client'; + +const root = createRoot( + document.getElementById('root'), + { + onUncaughtError: (error, errorInfo) => { + console.error( + 'Uncaught error', + error, + errorInfo.componentStack + ); + } + } +); +root.render(<App />); +``` + +The <CodeStep step={1}>onUncaughtError</CodeStep> option is a function called with two arguments: + +1. The <CodeStep step={2}>error</CodeStep> that was thrown. +2. An <CodeStep step={3}>errorInfo</CodeStep> object that contains the <CodeStep step={4}>componentStack</CodeStep> of the error. + +You can use the `onUncaughtError` root option to display error dialogs: + +<Sandpack> + +```html index.html hidden +<!DOCTYPE html> +<html> +<head> + <title>My app + + + + + +
    + + +``` + +```css src/styles.css active +label, button { display: block; margin-bottom: 20px; } +html, body { min-height: 300px; } + +#error-dialog { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + background-color: white; + padding: 15px; + opacity: 0.9; + text-wrap: wrap; + overflow: scroll; +} + +.text-red { + color: red; +} + +.-mb-20 { + margin-bottom: -20px; +} + +.mb-0 { + margin-bottom: 0; +} + +.mb-10 { + margin-bottom: 10px; +} + +pre { + text-wrap: wrap; +} + +pre.nowrap { + text-wrap: nowrap; +} + +.hidden { + display: none; +} +``` + +```js src/reportError.js hidden +function reportError({ title, error, componentStack, dismissable }) { + const errorDialog = document.getElementById("error-dialog"); + const errorTitle = document.getElementById("error-title"); + const errorMessage = document.getElementById("error-message"); + const errorBody = document.getElementById("error-body"); + const errorComponentStack = document.getElementById("error-component-stack"); + const errorStack = document.getElementById("error-stack"); + const errorClose = document.getElementById("error-close"); + const errorCause = document.getElementById("error-cause"); + const errorCauseMessage = document.getElementById("error-cause-message"); + const errorCauseStack = document.getElementById("error-cause-stack"); + const errorNotDismissible = document.getElementById("error-not-dismissible"); + + // Set the title + errorTitle.innerText = title; + + // Display error message and body + const [heading, body] = error.message.split(/\n(.*)/s); + errorMessage.innerText = heading; + if (body) { + errorBody.innerText = body; + } else { + errorBody.innerText = ''; + } + + // Display component stack + errorComponentStack.innerText = componentStack; + + // Display the call stack + // Since we already displayed the message, strip it, and the first Error: line. + errorStack.innerText = error.stack.replace(error.message, '').split(/\n(.*)/s)[1]; + + // Display the cause, if available + if (error.cause) { + errorCauseMessage.innerText = error.cause.message; + errorCauseStack.innerText = error.cause.stack; + errorCause.classList.remove('hidden'); + } else { + errorCause.classList.add('hidden'); + } + // Display the close button, if dismissible + if (dismissable) { + errorNotDismissible.classList.add('hidden'); + errorClose.classList.remove("hidden"); + } else { + errorNotDismissible.classList.remove('hidden'); + errorClose.classList.add("hidden"); + } + + // Show the dialog + errorDialog.classList.remove("hidden"); +} + +export function reportCaughtError({error, cause, componentStack}) { + reportError({ title: "Caught Error", error, componentStack, dismissable: true}); +} + +export function reportUncaughtError({error, cause, componentStack}) { + reportError({ title: "Uncaught Error", error, componentStack, dismissable: false }); +} + +export function reportRecoverableError({error, cause, componentStack}) { + reportError({ title: "Recoverable Error", error, componentStack, dismissable: true }); +} +``` + +```js src/index.js active +import { createRoot } from "react-dom/client"; +import App from "./App.js"; +import {reportUncaughtError} from "./reportError"; +import "./styles.css"; + +const container = document.getElementById("root"); +const root = createRoot(container, { + onUncaughtError: (error, errorInfo) => { + if (error.message !== 'Known error') { + reportUncaughtError({ + error, + componentStack: errorInfo.componentStack + }); + } + } +}); +root.render(); +``` + +```js src/App.js +import { useState } from 'react'; + +export default function App() { + const [throwError, setThrowError] = useState(false); + + if (throwError) { + foo.bar = 'baz'; + } + + return ( +
    + This error shows the error dialog: + +
    + ); +} +``` + +```json package.json hidden +{ + "dependencies": { + "react": "canary", + "react-dom": "canary", + "react-scripts": "^5.0.0" + }, + "main": "/index.js" +} +``` + + + + +### Displaying Error Boundary errors {/*displaying-error-boundary-errors*/} + + + +`onCaughtError` is only available in the latest React Canary release. + + + +By default, React will log all errors caught by an Error Boundary to `console.error`. To override this behavior, you can provide the optional `onCaughtError` root option to handle errors caught by an [Error Boundary](/reference/react/Component#catching-rendering-errors-with-an-error-boundary): + +```js [[1, 6, "onCaughtError"], [2, 6, "error", 1], [3, 6, "errorInfo"], [4, 10, "componentStack"]] +import { createRoot } from 'react-dom/client'; + +const root = createRoot( + document.getElementById('root'), + { + onCaughtError: (error, errorInfo) => { + console.error( + 'Caught error', + error, + errorInfo.componentStack + ); + } + } +); +root.render(); +``` + +The onCaughtError option is a function called with two arguments: + +1. The error that was caught by the boundary. +2. An errorInfo object that contains the componentStack of the error. + +You can use the `onCaughtError` root option to display error dialogs or filter known errors from logging: + + + +```html index.html hidden + + + + My app + + + + + +
    + + +``` + +```css src/styles.css active +label, button { display: block; margin-bottom: 20px; } +html, body { min-height: 300px; } + +#error-dialog { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + background-color: white; + padding: 15px; + opacity: 0.9; + text-wrap: wrap; + overflow: scroll; +} + +.text-red { + color: red; +} + +.-mb-20 { + margin-bottom: -20px; +} + +.mb-0 { + margin-bottom: 0; +} + +.mb-10 { + margin-bottom: 10px; +} + +pre { + text-wrap: wrap; +} + +pre.nowrap { + text-wrap: nowrap; +} + +.hidden { + display: none; +} +``` + +```js src/reportError.js hidden +function reportError({ title, error, componentStack, dismissable }) { + const errorDialog = document.getElementById("error-dialog"); + const errorTitle = document.getElementById("error-title"); + const errorMessage = document.getElementById("error-message"); + const errorBody = document.getElementById("error-body"); + const errorComponentStack = document.getElementById("error-component-stack"); + const errorStack = document.getElementById("error-stack"); + const errorClose = document.getElementById("error-close"); + const errorCause = document.getElementById("error-cause"); + const errorCauseMessage = document.getElementById("error-cause-message"); + const errorCauseStack = document.getElementById("error-cause-stack"); + const errorNotDismissible = document.getElementById("error-not-dismissible"); + + // Set the title + errorTitle.innerText = title; + + // Display error message and body + const [heading, body] = error.message.split(/\n(.*)/s); + errorMessage.innerText = heading; + if (body) { + errorBody.innerText = body; + } else { + errorBody.innerText = ''; + } + + // Display component stack + errorComponentStack.innerText = componentStack; + + // Display the call stack + // Since we already displayed the message, strip it, and the first Error: line. + errorStack.innerText = error.stack.replace(error.message, '').split(/\n(.*)/s)[1]; + + // Display the cause, if available + if (error.cause) { + errorCauseMessage.innerText = error.cause.message; + errorCauseStack.innerText = error.cause.stack; + errorCause.classList.remove('hidden'); + } else { + errorCause.classList.add('hidden'); + } + // Display the close button, if dismissible + if (dismissable) { + errorNotDismissible.classList.add('hidden'); + errorClose.classList.remove("hidden"); + } else { + errorNotDismissible.classList.remove('hidden'); + errorClose.classList.add("hidden"); + } + + // Show the dialog + errorDialog.classList.remove("hidden"); +} + +export function reportCaughtError({error, cause, componentStack}) { + reportError({ title: "Caught Error", error, componentStack, dismissable: true}); +} + +export function reportUncaughtError({error, cause, componentStack}) { + reportError({ title: "Uncaught Error", error, componentStack, dismissable: false }); +} + +export function reportRecoverableError({error, cause, componentStack}) { + reportError({ title: "Recoverable Error", error, componentStack, dismissable: true }); +} +``` + +```js src/index.js active +import { createRoot } from "react-dom/client"; +import App from "./App.js"; +import {reportCaughtError} from "./reportError"; +import "./styles.css"; + +const container = document.getElementById("root"); +const root = createRoot(container, { + onCaughtError: (error, errorInfo) => { + if (error.message !== 'Known error') { + reportCaughtError({ + error, + componentStack: errorInfo.componentStack, + }); + } + } +}); +root.render(); +``` + +```js src/App.js +import { useState } from 'react'; +import { ErrorBoundary } from "react-error-boundary"; + +export default function App() { + const [error, setError] = useState(null); + + function handleUnknown() { + setError("unknown"); + } + + function handleKnown() { + setError("known"); + } + + return ( + <> + { + setError(null); + }} + > + {error != null && } + This error will not show the error dialog: + + This error will show the error dialog: + + + + + ); +} + +function fallbackRender({ resetErrorBoundary }) { + return ( +
    +

    Error Boundary

    +

    Something went wrong.

    + +
    + ); +} + +function Throw({error}) { + if (error === "known") { + throw new Error('Known error') + } else { + foo.bar = 'baz'; + } +} +``` + +```json package.json hidden +{ + "dependencies": { + "react": "canary", + "react-dom": "canary", + "react-scripts": "^5.0.0", + "react-error-boundary": "4.0.3" + }, + "main": "/index.js" +} +``` + +
    + +### Displaying a dialog for recoverable errors {/*displaying-a-dialog-for-recoverable-errors*/} + +React may automatically render a component a second time to attempt to recover from an error thrown in render. If successful, React will log a recoverable error to the console to notify the developer. To override this behavior, you can provide the optional `onRecoverableError` root option: + +```js [[1, 6, "onRecoverableError"], [2, 6, "error", 1], [3, 10, "error.cause"], [4, 6, "errorInfo"], [5, 11, "componentStack"]] +import { createRoot } from 'react-dom/client'; + +const root = createRoot( + document.getElementById('root'), + { + onRecoverableError: (error, errorInfo) => { + console.error( + 'Recoverable error', + error, + error.cause, + errorInfo.componentStack, + ); + } + } +); +root.render(); +``` + +The onRecoverableError option is a function called with two arguments: + +1. The error that React throws. Some errors may include the original cause as error.cause. +2. An errorInfo object that contains the componentStack of the error. + +You can use the `onRecoverableError` root option to display error dialogs: + + + +```html index.html hidden + + + + My app + + + + + +
    + + +``` + +```css src/styles.css active +label, button { display: block; margin-bottom: 20px; } +html, body { min-height: 300px; } + +#error-dialog { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + background-color: white; + padding: 15px; + opacity: 0.9; + text-wrap: wrap; + overflow: scroll; +} + +.text-red { + color: red; +} + +.-mb-20 { + margin-bottom: -20px; +} + +.mb-0 { + margin-bottom: 0; +} + +.mb-10 { + margin-bottom: 10px; +} + +pre { + text-wrap: wrap; +} + +pre.nowrap { + text-wrap: nowrap; +} + +.hidden { + display: none; +} +``` + +```js src/reportError.js hidden +function reportError({ title, error, componentStack, dismissable }) { + const errorDialog = document.getElementById("error-dialog"); + const errorTitle = document.getElementById("error-title"); + const errorMessage = document.getElementById("error-message"); + const errorBody = document.getElementById("error-body"); + const errorComponentStack = document.getElementById("error-component-stack"); + const errorStack = document.getElementById("error-stack"); + const errorClose = document.getElementById("error-close"); + const errorCause = document.getElementById("error-cause"); + const errorCauseMessage = document.getElementById("error-cause-message"); + const errorCauseStack = document.getElementById("error-cause-stack"); + const errorNotDismissible = document.getElementById("error-not-dismissible"); + + // Set the title + errorTitle.innerText = title; + + // Display error message and body + const [heading, body] = error.message.split(/\n(.*)/s); + errorMessage.innerText = heading; + if (body) { + errorBody.innerText = body; + } else { + errorBody.innerText = ''; + } + + // Display component stack + errorComponentStack.innerText = componentStack; + + // Display the call stack + // Since we already displayed the message, strip it, and the first Error: line. + errorStack.innerText = error.stack.replace(error.message, '').split(/\n(.*)/s)[1]; + + // Display the cause, if available + if (error.cause) { + errorCauseMessage.innerText = error.cause.message; + errorCauseStack.innerText = error.cause.stack; + errorCause.classList.remove('hidden'); + } else { + errorCause.classList.add('hidden'); + } + // Display the close button, if dismissible + if (dismissable) { + errorNotDismissible.classList.add('hidden'); + errorClose.classList.remove("hidden"); + } else { + errorNotDismissible.classList.remove('hidden'); + errorClose.classList.add("hidden"); + } + + // Show the dialog + errorDialog.classList.remove("hidden"); +} + +export function reportCaughtError({error, cause, componentStack}) { + reportError({ title: "Caught Error", error, componentStack, dismissable: true}); +} + +export function reportUncaughtError({error, cause, componentStack}) { + reportError({ title: "Uncaught Error", error, componentStack, dismissable: false }); +} + +export function reportRecoverableError({error, cause, componentStack}) { + reportError({ title: "Recoverable Error", error, componentStack, dismissable: true }); +} +``` + +```js src/index.js active +import { createRoot } from "react-dom/client"; +import App from "./App.js"; +import {reportRecoverableError} from "./reportError"; +import "./styles.css"; + +const container = document.getElementById("root"); +const root = createRoot(container, { + onRecoverableError: (error, errorInfo) => { + reportRecoverableError({ + error, + cause: error.cause, + componentStack: errorInfo.componentStack, + }); + } +}); +root.render(); +``` + +```js src/App.js +import { useState } from 'react'; +import { ErrorBoundary } from "react-error-boundary"; + +// 🚩 Bug: Never do this. This will force an error. +let errorThrown = false; +export default function App() { + return ( + <> + + {!errorThrown && } +

    This component threw an error, but recovered during a second render.

    +

    Since it recovered, no Error Boundary was shown, but onRecoverableError was used to show an error dialog.

    +
    + + + ); +} + +function fallbackRender() { + return ( +
    +

    Error Boundary

    +

    Something went wrong.

    +
    + ); +} + +function Throw({error}) { + // Simulate an external value changing during concurrent render. + errorThrown = true; + foo.bar = 'baz'; +} +``` + +```json package.json hidden +{ + "dependencies": { + "react": "canary", + "react-dom": "canary", + "react-scripts": "^5.0.0", + "react-error-boundary": "4.0.3" + }, + "main": "/index.js" +} +``` + +
    + + --- ## Troubleshooting {/*troubleshooting*/} @@ -361,6 +1154,28 @@ Until you do that, nothing is displayed. --- +### I'm getting an error: "You passed a second argument to root.render" {/*im-getting-an-error-you-passed-a-second-argument-to-root-render*/} + +A common mistake is to pass the options for `createRoot` to `root.render(...)`: + + + +Warning: You passed a second argument to root.render(...) but it only accepts one argument. + + + +To fix, pass the root options to `createRoot(...)`, not `root.render(...)`: +```js {2,5} +// 🚩 Wrong: root.render only takes one argument. +root.render(App, {onUncaughtError}); + +// ✅ Correct: pass options to createRoot. +const root = createRoot(container, {onUncaughtError}); +root.render(); +``` + +--- + ### I'm getting an error: "Target container is not a DOM element" {/*im-getting-an-error-target-container-is-not-a-dom-element*/} This error means that whatever you're passing to `createRoot` is not a DOM node. diff --git a/src/content/reference/react-dom/client/hydrateRoot.md b/src/content/reference/react-dom/client/hydrateRoot.md index c137cdda9..cc30ce22c 100644 --- a/src/content/reference/react-dom/client/hydrateRoot.md +++ b/src/content/reference/react-dom/client/hydrateRoot.md @@ -41,7 +41,9 @@ React will attach to the HTML that exists inside the `domNode`, and take over ma * **optional** `options`: An object with options for this React root. - * **optional** `onRecoverableError`: Callback called when React automatically recovers from errors. + * **optional** `onCaughtError`: Callback called when React catches an error in an Error Boundary. Called with the `error` caught by the Error Boundary, and an `errorInfo` object containing the `componentStack`. + * **optional** `onUncaughtError`: Callback called when an error is thrown and not caught by an Error Boundary. Called with the `error` that was thrown and an `errorInfo` object containing the `componentStack`. + * **optional** `onRecoverableError`: Callback called when React automatically recovers from errors. Called with the `error` React throws, and an `errorInfo` object containing the `componentStack`. Some recoverable errors may include the original error cause as `error.cause`. * **optional** `identifierPrefix`: A string prefix React uses for IDs generated by [`useId`.](/reference/react/useId) Useful to avoid conflicts when using multiple roots on the same page. Must be the same prefix as used on the server. @@ -371,3 +373,826 @@ export default function App({counter}) { It is uncommon to call [`root.render`](#root-render) on a hydrated root. Usually, you'll [update state](/reference/react/useState) inside one of the components instead. + +### Show a dialog for uncaught errors {/*show-a-dialog-for-uncaught-errors*/} + + + +`onUncaughtError` is only available in the latest React Canary release. + + + +By default, React will log all uncaught errors to the console. To implement your own error reporting, you can provide the optional `onUncaughtError` root option: + +```js [[1, 7, "onUncaughtError"], [2, 7, "error", 1], [3, 7, "errorInfo"], [4, 11, "componentStack"]] +import { hydrateRoot } from 'react-dom/client'; + +const root = hydrateRoot( + document.getElementById('root'), + , + { + onUncaughtError: (error, errorInfo) => { + console.error( + 'Uncaught error', + error, + errorInfo.componentStack + ); + } + } +); +root.render(); +``` + +The onUncaughtError option is a function called with two arguments: + +1. The error that was thrown. +2. An errorInfo object that contains the componentStack of the error. + +You can use the `onUncaughtError` root option to display error dialogs: + + + +```html index.html hidden + + + + My app + + + + + +
    This error shows the error dialog:
    + + +``` + +```css src/styles.css active +label, button { display: block; margin-bottom: 20px; } +html, body { min-height: 300px; } + +#error-dialog { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + background-color: white; + padding: 15px; + opacity: 0.9; + text-wrap: wrap; + overflow: scroll; +} + +.text-red { + color: red; +} + +.-mb-20 { + margin-bottom: -20px; +} + +.mb-0 { + margin-bottom: 0; +} + +.mb-10 { + margin-bottom: 10px; +} + +pre { + text-wrap: wrap; +} + +pre.nowrap { + text-wrap: nowrap; +} + +.hidden { + display: none; +} +``` + +```js src/reportError.js hidden +function reportError({ title, error, componentStack, dismissable }) { + const errorDialog = document.getElementById("error-dialog"); + const errorTitle = document.getElementById("error-title"); + const errorMessage = document.getElementById("error-message"); + const errorBody = document.getElementById("error-body"); + const errorComponentStack = document.getElementById("error-component-stack"); + const errorStack = document.getElementById("error-stack"); + const errorClose = document.getElementById("error-close"); + const errorCause = document.getElementById("error-cause"); + const errorCauseMessage = document.getElementById("error-cause-message"); + const errorCauseStack = document.getElementById("error-cause-stack"); + const errorNotDismissible = document.getElementById("error-not-dismissible"); + + // Set the title + errorTitle.innerText = title; + + // Display error message and body + const [heading, body] = error.message.split(/\n(.*)/s); + errorMessage.innerText = heading; + if (body) { + errorBody.innerText = body; + } else { + errorBody.innerText = ''; + } + + // Display component stack + errorComponentStack.innerText = componentStack; + + // Display the call stack + // Since we already displayed the message, strip it, and the first Error: line. + errorStack.innerText = error.stack.replace(error.message, '').split(/\n(.*)/s)[1]; + + // Display the cause, if available + if (error.cause) { + errorCauseMessage.innerText = error.cause.message; + errorCauseStack.innerText = error.cause.stack; + errorCause.classList.remove('hidden'); + } else { + errorCause.classList.add('hidden'); + } + // Display the close button, if dismissible + if (dismissable) { + errorNotDismissible.classList.add('hidden'); + errorClose.classList.remove("hidden"); + } else { + errorNotDismissible.classList.remove('hidden'); + errorClose.classList.add("hidden"); + } + + // Show the dialog + errorDialog.classList.remove("hidden"); +} + +export function reportCaughtError({error, cause, componentStack}) { + reportError({ title: "Caught Error", error, componentStack, dismissable: true}); +} + +export function reportUncaughtError({error, cause, componentStack}) { + reportError({ title: "Uncaught Error", error, componentStack, dismissable: false }); +} + +export function reportRecoverableError({error, cause, componentStack}) { + reportError({ title: "Recoverable Error", error, componentStack, dismissable: true }); +} +``` + +```js src/index.js active +import { hydrateRoot } from "react-dom/client"; +import App from "./App.js"; +import {reportUncaughtError} from "./reportError"; +import "./styles.css"; +import {renderToString} from 'react-dom/server'; + +const container = document.getElementById("root"); +const root = hydrateRoot(container, , { + onUncaughtError: (error, errorInfo) => { + if (error.message !== 'Known error') { + reportUncaughtError({ + error, + componentStack: errorInfo.componentStack + }); + } + } +}); +``` + +```js src/App.js +import { useState } from 'react'; + +export default function App() { + const [throwError, setThrowError] = useState(false); + + if (throwError) { + foo.bar = 'baz'; + } + + return ( +
    + This error shows the error dialog: + +
    + ); +} +``` + +```json package.json hidden +{ + "dependencies": { + "react": "canary", + "react-dom": "canary", + "react-scripts": "^5.0.0" + }, + "main": "/index.js" +} +``` + +
    + + +### Displaying Error Boundary errors {/*displaying-error-boundary-errors*/} + + + +`onCaughtError` is only available in the latest React Canary release. + + + +By default, React will log all errors caught by an Error Boundary to `console.error`. To override this behavior, you can provide the optional `onCaughtError` root option for errors caught by an [Error Boundary](/reference/react/Component#catching-rendering-errors-with-an-error-boundary): + +```js [[1, 7, "onCaughtError"], [2, 7, "error", 1], [3, 7, "errorInfo"], [4, 11, "componentStack"]] +import { hydrateRoot } from 'react-dom/client'; + +const root = hydrateRoot( + document.getElementById('root'), + , + { + onCaughtError: (error, errorInfo) => { + console.error( + 'Caught error', + error, + errorInfo.componentStack + ); + } + } +); +root.render(); +``` + +The onCaughtError option is a function called with two arguments: + +1. The error that was caught by the boundary. +2. An errorInfo object that contains the componentStack of the error. + +You can use the `onCaughtError` root option to display error dialogs or filter known errors from logging: + + + +```html index.html hidden + + + + My app + + + + + +
    This error will not show the error dialog:This error will show the error dialog:
    + + +``` + +```css src/styles.css active +label, button { display: block; margin-bottom: 20px; } +html, body { min-height: 300px; } + +#error-dialog { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + background-color: white; + padding: 15px; + opacity: 0.9; + text-wrap: wrap; + overflow: scroll; +} + +.text-red { + color: red; +} + +.-mb-20 { + margin-bottom: -20px; +} + +.mb-0 { + margin-bottom: 0; +} + +.mb-10 { + margin-bottom: 10px; +} + +pre { + text-wrap: wrap; +} + +pre.nowrap { + text-wrap: nowrap; +} + +.hidden { + display: none; +} +``` + +```js src/reportError.js hidden +function reportError({ title, error, componentStack, dismissable }) { + const errorDialog = document.getElementById("error-dialog"); + const errorTitle = document.getElementById("error-title"); + const errorMessage = document.getElementById("error-message"); + const errorBody = document.getElementById("error-body"); + const errorComponentStack = document.getElementById("error-component-stack"); + const errorStack = document.getElementById("error-stack"); + const errorClose = document.getElementById("error-close"); + const errorCause = document.getElementById("error-cause"); + const errorCauseMessage = document.getElementById("error-cause-message"); + const errorCauseStack = document.getElementById("error-cause-stack"); + const errorNotDismissible = document.getElementById("error-not-dismissible"); + + // Set the title + errorTitle.innerText = title; + + // Display error message and body + const [heading, body] = error.message.split(/\n(.*)/s); + errorMessage.innerText = heading; + if (body) { + errorBody.innerText = body; + } else { + errorBody.innerText = ''; + } + + // Display component stack + errorComponentStack.innerText = componentStack; + + // Display the call stack + // Since we already displayed the message, strip it, and the first Error: line. + errorStack.innerText = error.stack.replace(error.message, '').split(/\n(.*)/s)[1]; + + // Display the cause, if available + if (error.cause) { + errorCauseMessage.innerText = error.cause.message; + errorCauseStack.innerText = error.cause.stack; + errorCause.classList.remove('hidden'); + } else { + errorCause.classList.add('hidden'); + } + // Display the close button, if dismissible + if (dismissable) { + errorNotDismissible.classList.add('hidden'); + errorClose.classList.remove("hidden"); + } else { + errorNotDismissible.classList.remove('hidden'); + errorClose.classList.add("hidden"); + } + + // Show the dialog + errorDialog.classList.remove("hidden"); +} + +export function reportCaughtError({error, cause, componentStack}) { + reportError({ title: "Caught Error", error, componentStack, dismissable: true}); +} + +export function reportUncaughtError({error, cause, componentStack}) { + reportError({ title: "Uncaught Error", error, componentStack, dismissable: false }); +} + +export function reportRecoverableError({error, cause, componentStack}) { + reportError({ title: "Recoverable Error", error, componentStack, dismissable: true }); +} +``` + +```js src/index.js active +import { hydrateRoot } from "react-dom/client"; +import App from "./App.js"; +import {reportCaughtError} from "./reportError"; +import "./styles.css"; + +const container = document.getElementById("root"); +const root = hydrateRoot(container, , { + onCaughtError: (error, errorInfo) => { + if (error.message !== 'Known error') { + reportCaughtError({ + error, + componentStack: errorInfo.componentStack + }); + } + } +}); +``` + +```js src/App.js +import { useState } from 'react'; +import { ErrorBoundary } from "react-error-boundary"; + +export default function App() { + const [error, setError] = useState(null); + + function handleUnknown() { + setError("unknown"); + } + + function handleKnown() { + setError("known"); + } + + return ( + <> + { + setError(null); + }} + > + {error != null && } + This error will not show the error dialog: + + This error will show the error dialog: + + + + + ); +} + +function fallbackRender({ resetErrorBoundary }) { + return ( +
    +

    Error Boundary

    +

    Something went wrong.

    + +
    + ); +} + +function Throw({error}) { + if (error === "known") { + throw new Error('Known error') + } else { + foo.bar = 'baz'; + } +} +``` + +```json package.json hidden +{ + "dependencies": { + "react": "canary", + "react-dom": "canary", + "react-scripts": "^5.0.0", + "react-error-boundary": "4.0.3" + }, + "main": "/index.js" +} +``` + +
    + +### Show a dialog for recoverable hydration mismatch errors {/*show-a-dialog-for-recoverable-hydration-mismatch-errors*/} + +When React encounters a hydration mismatch, it will automatically attempt to recover by rendering on the client. By default, React will log hydration mismatch errors to `console.error`. To override this behavior, you can provide the optional `onRecoverableError` root option: + +```js [[1, 7, "onRecoverableError"], [2, 7, "error", 1], [3, 11, "error.cause", 1], [4, 7, "errorInfo"], [5, 12, "componentStack"]] +import { hydrateRoot } from 'react-dom/client'; + +const root = hydrateRoot( + document.getElementById('root'), + , + { + onRecoverableError: (error, errorInfo) => { + console.error( + 'Caught error', + error, + error.cause, + errorInfo.componentStack + ); + } + } +); +``` + +The onRecoverableError option is a function called with two arguments: + +1. The error React throws. Some errors may include the original cause as error.cause. +2. An errorInfo object that contains the componentStack of the error. + +You can use the `onRecoverableError` root option to display error dialogs for hydration mismatches: + + + +```html index.html hidden + + + + My app + + + + + +
    Server
    + + +``` + +```css src/styles.css active +label, button { display: block; margin-bottom: 20px; } +html, body { min-height: 300px; } + +#error-dialog { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + background-color: white; + padding: 15px; + opacity: 0.9; + text-wrap: wrap; + overflow: scroll; +} + +.text-red { + color: red; +} + +.-mb-20 { + margin-bottom: -20px; +} + +.mb-0 { + margin-bottom: 0; +} + +.mb-10 { + margin-bottom: 10px; +} + +pre { + text-wrap: wrap; +} + +pre.nowrap { + text-wrap: nowrap; +} + +.hidden { + display: none; +} +``` + +```js src/reportError.js hidden +function reportError({ title, error, componentStack, dismissable }) { + const errorDialog = document.getElementById("error-dialog"); + const errorTitle = document.getElementById("error-title"); + const errorMessage = document.getElementById("error-message"); + const errorBody = document.getElementById("error-body"); + const errorComponentStack = document.getElementById("error-component-stack"); + const errorStack = document.getElementById("error-stack"); + const errorClose = document.getElementById("error-close"); + const errorCause = document.getElementById("error-cause"); + const errorCauseMessage = document.getElementById("error-cause-message"); + const errorCauseStack = document.getElementById("error-cause-stack"); + const errorNotDismissible = document.getElementById("error-not-dismissible"); + + // Set the title + errorTitle.innerText = title; + + // Display error message and body + const [heading, body] = error.message.split(/\n(.*)/s); + errorMessage.innerText = heading; + if (body) { + errorBody.innerText = body; + } else { + errorBody.innerText = ''; + } + + // Display component stack + errorComponentStack.innerText = componentStack; + + // Display the call stack + // Since we already displayed the message, strip it, and the first Error: line. + errorStack.innerText = error.stack.replace(error.message, '').split(/\n(.*)/s)[1]; + + // Display the cause, if available + if (error.cause) { + errorCauseMessage.innerText = error.cause.message; + errorCauseStack.innerText = error.cause.stack; + errorCause.classList.remove('hidden'); + } else { + errorCause.classList.add('hidden'); + } + // Display the close button, if dismissible + if (dismissable) { + errorNotDismissible.classList.add('hidden'); + errorClose.classList.remove("hidden"); + } else { + errorNotDismissible.classList.remove('hidden'); + errorClose.classList.add("hidden"); + } + + // Show the dialog + errorDialog.classList.remove("hidden"); +} + +export function reportCaughtError({error, cause, componentStack}) { + reportError({ title: "Caught Error", error, componentStack, dismissable: true}); +} + +export function reportUncaughtError({error, cause, componentStack}) { + reportError({ title: "Uncaught Error", error, componentStack, dismissable: false }); +} + +export function reportRecoverableError({error, cause, componentStack}) { + reportError({ title: "Recoverable Error", error, componentStack, dismissable: true }); +} +``` + +```js src/index.js active +import { hydrateRoot } from "react-dom/client"; +import App from "./App.js"; +import {reportRecoverableError} from "./reportError"; +import "./styles.css"; + +const container = document.getElementById("root"); +const root = hydrateRoot(container, , { + onRecoverableError: (error, errorInfo) => { + reportRecoverableError({ + error, + cause: error.cause, + componentStack: errorInfo.componentStack + }); + } +}); +``` + +```js src/App.js +import { useState } from 'react'; +import { ErrorBoundary } from "react-error-boundary"; + +export default function App() { + const [error, setError] = useState(null); + + function handleUnknown() { + setError("unknown"); + } + + function handleKnown() { + setError("known"); + } + + return ( + {typeof window !== 'undefined' ? 'Client' : 'Server'} + ); +} + +function fallbackRender({ resetErrorBoundary }) { + return ( +
    +

    Error Boundary

    +

    Something went wrong.

    + +
    + ); +} + +function Throw({error}) { + if (error === "known") { + throw new Error('Known error') + } else { + foo.bar = 'baz'; + } +} +``` + +```json package.json hidden +{ + "dependencies": { + "react": "canary", + "react-dom": "canary", + "react-scripts": "^5.0.0", + "react-error-boundary": "4.0.3" + }, + "main": "/index.js" +} +``` + +
    + +## Troubleshooting {/*troubleshooting*/} + + +### I'm getting an error: "You passed a second argument to root.render" {/*im-getting-an-error-you-passed-a-second-argument-to-root-render*/} + +A common mistake is to pass the options for `hydrateRoot` to `root.render(...)`: + + + +Warning: You passed a second argument to root.render(...) but it only accepts one argument. + + + +To fix, pass the root options to `hydrateRoot(...)`, not `root.render(...)`: +```js {2,5} +// 🚩 Wrong: root.render only takes one argument. +root.render(App, {onUncaughtError}); + +// ✅ Correct: pass options to createRoot. +const root = hydrateRoot(container, , {onUncaughtError}); +``` diff --git a/src/content/reference/react-dom/components/common.md b/src/content/reference/react-dom/components/common.md index 610742735..62ee08139 100644 --- a/src/content/reference/react-dom/components/common.md +++ b/src/content/reference/react-dom/components/common.md @@ -257,11 +257,32 @@ React will also call your `ref` callback whenever you pass a *different* `ref` c #### Parameters {/*ref-callback-parameters*/} -* `node`: A DOM node or `null`. React will pass you the DOM node when the ref gets attached, and `null` when the ref gets detached. Unless you pass the same function reference for the `ref` callback on every render, the callback will get temporarily detached and re-attached during every re-render of the component. +* `node`: A DOM node or `null`. React will pass you the DOM node when the ref gets attached, and `null` when the `ref` gets detached. Unless you pass the same function reference for the `ref` callback on every render, the callback will get temporarily detached and re-attached during every re-render of the component. + + #### Returns {/*returns*/} -Do not return anything from the `ref` callback. +* **optional** `cleanup function`: When the `ref` is detached, React will call the cleanup function. If a function is not returned by the `ref` callback, React will call the callback again with `null` as the argument when the `ref` gets detached. + +```js + +
    { + console.log(node); + + return () => { + console.log('Clean up', node) + } +}}> + +``` + +#### Caveats {/*caveats*/} + +* When Strict Mode is on, React will **run one extra development-only setup+cleanup cycle** before the first real setup. This is a stress-test that ensures that your cleanup logic "mirrors" your setup logic and that it stops or undoes whatever the setup is doing. If this causes a problem, implement the cleanup function. +* When you pass a *different* `ref` callback, React will call the *previous* callback's cleanup function if provided. If not cleanup function is defined, the `ref` callback will be called with `null` as the argument. The *next* function will be called with the DOM node. + + --- diff --git a/src/content/reference/react-dom/components/form.md b/src/content/reference/react-dom/components/form.md index 4b7d5d8a0..8f6ab00e0 100644 --- a/src/content/reference/react-dom/components/form.md +++ b/src/content/reference/react-dom/components/form.md @@ -93,7 +93,7 @@ export default function Search() { ### 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. +Render a `` with an input and submit button. Pass a Server Action (a function marked with [`'use server'`](/reference/rsc/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. @@ -137,7 +137,7 @@ function AddToCart({productId}) { } ``` -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). +When `` is rendered by a [Server Component](/reference/rsc/use-client), and a [Server Action](/reference/rsc/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. @@ -322,16 +322,16 @@ export default function Search() { 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 +1. `` be rendered by a [Server Component](/reference/rsc/use-client) +1. the function passed to the ``'s `action` prop be a [Server Action](/reference/rsc/use-server) +1. the `useActionState` 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. +`useActionState` takes two parameters: a [Server Action](/reference/rsc/use-server) and an initial state. `useActionState` returns two values, a state variable and an action. The action returned by `useActionState` should be passed to the `action` prop of the form. The state variable returned by `useActionState` can be used to displayed an error message. The value returned by the [Server Action](/reference/rsc/use-server) passed to `useActionState` will be used to update the state variable. ```js src/App.js -import { useFormState } from "react-dom"; +import { useActionState } from "react"; import { signUpNewUser } from "./api"; export default function Page() { @@ -345,12 +345,12 @@ export default function Page() { return err.toString(); } } - const [message, formAction] = useFormState(signup, null); + const [message, signupAction] = useActionState(signup, null); return ( <>

    Signup for my newsletter

    Signup with the same email twice to see an error

    - + @@ -375,8 +375,8 @@ export async function signUpNewUser(newEmail) { ```json package.json hidden { "dependencies": { - "react": "18.3.0-canary-6db7f4209-20231021", - "react-dom": "18.3.0-canary-6db7f4209-20231021", + "react": "canary", + "react-dom": "canary", "react-scripts": "^5.0.0" }, "main": "/index.js", @@ -386,7 +386,7 @@ export async function signUpNewUser(newEmail) {
    -Learn more about updating state from a form action with the [`useFormState`](/reference/react-dom/hooks/useFormState) docs +Learn more about updating state from a form action with the [`useActionState`](/reference/react/useActionState) docs ### Handling multiple submission types {/*handling-multiple-submission-types*/} diff --git a/src/content/reference/react-dom/components/link.md b/src/content/reference/react-dom/components/link.md index fcbb9fab0..730d9e995 100644 --- a/src/content/reference/react-dom/components/link.md +++ b/src/content/reference/react-dom/components/link.md @@ -43,13 +43,13 @@ To link to external resources such as stylesheets, fonts, and icons, or to annot These props apply when `rel="stylesheet"`: -* `precedence`: a string. Tells React where to rank the `` DOM node relative to others in the document ``, which determines which stylesheet can override the other. Its value can be (in order of precedence) `"reset"`, `"low"`, `"medium"`, `"high"`. Stylesheets with the same precedence go together whether they are `` or inline `