From 6c95de96f75c89515ceb677e4c696e34cca77f4e Mon Sep 17 00:00:00 2001 From: Sha Mwe La <62544170+shamwela@users.noreply.github.com> Date: Tue, 1 Mar 2022 14:36:37 +0630 Subject: [PATCH 001/495] [Beta] Delete unused variables (#4379) * Delete unused variables * Change event to e * Change e to evt --- beta/src/components/Icon/IconChevron.tsx | 2 +- beta/src/components/Icon/IconNavArrow.tsx | 2 +- beta/src/components/Layout/Sidebar/Sidebar.tsx | 3 +-- beta/src/components/MDX/CodeBlock/CodeBlock.tsx | 2 -- beta/src/components/MDX/Intro.tsx | 2 +- beta/src/components/MDX/Link.tsx | 2 +- beta/src/components/MDX/MDXComponents.tsx | 2 -- beta/src/components/MDX/Sandpack/ResetButton.tsx | 6 +----- beta/src/components/MDX/Sandpack/utils.ts | 3 --- beta/src/components/useMenu.tsx | 1 - beta/src/pages/_app.tsx | 1 - beta/src/pages/_document.tsx | 2 +- 12 files changed, 7 insertions(+), 21 deletions(-) diff --git a/beta/src/components/Icon/IconChevron.tsx b/beta/src/components/Icon/IconChevron.tsx index fd060dc46..53416a0e5 100644 --- a/beta/src/components/Icon/IconChevron.tsx +++ b/beta/src/components/Icon/IconChevron.tsx @@ -9,7 +9,7 @@ export const IconChevron = React.memo< JSX.IntrinsicElements['svg'] & { displayDirection: 'up' | 'down' | 'left' | 'right'; } ->(function IconChevron({className, displayDirection, ...rest}) { +>(function IconChevron({className, displayDirection}) { const classes = cn( { 'rotate-0': displayDirection === 'down', diff --git a/beta/src/components/Icon/IconNavArrow.tsx b/beta/src/components/Icon/IconNavArrow.tsx index a78ba1dc5..4531b3c0f 100644 --- a/beta/src/components/Icon/IconNavArrow.tsx +++ b/beta/src/components/Icon/IconNavArrow.tsx @@ -9,7 +9,7 @@ export const IconNavArrow = React.memo< JSX.IntrinsicElements['svg'] & { displayDirection: 'right' | 'down' | 'left'; } ->(function IconNavArrow({displayDirection = 'right', className, ...rest}) { +>(function IconNavArrow({displayDirection = 'right', className}) { const classes = cn( 'duration-100 ease-in transition', { diff --git a/beta/src/components/Layout/Sidebar/Sidebar.tsx b/beta/src/components/Layout/Sidebar/Sidebar.tsx index b208ebbd2..f3dab4bc9 100644 --- a/beta/src/components/Layout/Sidebar/Sidebar.tsx +++ b/beta/src/components/Layout/Sidebar/Sidebar.tsx @@ -9,13 +9,12 @@ import {MenuContext} from 'components/useMenu'; import {useMediaQuery} from '../useMediaQuery'; import {SidebarRouteTree} from './SidebarRouteTree'; import {Search} from 'components/Search'; -import {Button} from 'components/Button'; import {MobileNav} from '../Nav/MobileNav'; import {Feedback} from '../Feedback'; const SIDEBAR_BREAKPOINT = 1023; -export function Sidebar({isMobileOnly}: {isMobileOnly?: boolean}) { +export function Sidebar() { const {menuRef, isOpen} = React.useContext(MenuContext); const isMobileSidebar = useMediaQuery(SIDEBAR_BREAKPOINT); let routeTree = React.useContext(SidebarContext); diff --git a/beta/src/components/MDX/CodeBlock/CodeBlock.tsx b/beta/src/components/MDX/CodeBlock/CodeBlock.tsx index d5efefd80..965bc816a 100644 --- a/beta/src/components/MDX/CodeBlock/CodeBlock.tsx +++ b/beta/src/components/MDX/CodeBlock/CodeBlock.tsx @@ -23,13 +23,11 @@ const CodeBlock = function CodeBlock({ className = 'language-js', metastring, noMargin, - noMarkers, }: { children: string; className?: string; metastring: string; noMargin?: boolean; - noMarkers?: boolean; }) { const getDecoratedLineInfo = () => { if (!metastring) { diff --git a/beta/src/components/MDX/Intro.tsx b/beta/src/components/MDX/Intro.tsx index a4e81564c..68094395a 100644 --- a/beta/src/components/MDX/Intro.tsx +++ b/beta/src/components/MDX/Intro.tsx @@ -2,7 +2,7 @@ * Copyright (c) Facebook, Inc. and its affiliates. */ -import React, {ReactNode} from 'react'; +import React from 'react'; export interface IntroProps { children?: React.ReactNode; diff --git a/beta/src/components/MDX/Link.tsx b/beta/src/components/MDX/Link.tsx index 713d6116f..ea9b84b03 100644 --- a/beta/src/components/MDX/Link.tsx +++ b/beta/src/components/MDX/Link.tsx @@ -17,7 +17,7 @@ function Link({ const classes = 'inline text-link dark:text-link-dark break-normal border-b border-link border-opacity-0 hover:border-opacity-100 duration-100 ease-in transition leading-normal'; const modifiedChildren = React.Children.toArray(children).map( - (child: any, idx: number) => { + (child: any) => { if (child.props?.mdxType && child.props?.mdxType === 'inlineCode') { return React.cloneElement(child, { isLink: true, diff --git a/beta/src/components/MDX/MDXComponents.tsx b/beta/src/components/MDX/MDXComponents.tsx index 4defe25c9..fb662fe6f 100644 --- a/beta/src/components/MDX/MDXComponents.tsx +++ b/beta/src/components/MDX/MDXComponents.tsx @@ -200,14 +200,12 @@ function Illustration({ alt, author, authorLink, - children, }: { caption: string; src: string; alt: string; author: string; authorLink: string; - children: any; }) { return (
diff --git a/beta/src/components/MDX/Sandpack/ResetButton.tsx b/beta/src/components/MDX/Sandpack/ResetButton.tsx index 6ff74a034..ba0124b73 100644 --- a/beta/src/components/MDX/Sandpack/ResetButton.tsx +++ b/beta/src/components/MDX/Sandpack/ResetButton.tsx @@ -5,14 +5,10 @@ import * as React from 'react'; import {IconRestart} from '../../Icon/IconRestart'; export interface ResetButtonProps { - clientId?: string; onReset: () => void; } -export const ResetButton: React.FC = ({ - clientId, - onReset, -}) => { +export const ResetButton: React.FC = ({onReset}) => { return ( - {isPending ? " Loading..." : null} - - -); -``` - -**[Try it on CodeSandbox](https://codesandbox.io/s/frosty-haslett-ds0h9h)** - -Now, this feels a lot better! When we click Next, it gets disabled because clicking it multiple times doesn't make sense. And the new "Loading..." tells the user that the app didn't freeze. - -### Reviewing the Changes {#reviewing-the-changes} - -Let's take another look at all the changes we've made since the [original example](https://codesandbox.io/s/nice-shadow-zvosx0): - -```js{3-5,9,11,14,19} -function App() { - const [resource, setResource] = useState(initialResource); - const [startTransition, isPending] = useTransition({ - timeoutMs: 3000 - }); - return ( - <> - - {isPending ? " Loading..." : null} - - - ); -} -``` - -**[Try it on CodeSandbox](https://codesandbox.io/s/frosty-haslett-ds0h9h)** - -It took us only seven lines of code to add this transition: - -* We've imported the `useTransition` Hook and used it in the component that updates the state. -* We've passed `{timeoutMs: 3000}` to stay on the previous screen for at most 3 seconds. -* We've wrapped our state update into `startTransition` to tell React it's okay to delay it. -* We're using `isPending` to communicate the state transition progress to the user and to disable the button. - -As a result, clicking "Next" doesn't perform an immediate state transition to an "undesirable" loading state, but instead stays on the previous screen and communicates progress there. - -### Where Does the Update Happen? {#where-does-the-update-happen} - -This wasn't very difficult to implement. However, if you start thinking about how this could possibly work, it might become a little mindbending. If we set the state, how come we don't see the result right away? *Where* is the next `` rendering? - -Clearly, both "versions" of `` exist at the same time. We know the old one exists because we see it on the screen and even display a progress indicator on it. And we know the new version also exists *somewhere*, because it's the one that we're waiting for! - -**But how can two versions of the same component exist at the same time?** - -This gets at the root of what Concurrent Mode is. We've [previously said](/docs/concurrent-mode-intro.html#intentional-loading-sequences) it's a bit like React working on state update on a "branch". Another way we can conceptualize is that wrapping a state update in `startTransition` begins rendering it *"in a different universe"*, much like in science fiction movies. We don't "see" that universe directly -- but we can get a signal from it that tells us something is happening (`isPending`). When the update is ready, our "universes" merge back together, and we see the result on the screen! - -Play a bit more with the [demo](https://codesandbox.io/s/frosty-haslett-ds0h9h), and try to imagine it happening. - -Of course, two versions of the tree rendering *at the same time* is an illusion, just like the idea that all programs run on your computer at the same time is an illusion. An operating system switches between different applications very fast. Similarly, React can switch between the version of the tree you see on the screen and the version that it's "preparing" to show next. - -An API like `useTransition` lets you focus on the desired user experience, and not think about the mechanics of how it's implemented. Still, it can be a helpful metaphor to imagine that updates wrapped in `startTransition` happen "on a branch" or "in a different world". - -### Transitions Are Everywhere {#transitions-are-everywhere} - -As we learned from the [Suspense walkthrough](/docs/concurrent-mode-suspense.html), any component can "suspend" any time if some data it needs is not ready yet. We can strategically place `` boundaries in different parts of the tree to handle this, but it won't always be enough. - -Let's get back to our [first Suspense demo](https://codesandbox.io/s/frosty-hermann-bztrp) where there was just one profile. Currently, it fetches the data only once. We'll add a "Refresh" button to check for server updates. - -Our first attempt might look like this: - -```js{6-8,13-15} -const initialResource = fetchUserAndPosts(); - -function ProfilePage() { - const [resource, setResource] = useState(initialResource); - - function handleRefreshClick() { - setResource(fetchUserAndPosts()); - } - - return ( - Loading profile...}> - - - Loading posts...}> - - - - ); -} -``` - -**[Try it on CodeSandbox](https://codesandbox.io/s/trusting-brown-6hj0m0)** - -In this example, we start data fetching at the load *and* every time you press "Refresh". We put the result of calling `fetchUserAndPosts()` into state so that components below can start reading the new data from the request we just kicked off. - -We can see in [this example](https://codesandbox.io/s/trusting-brown-6hj0m0) that pressing "Refresh" works. The `` and `` components receive a new `resource` prop that represents the fresh data, they "suspend" because we don't have a response yet, and we see the fallbacks. When the response loads, we can see the updated posts (our fake API adds them every 3 seconds). - -However, the experience feels really jarring. We were browsing a page, but it got replaced by a loading state right as we were interacting with it. It's disorienting. **Just like before, to avoid showing an undesirable loading state, we can wrap the state update in a transition:** - -```js{2-5,9-11,21} -function ProfilePage() { - const [startTransition, isPending] = useTransition({ - // Wait 10 seconds before fallback - timeoutMs: 10000 - }); - const [resource, setResource] = useState(initialResource); - - function handleRefreshClick() { - startTransition(() => { - setResource(fetchProfileData()); - }); - } - - return ( - Loading profile...}> - - - Loading posts...}> - - - - ); -} -``` - -**[Try it on CodeSandbox](https://codesandbox.io/s/zealous-mccarthy-fiiwu2)** - -This feels a lot better! Clicking "Refresh" doesn't pull us away from the page we're browsing anymore. We see something is loading "inline", and when the data is ready, it's displayed. - -### Baking Transitions Into the Design System {#baking-transitions-into-the-design-system} - -We can now see that the need for `useTransition` is *very* common. Pretty much any button click or interaction that can lead to a component suspending needs to be wrapped in `useTransition` to avoid accidentally hiding something the user is interacting with. - -This can lead to a lot of repetitive code across components. This is why **we generally recommend to bake `useTransition` into the *design system* components of your app**. For example, we can extract the transition logic into our own ` - {isPending ? spinner : null} - - ); -} -``` - -**[Try it on CodeSandbox](https://codesandbox.io/s/heuristic-cerf-8bo4rk)** - -Note that the button doesn't care *what* state we're updating. It's wrapping *any* state updates that happen during its `onClick` handler into a transition. Now that our ` - Loading posts...}> - - - - ); -} -``` - -**[Try it on CodeSandbox](https://codesandbox.io/s/heuristic-cerf-8bo4rk)** - -When a button gets clicked, it starts a transition and calls `props.onClick()` inside of it -- which triggers `handleRefreshClick` in the `` component. We start fetching the fresh data, but it doesn't trigger a fallback because we're inside a transition, and the 10 second timeout specified in the `useTransition` call hasn't passed yet. While a transition is pending, the button displays an inline loading indicator. - -We can see now how Concurrent Mode helps us achieve a good user experience without sacrificing isolation and modularity of components. React coordinates the transition. - -## The Three Steps {#the-three-steps} - -By now we have discussed all of the different visual states that an update may go through. In this section, we will give them names and talk about the progression between them. - -
- -Three steps - -At the very end, we have the **Complete** state. That's where we want to eventually get to. It represents the moment when the next screen is fully rendered and isn't loading more data. - -But before our screen can be Complete, we might need to load some data or code. When we're on the next screen, but some parts of it are still loading, we call that a **Skeleton** state. - -Finally, there are two primary ways that lead us to the Skeleton state. We will illustrate the difference between them with a concrete example. - -### Default: Receded → Skeleton → Complete {#default-receded-skeleton-complete} - -Open [this example](https://codesandbox.io/s/xenodochial-breeze-khk2fh) and click "Open Profile". You will see several visual states one by one: - -* **Receded**: For a second, you will see the `

Loading the app...

` fallback. -* **Skeleton:** You will see the `` component with `

Loading posts...

` inside. -* **Complete:** You will see the `` component with no fallbacks inside. Everything was fetched. - -How do we separate the Receded and the Skeleton states? The difference between them is that the **Receded** state feels like "taking a step back" to the user, while the **Skeleton** state feels like "taking a step forward" in our progress to show more content. - -In this example, we started our journey on the ``: - -```js - - {/* previous screen */} - - -``` - -After the click, React started rendering the next screen: - -```js - - {/* next screen */} - - - - - - - -``` - -Both `` and `` need data to render, so they suspend: - -```js{4,6} - - {/* next screen */} - - {/* suspends! */} - Loading posts...}> - {/* suspends! */} - - - -``` - -When a component suspends, React needs to show the closest fallback. But the closest fallback to `` is at the top level: - -```js{2,3,7} - -

Loading the app...

-}> - {/* next screen */} - - {/* suspends! */} - - - - -
-``` - -This is why when we click the button, it feels like we've "taken a step back". The `` boundary which was previously showing useful content (``) had to "recede" to showing the fallback (`

Loading the app...

`). We call that a **Receded** state. - -As we load more data, React will retry rendering, and `` can render successfully. Finally, we're in the **Skeleton** state. We see the new page with missing parts: - -```js{6,7,9} - - {/* next screen */} - - - -

Loading posts...

- }> - {/* suspends! */} -
-
-
-``` - -Eventually, they load too, and we get to the **Complete** state. - -This scenario (Receded → Skeleton → Complete) is the default one. However, the Receded state is not very pleasant because it "hides" existing information. This is why React lets us opt into a different sequence (**Pending** → Skeleton → Complete) with `useTransition`. - -### Preferred: Pending → Skeleton → Complete {#preferred-pending-skeleton-complete} - -When we `useTransition`, React will let us "stay" on the previous screen -- and show a progress indicator there. We call that a **Pending** state. It feels much better than the Receded state because none of our existing content disappears, and the page stays interactive. - -You can compare these two examples to feel the difference: - -* Default: [Receded → Skeleton → Complete](https://codesandbox.io/s/xenodochial-breeze-khk2fh) -* **Preferred: [Pending → Skeleton → Complete](https://codesandbox.io/s/serene-pascal-w3no1l)** - -The only difference between these two examples is that the first uses regular ` - {isPending ? spinner : null} - - ); -} -``` - -**[Try it on CodeSandbox](https://codesandbox.io/s/jolly-http-n94od0)** - -This signals to the user that some work is happening. However, if the transition is relatively short (less than 500ms), it might be too distracting and make the transition itself feel *slower*. - -One possible solution to this is to *delay the spinner itself* from displaying: - -```css -.DelayedSpinner { - animation: 0s linear 0.5s forwards makeVisible; - visibility: hidden; -} - -@keyframes makeVisible { - to { - visibility: visible; - } -} -``` - -```js{2-4,10} -const spinner = ( - - {/* ... */} - -); - -return ( - <> - - {isPending ? spinner : null} - -); -``` - -**[Try it on CodeSandbox](https://codesandbox.io/s/optimistic-night-4td1me)** - -With this change, even though we're in the Pending state, we don't display any indication to the user until 500ms has passed. This may not seem like much of an improvement when the API responses are slow. But compare how it feels [before](https://codesandbox.io/s/priceless-water-yw7zw4) and [after](https://codesandbox.io/s/mystifying-noether-tnxftn) when the API call is fast. Even though the rest of the code hasn't changed, suppressing a "too fast" loading state improves the perceived performance by not calling attention to the delay. - -### Recap {#recap} - -The most important things we learned so far are: - -* By default, our loading sequence is Receded → Skeleton → Complete. -* The Receded state doesn't feel very nice because it hides existing content. -* With `useTransition`, we can opt into showing a Pending state first instead. This will keep us on the previous screen while the next screen is being prepared. -* If we don't want some component to delay the transition, we can wrap it in its own `` boundary. -* Instead of doing `useTransition` in every other component, we can build it into our design system. - -## Other Patterns {#other-patterns} - -Transitions are probably the most common Concurrent Mode pattern you'll encounter, but there are a few more patterns you might find useful. - -### Splitting High and Low Priority State {#splitting-high-and-low-priority-state} - -When you design React components, it is usually best to find the "minimal representation" of state. For example, instead of keeping `firstName`, `lastName`, and `fullName` in state, it's usually better keep only `firstName` and `lastName`, and then calculate `fullName` during rendering. This lets us avoid mistakes where we update one state but forget the other state. - -However, in Concurrent Mode there are cases where you might *want* to "duplicate" some data in different state variables. Consider this tiny translation app: - -```js -const initialQuery = "Hello, world"; -const initialResource = fetchTranslation(initialQuery); - -function App() { - const [query, setQuery] = useState(initialQuery); - const [resource, setResource] = useState(initialResource); - - function handleChange(e) { - const value = e.target.value; - setQuery(value); - setResource(fetchTranslation(value)); - } - - return ( - <> - - Loading...

}> - -
- - ); -} - -function Translation({ resource }) { - return ( -

- {resource.read()} -

- ); -} -``` - -**[Try it on CodeSandbox](https://codesandbox.io/s/boring-frost-t5ijqm)** - -Notice how when you type into the input, the `` component suspends, and we see the `

Loading...

` fallback until we get fresh results. This is not ideal. It would be better if we could see the *previous* translation for a bit while we're fetching the next one. - -In fact, if we open the console, we'll see a warning: - -``` -Warning: App triggered a user-blocking update that suspended. - -The fix is to split the update into multiple parts: a user-blocking update to provide immediate feedback, and another update that triggers the bulk of the changes. - -Refer to the documentation for useTransition to learn how to implement this pattern. -``` - -As we mentioned earlier, if some state update causes a component to suspend, that state update should be wrapped in a transition. Let's add `useTransition` to our component: - -```js{4-6,10,13} -function App() { - const [query, setQuery] = useState(initialQuery); - const [resource, setResource] = useState(initialResource); - const [startTransition, isPending] = useTransition({ - timeoutMs: 5000 - }); - - function handleChange(e) { - const value = e.target.value; - startTransition(() => { - setQuery(value); - setResource(fetchTranslation(value)); - }); - } - - // ... - -} -``` - -**[Try it on CodeSandbox](https://codesandbox.io/s/wizardly-swirles-476m52)** - -Try typing into the input now. Something's wrong! The input is updating very slowly. - -We've fixed the first problem (suspending outside of a transition). But now because of the transition, our state doesn't update immediately, and it can't "drive" a controlled input! - -The answer to this problem **is to split the state in two parts:** a "high priority" part that updates immediately, and a "low priority" part that may wait for a transition. - -In our example, we already have two state variables. The input text is in `query`, and we read the translation from `resource`. We want changes to the `query` state to happen immediately, but changes to the `resource` (i.e. fetching a new translation) should trigger a transition. - -So the correct fix is to put `setQuery` (which doesn't suspend) *outside* the transition, but `setResource` (which will suspend) *inside* of it. - -```js{4,5} -function handleChange(e) { - const value = e.target.value; - - // Outside the transition (urgent) - setQuery(value); - - startTransition(() => { - // Inside the transition (may be delayed) - setResource(fetchTranslation(value)); - }); -} -``` - -**[Try it on CodeSandbox](https://codesandbox.io/s/elegant-kalam-dhlrkz)** - -With this change, it works as expected. We can type into the input immediately, and the translation later "catches up" to what we have typed. - -### Deferring a Value {#deferring-a-value} - -By default, React always renders a consistent UI. Consider code like this: - -```js -<> - - - -``` - -React guarantees that whenever we look at these components on the screen, they will reflect data from the same `user`. If a different `user` is passed down because of a state update, you would see them changing together. You can't ever record a screen and find a frame where they would show values from different `user`s. (If you ever run into a case like this, file a bug!) - -This makes sense in the vast majority of situations. Inconsistent UI is confusing and can mislead users. (For example, it would be terrible if a messenger's Send button and the conversation picker pane "disagreed" about which thread is currently selected.) - -However, sometimes it might be helpful to intentionally introduce an inconsistency. We could do it manually by "splitting" the state like above, but React also offers a built-in Hook for this: - -```js -import { useDeferredValue } from 'react'; - -const deferredValue = useDeferredValue(value, { - timeoutMs: 5000 -}); -``` - -To demonstrate this feature, we'll use [the profile switcher example](https://codesandbox.io/s/quirky-carson-vs6g0i). Click the "Next" button and notice how it takes 1 second to do a transition. - -Let's say that fetching the user details is very fast and only takes 300 milliseconds. Currently, we're waiting a whole second because we need both user details and posts to display a consistent profile page. But what if we want to show the details faster? - -If we're willing to sacrifice consistency, we could **pass potentially stale data to the components that delay our transition**. That's what `useDeferredValue()` lets us do: - -```js{2-4,10,11,21} -function ProfilePage({ resource }) { - const deferredResource = useDeferredValue(resource, { - timeoutMs: 1000 - }); - return ( - Loading profile...}> - - Loading posts...}> - - - - ); -} - -function ProfileTimeline({ isStale, resource }) { - const posts = resource.posts.read(); - return ( -
    - {posts.map(post => ( -
  • {post.text}
  • - ))} -
- ); -} -``` - -**[Try it on CodeSandbox](https://codesandbox.io/s/dazzling-fog-o6ovhr)** - -The tradeoff we're making here is that `` will be inconsistent with other components and potentially show an older item. Click "Next" a few times, and you'll notice it. But thanks to that, we were able to cut down the transition time from 1000ms to 300ms. - -Whether or not it's an appropriate tradeoff depends on the situation. But it's a handy tool, especially when the content doesn't change noticeably between items, and the user might not even realize they were looking at a stale version for a second. - -It's worth noting that `useDeferredValue` is not *only* useful for data fetching. It also helps when an expensive component tree causes an interaction (e.g. typing in an input) to be sluggish. Just like we can "defer" a value that takes too long to fetch (and show its old value despite other components updating), we can do this with trees that take too long to render. - -For example, consider a filterable list like this: - -```js -function App() { - const [text, setText] = useState("hello"); - - function handleChange(e) { - setText(e.target.value); - } - - return ( -
- - ... - -
- ); -} -``` - -**[Try it on CodeSandbox](https://codesandbox.io/s/runtime-pine-kl2yff)** - -In this example, **every item in `` has an artificial slowdown -- each of them blocks the thread for a few milliseconds**. We'd never do this in a real app, but this helps us simulate what can happen in a deep component tree with no single obvious place to optimize. - -We can see how typing in the input causes stutter. Now let's add `useDeferredValue`: - -```js{3-5,18} -function App() { - const [text, setText] = useState("hello"); - const deferredText = useDeferredValue(text, { - timeoutMs: 5000 - }); - - function handleChange(e) { - setText(e.target.value); - } - - return ( -
- - ... - -
- ); -} -``` - -**[Try it on CodeSandbox](https://codesandbox.io/s/charming-goldwasser-6kuh4m)** - -Now typing has a lot less stutter -- although we pay for this by showing the results with a lag. - -How is this different from debouncing? Our example has a fixed artificial delay (3ms for every one of 80 items), so there is always a delay, no matter how fast our computer is. However, the `useDeferredValue` value only "lags behind" if the rendering takes a while. There is no minimal lag imposed by React. With a more realistic workload, you can expect the lag to adjust to the user’s device. On fast machines, the lag would be smaller or non-existent, and on slow machines, it would be more noticeable. In both cases, the app would remain responsive. That’s the advantage of this mechanism over debouncing or throttling, which always impose a minimal delay and can't avoid blocking the thread while rendering. - -Even though there is an improvement in responsiveness, this example isn't as compelling yet because Concurrent Mode is missing some crucial optimizations for this use case. Still, it is interesting to see that features like `useDeferredValue` (or `useTransition`) are useful regardless of whether we're waiting for network or for computational work to finish. - -### SuspenseList {#suspenselist} - -`` is the last pattern that's related to orchestrating loading states. - -Consider this example: - -```js{5-10} -function ProfilePage({ resource }) { - return ( - <> - - Loading posts...}> - - - Loading fun facts...}> - - - - ); -} -``` - -**[Try it on CodeSandbox](https://codesandbox.io/s/hardcore-river-14ecuq)** - -The API call duration in this example is randomized. If you keep refreshing it, you will notice that sometimes the posts arrive first, and sometimes the "fun facts" arrive first. - -This presents a problem. If the response for fun facts arrives first, we'll see the fun facts below the `

Loading posts...

` fallback for posts. We might start reading them, but then the *posts* response will come back, and shift all the facts down. This is jarring. - -One way we could fix it is by putting them both in a single boundary: - -```js -Loading posts and fun facts...}> - - - -``` - -**[Try it on CodeSandbox](https://codesandbox.io/s/quirky-meadow-w1c61p)** - -The problem with this is that now we *always* wait for both of them to be fetched. However, if it's the *posts* that came back first, there's no reason to delay showing them. When fun facts load later, they won't shift the layout because they're already below the posts. - -Other approaches to this, such as composing Promises in a special way, are increasingly difficult to pull off when the loading states are located in different components down the tree. - -To solve this, we will import `SuspenseList`: - -```js -import { SuspenseList } from 'react'; -``` - -`` coordinates the "reveal order" of the closest `` nodes below it: - -```js{3,11} -function ProfilePage({ resource }) { - return ( - - - Loading posts...}> - - - Loading fun facts...}> - - - - ); -} -``` - -**[Try it on CodeSandbox](https://codesandbox.io/s/empty-leaf-lp7eom)** - -The `revealOrder="forwards"` option means that the closest `` nodes inside this list **will only "reveal" their content in the order they appear in the tree -- even if the data for them arrives in a different order**. `` has other interesting modes: try changing `"forwards"` to `"backwards"` or `"together"` and see what happens. - -You can control how many loading states are visible at once with the `tail` prop. If we specify `tail="collapsed"`, we'll see *at most one* fallback at a time. You can play with it [here](https://codesandbox.io/s/keen-leaf-gccxd8). - -Keep in mind that `` is composable, like anything in React. For example, you can create a grid by putting several `` rows inside a `` table. - -## Next Steps {#next-steps} - -Concurrent Mode offers a powerful UI programming model and a set of new composable primitives to help you orchestrate delightful user experiences. - -It's a result of several years of research and development, but it's not finished. In the section on [adopting Concurrent Mode](/docs/concurrent-mode-adoption.html), we'll describe how you can try it and what you can expect. diff --git a/content/docs/concurrent-mode-reference.md b/content/docs/concurrent-mode-reference.md deleted file mode 100644 index 09fb109f7..000000000 --- a/content/docs/concurrent-mode-reference.md +++ /dev/null @@ -1,204 +0,0 @@ ---- -id: concurrent-mode-reference -title: Concurrent Mode API Reference (Experimental) -permalink: docs/concurrent-mode-reference.html -prev: concurrent-mode-adoption.html ---- - - - -
- ->Caution: -> ->This page was about experimental features that aren't yet available in a stable release. It was aimed at early adopters and people who are curious. -> ->Much of the information on this page is now outdated and exists only for archival purposes. **Please refer to the [React 18 Alpha announcement post](/blog/2021/06/08/the-plan-for-react-18.html -) for the up-to-date information.** -> ->Before React 18 is released, we will replace this page with stable documentation. - -
- -This page is an API reference for the React [Concurrent Mode](/docs/concurrent-mode-intro.html). If you're looking for a guided introduction instead, check out [Concurrent UI Patterns](/docs/concurrent-mode-patterns.html). - -**Note: This is a Community Preview and not the final stable version. There will likely be future changes to these APIs. Use at your own risk!** - -- [Enabling Concurrent Mode](#concurrent-mode) - - [`createRoot`](#createroot) -- [Suspense](#suspense) - - [`Suspense`](#suspensecomponent) - - [`SuspenseList`](#suspenselist) - - [`useTransition`](#usetransition) - - [`useDeferredValue`](#usedeferredvalue) - -## Enabling Concurrent Mode {#concurrent-mode} - -### `createRoot` {#createroot} - -```js -ReactDOM.createRoot(rootNode).render(); -``` - -Replaces `ReactDOM.render(, rootNode)` and enables Concurrent Mode. - -For more information on Concurrent Mode, check out the [Concurrent Mode documentation.](/docs/concurrent-mode-intro.html) - -## Suspense API {#suspense} - -### `Suspense` {#suspensecomponent} - -```js -Loading...}> - - - -``` - -`Suspense` lets your components "wait" for something before they can render, showing a fallback while waiting. - -In this example, `ProfileDetails` is waiting for an asynchronous API call to fetch some data. While we wait for `ProfileDetails` and `ProfilePhoto`, we will show the `Loading...` fallback instead. It is important to note that until all children inside `` have loaded, we will continue to show the fallback. - -`Suspense` takes two props: -* **fallback** takes a loading indicator. The fallback is shown until all of the children of the `Suspense` component have finished rendering. -* **unstable_avoidThisFallback** takes a boolean. It tells React whether to "skip" revealing this boundary during the initial load. This API will likely be removed in a future release. - -### `` {#suspenselist} - -```js - - - - - - - - - - - ... - -``` - -`SuspenseList` helps coordinate many components that can suspend by orchestrating the order in which these components are revealed to the user. - -When multiple components need to fetch data, this data may arrive in an unpredictable order. However, if you wrap these items in a `SuspenseList`, React will not show an item in the list until previous items have been displayed (this behavior is adjustable). - -`SuspenseList` takes two props: -* **revealOrder (forwards, backwards, together)** defines the order in which the `SuspenseList` children should be revealed. - * `together` reveals *all* of them when they're ready instead of one by one. -* **tail (collapsed, hidden)** dictates how unloaded items in a `SuspenseList` is shown. - * By default, `SuspenseList` will show all fallbacks in the list. - * `collapsed` shows only the next fallback in the list. - * `hidden` doesn't show any unloaded items. - -Note that `SuspenseList` only operates on the closest `Suspense` and `SuspenseList` components below it. It does not search for boundaries deeper than one level. However, it is possible to nest multiple `SuspenseList` components in each other to build grids. - -### `useTransition` {#usetransition} - -```js -const SUSPENSE_CONFIG = { timeoutMs: 2000 }; - -const [startTransition, isPending] = useTransition(SUSPENSE_CONFIG); -``` - -`useTransition` allows components to avoid undesirable loading states by waiting for content to load before **transitioning to the next screen**. It also allows components to defer slower, data fetching updates until subsequent renders so that more crucial updates can be rendered immediately. - -The `useTransition` hook returns two values in an array. -* `startTransition` is a function that takes a callback. We can use it to tell React which state we want to defer. -* `isPending` is a boolean. It's React's way of informing us whether we're waiting for the transition to finish. - -**If some state update causes a component to suspend, that state update should be wrapped in a transition.** - -```js -const SUSPENSE_CONFIG = { timeoutMs: 2000 }; - -function App() { - const [resource, setResource] = useState(initialResource); - const [startTransition, isPending] = useTransition(SUSPENSE_CONFIG); - return ( - <> - - {isPending ? " Loading..." : null} - }> - - - - ); -} -``` - -In this code, we've wrapped our data fetching with `startTransition`. This allows us to start fetching the profile data right away, while deferring the render of the next profile page and its associated `Spinner` for 2 seconds (the time shown in `timeoutMs`). - -The `isPending` boolean lets React know that our component is transitioning, so we are able to let the user know this by showing some loading text on the previous profile page. - -**For an in-depth look at transitions, you can read [Concurrent UI Patterns](/docs/concurrent-mode-patterns.html#transitions).** - -#### useTransition Config {#usetransition-config} - -```js -const SUSPENSE_CONFIG = { timeoutMs: 2000 }; -``` - -`useTransition` accepts an **optional Suspense Config** with a `timeoutMs`. This timeout (in milliseconds) tells React how long to wait before showing the next state (the new Profile Page in the above example). - -**Note: We recommend that you share Suspense Config between different modules.** - - -### `useDeferredValue` {#usedeferredvalue} - -```js -const deferredValue = useDeferredValue(value, { timeoutMs: 2000 }); -``` - -Returns a deferred version of the value that may "lag behind" it for at most `timeoutMs`. - -This is commonly used to keep the interface responsive when you have something that renders immediately based on user input and something that needs to wait for a data fetch. - -A good example of this is a text input. - -```js -function App() { - const [text, setText] = useState("hello"); - const deferredText = useDeferredValue(text, { timeoutMs: 2000 }); - - return ( -
- {/* Keep passing the current text to the input */} - - ... - {/* But the list is allowed to "lag behind" when necessary */} - -
- ); - } -``` - -This allows us to start showing the new text for the `input` immediately, which allows the webpage to feel responsive. Meanwhile, `MySlowList` "lags behind" for up to 2 seconds according to the `timeoutMs` before updating, allowing it to render with the current text in the background. - -**For an in-depth look at deferring values, you can read [Concurrent UI Patterns](/docs/concurrent-mode-patterns.html#deferring-a-value).** - -#### useDeferredValue Config {#usedeferredvalue-config} - -```js -const SUSPENSE_CONFIG = { timeoutMs: 2000 }; -``` - -`useDeferredValue` accepts an **optional Suspense Config** with a `timeoutMs`. This timeout (in milliseconds) tells React how long the deferred value is allowed to lag behind. - -React will always try to use a shorter lag when network and device allows it. diff --git a/content/docs/concurrent-mode-suspense.md b/content/docs/concurrent-mode-suspense.md deleted file mode 100644 index 9f2deb79a..000000000 --- a/content/docs/concurrent-mode-suspense.md +++ /dev/null @@ -1,739 +0,0 @@ ---- -id: concurrent-mode-suspense -title: Suspense for Data Fetching (Experimental) -permalink: docs/concurrent-mode-suspense.html -prev: concurrent-mode-intro.html -next: concurrent-mode-patterns.html ---- - - - -
- ->Caution: -> ->This page was about experimental features that aren't yet available in a stable release. It was aimed at early adopters and people who are curious. -> ->Much of the information on this page is now outdated and exists only for archival purposes. **Please refer to the [React 18 Alpha announcement post](/blog/2021/06/08/the-plan-for-react-18.html -) for the up-to-date information.** -> ->Before React 18 is released, we will replace this page with stable documentation. - -
- -React 16.6 added a `` component that lets you "wait" for some code to load and declaratively specify a loading state (like a spinner) while we're waiting: - -```jsx -const ProfilePage = React.lazy(() => import('./ProfilePage')); // Lazy-loaded - -// Show a spinner while the profile is loading -}> - - -``` - -Suspense for Data Fetching is a new feature that lets you also use `` to **declaratively "wait" for anything else, including data.** This page focuses on the data fetching use case, but it can also wait for images, scripts, or other asynchronous work. - -- [What Is Suspense, Exactly?](#what-is-suspense-exactly) - - [What Suspense Is Not](#what-suspense-is-not) - - [What Suspense Lets You Do](#what-suspense-lets-you-do) -- [Using Suspense in Practice](#using-suspense-in-practice) - - [What If I Don’t Use Relay?](#what-if-i-dont-use-relay) - - [For Library Authors](#for-library-authors) -- [Traditional Approaches vs Suspense](#traditional-approaches-vs-suspense) - - [Approach 1: Fetch-on-Render (not using Suspense)](#approach-1-fetch-on-render-not-using-suspense) - - [Approach 2: Fetch-Then-Render (not using Suspense)](#approach-2-fetch-then-render-not-using-suspense) - - [Approach 3: Render-as-You-Fetch (using Suspense)](#approach-3-render-as-you-fetch-using-suspense) -- [Start Fetching Early](#start-fetching-early) - - [We’re Still Figuring This Out](#were-still-figuring-this-out) -- [Suspense and Race Conditions](#suspense-and-race-conditions) - - [Race Conditions with useEffect](#race-conditions-with-useeffect) - - [Race Conditions with componentDidUpdate](#race-conditions-with-componentdidupdate) - - [The Problem](#the-problem) - - [Solving Race Conditions with Suspense](#solving-race-conditions-with-suspense) -- [Handling Errors](#handling-errors) -- [Next Steps](#next-steps) - -## What Is Suspense, Exactly? {#what-is-suspense-exactly} - -Suspense lets your components "wait" for something before they can render. In [this example](https://codesandbox.io/s/frosty-hermann-bztrp), two components wait for an asynchronous API call to fetch some data: - -```js -const resource = fetchProfileData(); - -function ProfilePage() { - return ( - Loading profile...}> - - Loading posts...}> - - - - ); -} - -function ProfileDetails() { - // Try to read user info, although it might not have loaded yet - const user = resource.user.read(); - return

{user.name}

; -} - -function ProfileTimeline() { - // Try to read posts, although they might not have loaded yet - const posts = resource.posts.read(); - return ( -
    - {posts.map(post => ( -
  • {post.text}
  • - ))} -
- ); -} -``` - -**[Try it on CodeSandbox](https://codesandbox.io/s/frosty-hermann-bztrp)** - -This demo is a teaser. Don't worry if it doesn't quite make sense yet. We'll talk more about how it works below. Keep in mind that Suspense is more of a *mechanism*, and particular APIs like `fetchProfileData()` or `resource.posts.read()` in the above example are not very important. If you're curious, you can find their definitions right in the [demo sandbox](https://codesandbox.io/s/frosty-hermann-bztrp). - -Suspense is not a data fetching library. It's a **mechanism for data fetching libraries** to communicate to React that *the data a component is reading is not ready yet*. React can then wait for it to be ready and update the UI. At Facebook, we use Relay and its [new Suspense integration](https://relay.dev/docs/getting-started/step-by-step-guide/). We expect that other libraries like Apollo can provide similar integrations. - -In the long term, we intend Suspense to become the primary way to read asynchronous data from components -- no matter where that data is coming from. - -### What Suspense Is Not {#what-suspense-is-not} - -Suspense is significantly different from existing approaches to these problems, so reading about it for the first time often leads to misconceptions. Let's clarify the most common ones: - - * **It is not a data fetching implementation.** It does not assume that you use GraphQL, REST, or any other particular data format, library, transport, or protocol. - - * **It is not a ready-to-use client.** You can't "replace" `fetch` or Relay with Suspense. But you can use a library that's integrated with Suspense (for example, [new Relay APIs](https://relay.dev/docs/api-reference/relay-environment-provider/)). - - * **It does not couple data fetching to the view layer.** It helps orchestrate displaying the loading states in your UI, but it doesn't tie your network logic to React components. - -### What Suspense Lets You Do {#what-suspense-lets-you-do} - -So what's the point of Suspense? There are a few ways we can answer this: - -* **It lets data fetching libraries deeply integrate with React.** If a data fetching library implements Suspense support, using it from React components feels very natural. - -* **It lets you orchestrate intentionally designed loading states.** It doesn't say _how_ the data is fetched, but it lets you closely control the visual loading sequence of your app. - -* **It helps you avoid race conditions.** Even with `await`, asynchronous code is often error-prone. Suspense feels more like reading data *synchronously* — as if it were already loaded. - -## Using Suspense in Practice {#using-suspense-in-practice} - -At Facebook, so far we have only used the Relay integration with Suspense in production. **If you're looking for a practical guide to get started today, [check out the Relay Guide](https://relay.dev/docs/getting-started/step-by-step-guide/)!** It demonstrates patterns that have already worked well for us in production. - -**The code demos on this page use a "fake" API implementation rather than Relay.** This makes them easier to understand if you're not familiar with GraphQL, but they won't tell you the "right way" to build an app with Suspense. This page is more conceptual and is intended to help you see *why* Suspense works in a certain way, and which problems it solves. - -### What If I Don't Use Relay? {#what-if-i-dont-use-relay} - -If you don't use Relay today, you might have to wait before you can really try Suspense in your app. So far, it's the only implementation that we tested in production and are confident in. - -Over the next several months, many libraries will appear with different takes on Suspense APIs. **If you prefer to learn when things are more stable, you might prefer to ignore this work for now, and come back when the Suspense ecosystem is more mature.** - -You can also write your own integration for a data fetching library, if you'd like. - -### For Library Authors {#for-library-authors} - -We expect to see a lot of experimentation in the community with other libraries. There is one important thing to note for data fetching library authors. - -Although it's technically doable, Suspense is **not** currently intended as a way to start fetching data when a component renders. Rather, it lets components express that they're "waiting" for data that is *already being fetched*. **[Building Great User Experiences with Concurrent Mode and Suspense](/blog/2019/11/06/building-great-user-experiences-with-concurrent-mode-and-suspense.html) describes why this matters and how to implement this pattern in practice.** - -Unless you have a solution that helps prevent waterfalls, we suggest to prefer APIs that favor or enforce fetching before render. For a concrete example, you can look at how [Relay Suspense API](https://relay.dev/docs/api-reference/use-preloaded-query/) enforces preloading. Our messaging about this hasn't been very consistent in the past. Suspense for Data Fetching is still experimental, so you can expect our recommendations to change over time as we learn more from production usage and understand the problem space better. - -## Traditional Approaches vs Suspense {#traditional-approaches-vs-suspense} - -We could introduce Suspense without mentioning the popular data fetching approaches. However, this makes it more difficult to see which problems Suspense solves, why these problems are worth solving, and how Suspense is different from the existing solutions. - -Instead, we'll look at Suspense as a logical next step in a sequence of approaches: - -* **Fetch-on-render (for example, `fetch` in `useEffect`):** Start rendering components. Each of these components may trigger data fetching in their effects and lifecycle methods. This approach often leads to "waterfalls". -* **Fetch-then-render (for example, Relay without Suspense):** Start fetching all the data for the next screen as early as possible. When the data is ready, render the new screen. We can't do anything until the data arrives. -* **Render-as-you-fetch (for example, Relay with Suspense):** Start fetching all the required data for the next screen as early as possible, and start rendering the new screen *immediately — before we get a network response*. As data streams in, React retries rendering components that still need data until they're all ready. - ->Note -> ->This is a bit simplified, and in practice solutions tend to use a mix of different approaches. Still, we will look at them in isolation to better contrast their tradeoffs. - -To compare these approaches, we'll implement a profile page with each of them. - -### Approach 1: Fetch-on-Render (not using Suspense) {#approach-1-fetch-on-render-not-using-suspense} - -A common way to fetch data in React apps today is to use an effect: - -```js -// In a function component: -useEffect(() => { - fetchSomething(); -}, []); - -// Or, in a class component: -componentDidMount() { - fetchSomething(); -} -``` - -We call this approach "fetch-on-render" because it doesn't start fetching until *after* the component has rendered on the screen. This leads to a problem known as a "waterfall". - -Consider these `` and `` components: - -```js{4-6,22-24} -function ProfilePage() { - const [user, setUser] = useState(null); - - useEffect(() => { - fetchUser().then(u => setUser(u)); - }, []); - - if (user === null) { - return

Loading profile...

; - } - return ( - <> -

{user.name}

- - - ); -} - -function ProfileTimeline() { - const [posts, setPosts] = useState(null); - - useEffect(() => { - fetchPosts().then(p => setPosts(p)); - }, []); - - if (posts === null) { - return

Loading posts...

; - } - return ( -
    - {posts.map(post => ( -
  • {post.text}
  • - ))} -
- ); -} -``` - -**[Try it on CodeSandbox](https://codesandbox.io/s/fast-glade-rqnhtt)** - -If you run this code and watch the console logs, you'll notice the sequence is: - -1. We start fetching user details -2. We wait... -3. We finish fetching user details -4. We start fetching posts -5. We wait... -6. We finish fetching posts - -If fetching user details takes three seconds, we'll only *start* fetching the posts after three seconds! That's a "waterfall": an unintentional *sequence* that should have been parallelized. - -Waterfalls are common in code that fetches data on render. They're possible to solve, but as the product grows, many people prefer to use a solution that guards against this problem. - -### Approach 2: Fetch-Then-Render (not using Suspense) {#approach-2-fetch-then-render-not-using-suspense} - -Libraries can prevent waterfalls by offering a more centralized way to do data fetching. For example, Relay solves this problem by moving the information about the data a component needs to statically analyzable *fragments*, which later get composed into a single query. - -On this page, we don't assume knowledge of Relay, so we won't be using it for this example. Instead, we'll write something similar manually by combining our data fetching methods: - -```js -function fetchProfileData() { - return Promise.all([ - fetchUser(), - fetchPosts() - ]).then(([user, posts]) => { - return {user, posts}; - }) -} -``` - -In this example, `` waits for both requests but starts them in parallel: - -```js{1,2,8-13} -// Kick off fetching as early as possible -const promise = fetchProfileData(); - -function ProfilePage() { - const [user, setUser] = useState(null); - const [posts, setPosts] = useState(null); - - useEffect(() => { - promise.then(data => { - setUser(data.user); - setPosts(data.posts); - }); - }, []); - - if (user === null) { - return

Loading profile...

; - } - return ( - <> -

{user.name}

- - - ); -} - -// The child doesn't trigger fetching anymore -function ProfileTimeline({ posts }) { - if (posts === null) { - return

Loading posts...

; - } - return ( -
    - {posts.map(post => ( -
  • {post.text}
  • - ))} -
- ); -} -``` - -**[Try it on CodeSandbox](https://codesandbox.io/s/hopeful-lake-loddz9)** - -The event sequence now becomes like this: - -1. We start fetching user details -2. We start fetching posts -3. We wait... -4. We finish fetching user details -5. We finish fetching posts - -We've solved the previous network "waterfall", but accidentally introduced a different one. We wait for *all* data to come back with `Promise.all()` inside `fetchProfileData`, so now we can't render profile details until the posts have been fetched too. We have to wait for both. - -Of course, this is possible to fix in this particular example. We could remove the `Promise.all()` call, and wait for both Promises separately. However, this approach gets progressively more difficult as the complexity of our data and component tree grows. It's hard to write reliable components when arbitrary parts of the data tree may be missing or stale. So fetching all data for the new screen and *then* rendering is often a more practical option. - -### Approach 3: Render-as-You-Fetch (using Suspense) {#approach-3-render-as-you-fetch-using-suspense} - -In the previous approach, we fetched data before we called `setState`: - -1. Start fetching -2. Finish fetching -3. Start rendering - -With Suspense, we still start fetching first, but we flip the last two steps around: - -1. Start fetching -2. **Start rendering** -3. **Finish fetching** - -**With Suspense, we don't wait for the response to come back before we start rendering.** In fact, we start rendering *pretty much immediately* after kicking off the network request: - -```js{2,17,23} -// This is not a Promise. It's a special object from our Suspense integration. -const resource = fetchProfileData(); - -function ProfilePage() { - return ( - Loading profile...}> - - Loading posts...}> - - - - ); -} - -function ProfileDetails() { - // Try to read user info, although it might not have loaded yet - const user = resource.user.read(); - return

{user.name}

; -} - -function ProfileTimeline() { - // Try to read posts, although they might not have loaded yet - const posts = resource.posts.read(); - return ( -
    - {posts.map(post => ( -
  • {post.text}
  • - ))} -
- ); -} -``` - -**[Try it on CodeSandbox](https://codesandbox.io/s/frosty-hermann-bztrp)** - -Here's what happens when we render `` on the screen: - -1. We've already kicked off the requests in `fetchProfileData()`. It gave us a special "resource" instead of a Promise. In a realistic example, it would be provided by our data library's Suspense integration, like Relay. -2. React tries to render ``. It returns `` and `` as children. -3. React tries to render ``. It calls `resource.user.read()`. None of the data is fetched yet, so this component "suspends". React skips over it, and tries rendering other components in the tree. -4. React tries to render ``. It calls `resource.posts.read()`. Again, there's no data yet, so this component also "suspends". React skips over it too, and tries rendering other components in the tree. -5. There's nothing left to try rendering. Because `` suspended, React shows the closest `` fallback above it in the tree: `

Loading profile...

`. We're done for now. - -This `resource` object represents the data that isn't there yet, but might eventually get loaded. When we call `read()`, we either get the data, or the component "suspends". - -**As more data streams in, React will retry rendering, and each time it might be able to progress "deeper".** When `resource.user` is fetched, the `` component will render successfully and we'll no longer need the `

Loading profile...

` fallback. Eventually, we'll get all the data, and there will be no fallbacks on the screen. - -This has an interesting implication. Even if we use a GraphQL client that collects all data requirements in a single request, *streaming the response lets us show more content sooner*. Because we render-*as-we-fetch* (as opposed to *after* fetching), if `user` appears in the response earlier than `posts`, we'll be able to "unlock" the outer `` boundary before the response even finishes. We might have missed this earlier, but even the fetch-then-render solution contained a waterfall: between fetching and rendering. Suspense doesn't inherently suffer from this waterfall, and libraries like Relay take advantage of this. - -Note how we eliminated the `if (...)` "is loading" checks from our components. This doesn't only remove boilerplate code, but it also simplifies making quick design changes. For example, if we wanted profile details and posts to always "pop in" together, we could delete the `` boundary between them. Or we could make them independent from each other by giving each *its own* `` boundary. Suspense lets us change the granularity of our loading states and orchestrate their sequencing without invasive changes to our code. - -## Start Fetching Early {#start-fetching-early} - -If you're working on a data fetching library, there's a crucial aspect of Render-as-You-Fetch you don't want to miss. **We kick off fetching _before_ rendering.** Look at this code example closer: - -```js -// Start fetching early! -const resource = fetchProfileData(); - -// ... - -function ProfileDetails() { - // Try to read user info - const user = resource.user.read(); - return

{user.name}

; -} -``` - -**[Try it on CodeSandbox](https://codesandbox.io/s/frosty-hermann-bztrp)** - -Note that the `read()` call in this example doesn't *start* fetching. It only tries to read the data that is **already being fetched**. This difference is crucial to creating fast applications with Suspense. We don't want to delay loading data until a component starts rendering. As a data fetching library author, you can enforce this by making it impossible to get a `resource` object without also starting a fetch. Every demo on this page using our "fake API" enforces this. - -You might object that fetching "at the top level" like in this example is impractical. What are we going to do if we navigate to another profile's page? We might want to fetch based on props. The answer to this is **we want to start fetching in the event handlers instead**. Here is a simplified example of navigating between user's pages: - -```js{1,2,10,11} -// First fetch: as soon as possible -const initialResource = fetchProfileData(0); - -function App() { - const [resource, setResource] = useState(initialResource); - return ( - <> - - - - ); -} -``` - -**[Try it on CodeSandbox](https://codesandbox.io/s/sparkling-field-41z4r3)** - -With this approach, we can **fetch code and data in parallel**. When we navigate between pages, we don't need to wait for a page's code to load to start loading its data. We can start fetching both code and data at the same time (during the link click), delivering a much better user experience. - -This poses a question of how do we know *what* to fetch before rendering the next screen. There are several ways to solve this (for example, by integrating data fetching closer with your routing solution). If you work on a data fetching library, [Building Great User Experiences with Concurrent Mode and Suspense](/blog/2019/11/06/building-great-user-experiences-with-concurrent-mode-and-suspense.html) presents a deep dive on how to accomplish this and why it's important. - -### We're Still Figuring This Out {#were-still-figuring-this-out} - -Suspense itself as a mechanism is flexible and doesn't have many constraints. Product code needs to be more constrained to ensure no waterfalls, but there are different ways to provide these guarantees. Some questions that we're currently exploring include: - -* Fetching early can be cumbersome to express. How do we make it easier to avoid waterfalls? -* When we fetch data for a page, can the API encourage including data for instant transitions *from* it? -* What is the lifetime of a response? Should caching be global or local? Who manages the cache? -* Can Proxies help express lazy-loaded APIs without inserting `read()` calls everywhere? -* What would the equivalent of composing GraphQL queries look like for arbitrary Suspense data? - -Relay has its own answers to some of these questions. There is certainly more than a single way to do it, and we're excited to see what new ideas the React community comes up with. - -## Suspense and Race Conditions {#suspense-and-race-conditions} - -Race conditions are bugs that happen due to incorrect assumptions about the order in which our code may run. Fetching data in the `useEffect` Hook or in class lifecycle methods like `componentDidUpdate` often leads to them. Suspense can help here, too — let's see how. - -To demonstrate the issue, we will add a top-level `` component that renders our `` with a button that lets us **switch between different profiles**: - -```js{9-11} -function getNextId(id) { - // ... -} - -function App() { - const [id, setId] = useState(0); - return ( - <> - - - - ); -} -``` - -Let's compare how different data fetching strategies deal with this requirement. - -### Race Conditions with `useEffect` {#race-conditions-with-useeffect} - -First, we'll try a version of our original "fetch in effect" example. We'll modify it to pass an `id` parameter from the `` props to `fetchUser(id)` and `fetchPosts(id)`: - -```js{1,5,6,14,19,23,24} -function ProfilePage({ id }) { - const [user, setUser] = useState(null); - - useEffect(() => { - fetchUser(id).then(u => setUser(u)); - }, [id]); - - if (user === null) { - return

Loading profile...

; - } - return ( - <> -

{user.name}

- - - ); -} - -function ProfileTimeline({ id }) { - const [posts, setPosts] = useState(null); - - useEffect(() => { - fetchPosts(id).then(p => setPosts(p)); - }, [id]); - - if (posts === null) { - return

Loading posts...

; - } - return ( -
    - {posts.map(post => ( -
  • {post.text}
  • - ))} -
- ); -} -``` - -**[Try it on CodeSandbox](https://codesandbox.io/s/beautiful-mendeleev-qwyxzg)** - -Note how we also changed the effect dependencies from `[]` to `[id]` — because we want the effect to re-run when the `id` changes. Otherwise, we wouldn't refetch new data. - -If we try this code, it might seem like it works at first. However, if we randomize the delay time in our "fake API" implementation and press the "Next" button fast enough, we'll see from the console logs that something is going very wrong. **Requests from the previous profiles may sometimes "come back" after we've already switched the profile to another ID -- and in that case they can overwrite the new state with a stale response for a different ID.** - -This problem is possible to fix (you could use the effect cleanup function to either ignore or cancel stale requests), but it's unintuitive and difficult to debug. - -### Race Conditions with `componentDidUpdate` {#race-conditions-with-componentdidupdate} - -One might think that this is a problem specific to `useEffect` or Hooks. Maybe if we port this code to classes or use convenient syntax like `async` / `await`, it will solve the problem? - -Let's try that: - -```js -class ProfilePage extends React.Component { - state = { - user: null, - }; - componentDidMount() { - this.fetchData(this.props.id); - } - componentDidUpdate(prevProps) { - if (prevProps.id !== this.props.id) { - this.fetchData(this.props.id); - } - } - async fetchData(id) { - const user = await fetchUser(id); - this.setState({ user }); - } - render() { - const { id } = this.props; - const { user } = this.state; - if (user === null) { - return

Loading profile...

; - } - return ( - <> -

{user.name}

- - - ); - } -} - -class ProfileTimeline extends React.Component { - state = { - posts: null, - }; - componentDidMount() { - this.fetchData(this.props.id); - } - componentDidUpdate(prevProps) { - if (prevProps.id !== this.props.id) { - this.fetchData(this.props.id); - } - } - async fetchData(id) { - const posts = await fetchPosts(id); - this.setState({ posts }); - } - render() { - const { posts } = this.state; - if (posts === null) { - return

Loading posts...

; - } - return ( -
    - {posts.map(post => ( -
  • {post.text}
  • - ))} -
- ); - } -} -``` - -**[Try it on CodeSandbox](https://codesandbox.io/s/async-wind-9o4ojn)** - -This code is deceptively easy to read. - -Unfortunately, neither using a class nor the `async` / `await` syntax helped us solve this problem. This version suffers from exactly the same race conditions, for the same reasons. - -### The Problem {#the-problem} - -React components have their own "lifecycle". They may receive props or update state at any point in time. However, each asynchronous request *also* has its own "lifecycle". It starts when we kick it off, and finishes when we get a response. The difficulty we're experiencing is "synchronizing" several processes in time that affect each other. This is hard to think about. - -### Solving Race Conditions with Suspense {#solving-race-conditions-with-suspense} - -Let's rewrite this example again, but using Suspense only: - -```js -const initialResource = fetchProfileData(0); - -function App() { - const [resource, setResource] = useState(initialResource); - return ( - <> - - - - ); -} - -function ProfilePage({ resource }) { - return ( - Loading profile...}> - - Loading posts...}> - - - - ); -} - -function ProfileDetails({ resource }) { - const user = resource.user.read(); - return

{user.name}

; -} - -function ProfileTimeline({ resource }) { - const posts = resource.posts.read(); - return ( -
    - {posts.map(post => ( -
  • {post.text}
  • - ))} -
- ); -} -``` - -**[Try it on CodeSandbox](https://codesandbox.io/s/sparkling-field-41z4r3)** - -In the previous Suspense example, we only had one `resource`, so we held it in a top-level variable. Now that we have multiple resources, we moved it to the ``'s component state: - -```js{4} -const initialResource = fetchProfileData(0); - -function App() { - const [resource, setResource] = useState(initialResource); -``` - -When we click "Next", the `` component kicks off a request for the next profile, and passes *that* object down to the `` component: - -```js{4,8} - <> - - - -``` - -Again, notice that **we're not waiting for the response to set the state. It's the other way around: we set the state (and start rendering) immediately after kicking off a request**. As soon as we have more data, React "fills in" the content inside `` components. - -This code is very readable, but unlike the examples earlier, the Suspense version doesn't suffer from race conditions. You might be wondering why. The answer is that in the Suspense version, we don't have to think about *time* as much in our code. Our original code with race conditions needed to set the state *at the right moment later*, or otherwise it would be wrong. But with Suspense, we set the state *immediately* -- so it's harder to mess it up. - -## Handling Errors {#handling-errors} - -When we write code with Promises, we might use `catch()` to handle errors. How does this work with Suspense, given that we don't *wait* for Promises to start rendering? - -With Suspense, handling fetching errors works the same way as handling rendering errors -- you can render an [error boundary](/docs/error-boundaries.html) anywhere to "catch" errors in components below. - -First, we'll define an error boundary component to use across our project: - -```js -// Error boundaries currently have to be classes. -class ErrorBoundary extends React.Component { - state = { hasError: false, error: null }; - static getDerivedStateFromError(error) { - return { - hasError: true, - error - }; - } - render() { - if (this.state.hasError) { - return this.props.fallback; - } - return this.props.children; - } -} -``` - -And then we can put it anywhere in the tree to catch errors: - -```js{5,9} -function ProfilePage() { - return ( - Loading profile...}> - - Could not fetch posts.}> - Loading posts...}> - - - - - ); -} -``` - -**[Try it on CodeSandbox](https://codesandbox.io/s/sparkling-rgb-r5vfhs)** - -It would catch both rendering errors *and* errors from Suspense data fetching. We can have as many error boundaries as we like but it's best to [be intentional](https://aweary.dev/fault-tolerance-react/) about their placement. - -## Next Steps {#next-steps} - -We've now covered the basics of Suspense for Data Fetching! Importantly, we now better understand *why* Suspense works this way, and how it fits into the data fetching space. - -Suspense answers some questions, but it also poses new questions of its own: - -* If some component "suspends", does the app freeze? How to avoid this? -* What if we want to show a spinner in a different place than "above" the component in a tree? -* If we intentionally *want* to show an inconsistent UI for a small period of time, can we do that? -* Instead of showing a spinner, can we add a visual effect like "greying out" the current screen? -* Why does our [last Suspense example](https://codesandbox.io/s/sparkling-field-41z4r3) log a warning when clicking the "Next" button? - -To answer these questions, we will refer to the next section on [Concurrent UI Patterns](/docs/concurrent-mode-patterns.html). diff --git a/content/docs/forms.md b/content/docs/forms.md index c43a72a3b..08344f4b9 100644 --- a/content/docs/forms.md +++ b/content/docs/forms.md @@ -275,10 +275,10 @@ Specifying the `value` prop on a [controlled component](/docs/forms.html#control The following code demonstrates this. (The input is locked at first but becomes editable after a short delay.) ```javascript -ReactDOM.render(, mountNode); +ReactDOM.createRoot(mountNode).render(); setTimeout(function() { - ReactDOM.render(, mountNode); + ReactDOM.createRoot(mountNode).render(); }, 1000); ``` diff --git a/content/docs/handling-events.md b/content/docs/handling-events.md index 5a746ee61..a3b56b589 100644 --- a/content/docs/handling-events.md +++ b/content/docs/handling-events.md @@ -84,11 +84,6 @@ class Toggle extends React.Component { ); } } - -ReactDOM.render( - , - document.getElementById('root') -); ``` [**Try it on CodePen**](https://codepen.io/gaearon/pen/xEmzGg?editors=0010) diff --git a/content/docs/hello-world.md b/content/docs/hello-world.md index 79b208d9e..f166a7d15 100644 --- a/content/docs/hello-world.md +++ b/content/docs/hello-world.md @@ -8,11 +8,10 @@ next: introducing-jsx.html The smallest React example looks like this: -```js -ReactDOM.render( -

Hello, world!

, - document.getElementById('root') -); +```jsx +ReactDOM + .createRoot(document.getElementById('root')) + .render(

Hello, world!

); ``` It displays a heading saying "Hello, world!" on the page. diff --git a/content/docs/hooks-faq.md b/content/docs/hooks-faq.md index 818206526..8976b0a0a 100644 --- a/content/docs/hooks-faq.md +++ b/content/docs/hooks-faq.md @@ -148,7 +148,7 @@ We'll test it using React DOM. To make sure that the behavior matches what happe ```js{3,20-22,29-31} import React from 'react'; -import ReactDOM from 'react-dom'; +import ReactDOM from 'react-dom/client'; import { act } from 'react-dom/test-utils'; import Counter from './Counter'; @@ -167,7 +167,7 @@ afterEach(() => { it('can render and update a counter', () => { // Test first render and effect act(() => { - ReactDOM.render(, container); + ReactDOM.createRoot(container).render(); }); const button = container.querySelector('button'); const label = container.querySelector('p'); diff --git a/content/docs/hooks-reference.md b/content/docs/hooks-reference.md index 1343cc90c..61966376d 100644 --- a/content/docs/hooks-reference.md +++ b/content/docs/hooks-reference.md @@ -24,6 +24,12 @@ If you're new to Hooks, you might want to check out [the overview](/docs/hooks-o - [`useImperativeHandle`](#useimperativehandle) - [`useLayoutEffect`](#uselayouteffect) - [`useDebugValue`](#usedebugvalue) + - [`useDeferredValue`](#usedeferredvalue) + - [`useTransition`](#usetransition) + - [`useId`](#useid) +- [Library Hooks](#library-hooks) + - [`useSyncExternalStore`](#usesyncexternalstore) + - [`useInsertionEffect`](#useinsertioneffect) ## Basic Hooks {#basic-hooks} @@ -138,7 +144,13 @@ Unlike `componentDidMount` and `componentDidUpdate`, the function passed to `use However, not all effects can be deferred. For example, a DOM mutation that is visible to the user must fire synchronously before the next paint so that the user does not perceive a visual inconsistency. (The distinction is conceptually similar to passive versus active event listeners.) For these types of effects, React provides one additional Hook called [`useLayoutEffect`](#uselayouteffect). It has the same signature as `useEffect`, and only differs in when it is fired. -Although `useEffect` is deferred until after the browser has painted, it's guaranteed to fire before any new renders. React will always flush a previous render's effects before starting a new update. +Additionally, starting in React 18, the function passed to `useEffect` will fire synchronously **before** layout and paint when it's the result of a discrete user input such as a click, or when it's the result of an update wrapped in [`flushSync`](/docs/react-dom.html#flushsync). This behavior allows the result of the effect to be observed by the event system, or by the caller of [`flushSync`](/docs/react-dom.html#flushsync). + +> Note +> +> This only affects the timing of when the function passed to `useEffect` is called - updates scheduled inside these effects are still deferred. This is different than [`useLayoutEffect`](#uselayouteffect), which fires the function and processes the updates inside of it immediately. + +Even in cases where `useEffect` is deferred until after the browser has painted, it's guaranteed to fire before any new renders. React will always flush a previous render's effects before starting a new update. #### Conditionally firing an effect {#conditionally-firing-an-effect} @@ -508,3 +520,195 @@ For example a custom Hook that returned a `Date` value could avoid calling the ` ```js useDebugValue(date, date => date.toDateString()); ``` + +### `useDeferredValue` {#usedeferredvalue} + +```js +const [deferredValue] = useDeferredValue(value); +``` + +`useDeferredValue` accepts a value and returns a new copy of the value that will defer to more urgent updates. If the current render is the result of an urgent update, like user input, React will return the previous value and then render the new value after the urgent render has completed. + +This hook is similar to user-space hooks which use debouncing or throttling to defer updates. The benefits to using `useDeferredValue` is that React will work on the update as soon as other work finishes (instead of waiting for an arbitrary amount of time), and like [`startTransition`](/docs/react-api.html#starttransition), deferred values can suspend without triggering an unexpected fallback for existing content. + +#### Memoizing deferred children {#memoizing-deferred-children} +`useDeferredValue` only defers the value that you pass to it. If you want to prevent a child component from re-rendering during an urgent update, you must also memoize that component with [`React.memo`](/docs/react-api.html#reactmemo) or [`React.useMemo`](/docs/hooks-reference.html#usememo): + +```js +function Typeahead() { + const query = useSearchQuery(''); + const deferredQuery = useDeferredValue(query); + + // Memoizing tells React to only re-render when deferredQuery changes, + // not when query changes. + const suggestions = useMemo(() => + , + [deferredQuery] + ); + + return ( + <> + + + {suggestions} + + + ); +} +``` + +Memoizing the children tells React that it only needs to re-render them when `deferredQuery` changes and not when `query` changes. This caveat is not unique to `useDeferredValue`, and it's the same pattern you would use with similar hooks that use debouncing or throttling. + +### `useTransition` {#usetransition} + +```js +const [isPending, startTransition] = useTransition(); +``` + +Returns a stateful value for the pending state of the transition, and a function to start it. + +`startTransition` lets you mark updates in the provided callback as transitions: + +```js +startTransition(() => { + setCount(count + 1); +}) +``` + +`isPending` indicates when a transition is active to show a pending state: + +```js +function App() { + const [isPending, startTransition] = useTransition(); + const [count, setCount] = useState(0); + + function handleClick() { + startTransition(() => { + setCount(c => c + 1); + }) + } + + return ( +
+ {isPending && } + +
+ ); +} +``` + +> Note: +> +> Updates in a transition yield to more urgent updates such as clicks. +> +> Updates in a transitions will not show a fallback for re-suspended content. This allows the user to continue interacting with the current content while rendering the update. + +### `useId` {#useid} + +```js +const id = useId(); +``` + +`useId` is a hook for generating unique IDs that are stable across the server and client, while avoiding hydration mismatches. + +For a basic example, pass the `id` directly to the elements that need it: + +```js +function Checkbox() { + const id = useId(); + return ( + <> + + + + ); +}; +``` + +For multiple IDs in the same component, append a suffix using the same `id`: + +```js +function NameFields() { + const id = useId(); + return ( +
+ +
+ +
+ +
+ +
+
+ ); +} +``` + +> Note: +> +> `useId` generates a string that includes the `:` token. This helps ensure that the token is unique, but is not supported in CSS selectors or APIs like `querySelectorAll`. +> +> `useId` supports an `identifierPrefix` to prevent collisions in multi-root apps. To configure, see the options for [`hydrateRoot`](/docs/react-dom-client.html#hydrateroot) and [`ReactDOMServer`](/docs/react-dom-server.html). + +## Library Hooks {#library-hooks} + +The following Hooks are provided for library authors to integrate libraries deeply into the React model, and are not typically used in application code. + +### `useSyncExternalStore` {#usesyncexternalstore} + +```js +const state = useSyncExternalStore(subscribe, getSnapshot[, getServerSnapshot]); +``` + +`useSyncExternalStore` is a hook recommended for reading and subscribing from external data sources in a way that's compatible with concurrent rendering features like selective hydration and time slicing. + +This method returns the value of the store and accepts three arguments: +- `subscribe`: function to register a callback that is called whenever the store changes. +- `getSnapshot`: function that returns the current value of the store. +- `getServerSnapshot`: function that returns the snapshot used during server rendering. + +The most basic example simply subscribes to the entire store: + +```js +const state = useSyncExternalStore(store.subscribe, store.getSnapshot); +``` + +However, you can also subscribe to a specific field: + +```js +const selectedField = useSyncExternalStore( + store.subscribe, + () => store.getSnapshot().selectedField, +); +``` + +When server rendering, you must serialize the store value used on the server, and provide it to `useSyncExternalStore`. React will use this snapshot during hydration to prevent server mismatches: + +```js +const selectedField = useSyncExternalStore( + store.subscribe, + () => store.getSnapshot().selectedField, + () => INITIAL_SERVER_SNAPSHOT.selectedField, +); +``` + +> Note: +> +> `getSnapshot` must return a cached value. If getSnapshot is called multiple times in a row, it must return the same exact value unless there was a store update in between. +> +> A shim is provided for supporting multiple React versions published as `use-sync-external-store/shim`. This shim will prefer `useSyncExternalStore` when available, and fallback to a user-space implementation when it's not. +> +> As a convenience, we also provide a version of the API with automatic support for memoizing the result of getSnapshot published as `use-sync-external-store/with-selector`. + +### `useInsertionEffect` {#useinsertioneffect} + +```js +useInsertionEffect(didUpdate); +``` + +The signature is identical to `useEffect`, but it fires synchronously _before_ all DOM mutations. Use this to inject styles into the DOM before reading layout in [`useLayoutEffect`](#uselayouteffect). Since this hook is limited in scope, this hook does not have access to refs and cannot schedule updates. + +> Note: +> +> `useInsertionEffect` should be limited to css-in-js library authors. Prefer [`useEffect`](#useeffect) or [`useLayoutEffect`](#uselayouteffect) instead. diff --git a/content/docs/implementation-notes.md b/content/docs/implementation-notes.md index b345f7d5d..ff67f062f 100644 --- a/content/docs/implementation-notes.md +++ b/content/docs/implementation-notes.md @@ -32,10 +32,11 @@ The reconciler itself doesn't have a public API. [Renderers](/docs/codebase-over Let's consider the first time you mount a component: ```js -ReactDOM.render(, rootEl); +const root = ReactDOM.createRoot(rootEl); +root.render(); ``` -React DOM will pass `` along to the reconciler. Remember that `` is a React element, that is, a description of *what* to render. You can think about it as a plain object: +`root.render` will pass `` along to the reconciler. Remember that `` is a React element, that is, a description of *what* to render. You can think about it as a plain object: ```js console.log(); @@ -236,9 +237,9 @@ This is working but still far from how the reconciler is really implemented. The The key feature of React is that you can re-render everything, and it won't recreate the DOM or reset the state: ```js -ReactDOM.render(, rootEl); +root.render(); // Should reuse the existing DOM: -ReactDOM.render(, rootEl); +root.render(); ``` However, our implementation above only knows how to mount the initial tree. It can't perform updates on it because it doesn't store all the necessary information, such as all the `publicInstance`s, or which DOM `node`s correspond to which components. @@ -412,7 +413,7 @@ If you're struggling to imagine how an internal instance tree is structured in m React DevTools tree -To complete this refactoring, we will introduce a function that mounts a complete tree into a container node, just like `ReactDOM.render()`. It returns a public instance, also like `ReactDOM.render()`: +To complete this refactoring, we will introduce a function that mounts a complete tree into a container node and a public instance: ```js function mountTree(element, containerNode) { diff --git a/content/docs/integrating-with-other-libraries.md b/content/docs/integrating-with-other-libraries.md index 5bc8b2570..83fa1768d 100644 --- a/content/docs/integrating-with-other-libraries.md +++ b/content/docs/integrating-with-other-libraries.md @@ -190,9 +190,9 @@ class Chosen extends React.Component { ## Integrating with Other View Libraries {#integrating-with-other-view-libraries} -React can be embedded into other applications thanks to the flexibility of [`ReactDOM.render()`](/docs/react-dom.html#render). +React can be embedded into other applications thanks to the flexibility of [`createRoot()`](/docs/react-dom-client.html#createRoot). -Although React is commonly used at startup to load a single root React component into the DOM, `ReactDOM.render()` can also be called multiple times for independent parts of the UI which can be as small as a button, or as large as an app. +Although React is commonly used at startup to load a single root React component into the DOM, `root.render()` can also be called multiple times for independent parts of the UI which can be as small as a button, or as large as an app. In fact, this is exactly how React is used at Facebook. This lets us write applications in React piece by piece, and combine them with our existing server-generated templates and other client-side code. @@ -216,15 +216,9 @@ function Button() { return ; } -ReactDOM.render( - ; ``` -These two code snippets are equivalent. JSX is popular syntax for describing markup in JavaScript. Many people find it familiar and helpful for writing UI code--both with React and with other libraries. You might see "markup sprinkled throughout your JavaScript" in other projects! +These two code snippets are equivalent. JSX is popular syntax for describing markup in JavaScript. Many people find it familiar and helpful for writing UI code--both with React and with other libraries. > You can play with transforming HTML markup into JSX using [this online converter](https://babeljs.io/en/repl#?babili=false&browsers=&build=&builtIns=false&spec=false&loose=false&code_lz=DwIwrgLhD2B2AEcDCAbAlgYwNYF4DeAFAJTw4B88EAFmgM4B0tAphAMoQCGETBe86WJgBMAXJQBOYJvAC-RGWQBQ8FfAAyaQYuAB6cFDhkgA&debug=false&forceAllTransforms=false&shippedProposals=false&circleciRepo=&evaluate=false&fileSize=false&timeTravel=false&sourceType=module&lineWrap=true&presets=es2015%2Creact%2Cstage-2&prettier=false&targets=&version=7.17). ### Try JSX {/*try-jsx*/} -The quickest way to try JSX in your project is to add the Babel compiler to your page's `` along with React and ReactDOM like so: - -```html {6} - +The quickest way to try JSX is to add the Babel compiler as a ` - - - - - - -``` - -Now you can use JSX in any ` +```html {3,4} + + + + + ``` -To convert **like_button.js** to use JSX: - -1. In **like_button.js**, replace +Now you can open **`like-button.js`** and replace ```js return React.createElement( @@ -177,21 +171,23 @@ return React.createElement( ); ``` -with: +with the equivalent JSX code: ```jsx -return ; +return ( + +); ``` -2. In **index.html**, add `type="text/babel"` to the like button's script tag: +It may feel a bit unusual at first to mix JS with markup, but it will grow on you! Check out [Writing Markup in JSX](/learn/writing-markup-with-jsx) for an introduction. Here is [an example HTML file with JSX](https://raw.githubusercontent.com/reactjs/reactjs.org/main/static/html/single-file-example.html) that you can download and play with. -```html - -``` + -Here is [an example HTML file with JSX](https://raw.githubusercontent.com/reactjs/reactjs.org/main/static/html/single-file-example.html) that you can download and play with. +The Babel `