From 166283d0a889b7d27d88e572758571b8d7a54e5d Mon Sep 17 00:00:00 2001 From: Lauren Tan Date: Mon, 4 Mar 2024 17:23:49 -0500 Subject: [PATCH 01/37] [wip] Add a few Rules of React docs --- .../rules/components-must-be-idempotent.md | 34 +++++++++ src/content/reference/rules/index.md | 38 ++++++++++ ...never-call-component-functions-directly.md | 7 ++ ...ver-pass-around-hooks-as-regular-values.md | 7 ++ .../rules/only-call-hooks-at-the-top-level.md | 7 ++ .../only-call-hooks-from-react-functions.md | 7 ++ .../rules/props-and-state-are-immutable.md | 69 +++++++++++++++++++ ...es-and-arguments-to-hooks-are-immutable.md | 7 ++ ...side-effects-must-run-outside-of-render.md | 68 ++++++++++++++++++ ...are-immutable-after-being-passed-to-jsx.md | 7 ++ src/sidebarReference.json | 42 +++++++++++ 11 files changed, 293 insertions(+) create mode 100644 src/content/reference/rules/components-must-be-idempotent.md create mode 100644 src/content/reference/rules/index.md create mode 100644 src/content/reference/rules/never-call-component-functions-directly.md create mode 100644 src/content/reference/rules/never-pass-around-hooks-as-regular-values.md create mode 100644 src/content/reference/rules/only-call-hooks-at-the-top-level.md create mode 100644 src/content/reference/rules/only-call-hooks-from-react-functions.md create mode 100644 src/content/reference/rules/props-and-state-are-immutable.md create mode 100644 src/content/reference/rules/return-values-and-arguments-to-hooks-are-immutable.md create mode 100644 src/content/reference/rules/side-effects-must-run-outside-of-render.md create mode 100644 src/content/reference/rules/values-are-immutable-after-being-passed-to-jsx.md diff --git a/src/content/reference/rules/components-must-be-idempotent.md b/src/content/reference/rules/components-must-be-idempotent.md new file mode 100644 index 00000000000..e3a3464d381 --- /dev/null +++ b/src/content/reference/rules/components-must-be-idempotent.md @@ -0,0 +1,34 @@ +--- +title: Components must be idempotent +--- + + +React components are assumed to always return the same output with respect to their props. This is known as [idempotency](https://stackoverflow.com/questions/1077412/what-is-an-idempotent-operation). + + +--- + +Put simply, idempotence means that you [always get the same result everytime](learn/keeping-components-pure) you run that piece of code. + +```js +function NewsFeed({ items }) { + const filteredItems = items.filter(item => item.isDisplayed === true); + return ( + + ); +} +``` + +This means that _all_ code that runs during render must also be idempotent in order for this rule to hold. For example, this line of code is not idempotent (and therefore, neither is the component) and breaks this rule: + +```js {2} +function Clock() { + return
{new Date()}
// ❌ always returns a different result! +} +``` + +`new Date()` is not idempotent as it always returns the current date and changes its result every time it's called. When you render the above component, the time displayed on the screen will stay stuck on the time that the component was rendered. Similarly, functions like `Math.random()` also aren't idempotent, because they return different results every time they're called, even when the inputs are the same. + +Try building a component that displays the time in real-time in our [challenge](learn/keeping-components-pure#challenges) to see if you follow this rule! diff --git a/src/content/reference/rules/index.md b/src/content/reference/rules/index.md new file mode 100644 index 00000000000..fb71ca08889 --- /dev/null +++ b/src/content/reference/rules/index.md @@ -0,0 +1,38 @@ +--- +title: Rules of React +--- + + +JavaScript rendering libraries and frameworks like React have constraints or "rules" to make the programming model cohesive and easy to reason about, while also helping you prevent bugs in your code. The rules also have the added benefit of creating a safe space for React to optimise and run your code more efficiently. This page lists all the Rules of React. + + +--- + +These constraints are known as the **Rules of React**. They are rules – and not just guidelines – in the sense that if they are broken, your app likely has bugs. Your code also becomes unidiomatic and harder to understand and reason about. + +We strongly recommend using Strict Mode alongside React's ESLint plugin to help your codebase follow the Rules of React. By following the Rules of React, you'll be able to find and address these bugs, as well as prepare your codebase to work out of the box with the upcoming compiler. + + +#### Why are rules necessary in React? {/*why-are-rules-necessary-in-react*/} + +You can think of React's constraints like the grammatical rules and patterns of languages: they constrain what we can do with words, so that we can correctly and efficiently communicate our thoughts. These rules have been used in the design of all of React's features over the years. React's Strict Mode enforces several of these rules at runtime in DEV mode, and with the release of React's upcoming compiler, more rules will now be statically checked to help you find more bugs as well as allow for correct optimisation of your code. + +The upcoming compiler automatically makes writing simple and intuitive React code run efficiently by default. It understands JavaScript semantics and relies on the Rules of React in order to correctly and precisely optimise your codebase. + +However, while the compiler can detect most cases of Rules of React breakages, because of the dynamic and flexible nature of JavaScript, it is not possible to exhaustively detect all edge cases. Future iterations of the compiler will continue to improve its static detection, but it's important to understand and follow the Rules of React so that your code continues to run correctly and efficiently even if it's not possible to statically detect. + +If the Rules of React are broken, at best the upcoming compiler will skip optimising the components that broke the rules; and at worst, if the breakage is not statically detectable the compiled code may break in unexpected ways. + + +--- + +## Rules {/*rules*/} +* [Side effects must run outside of render](/reference/rules/side-effects-must-run-outside-of-render): React can start and stop rendering components multiple times to create the best possible user experience. +* [Components must be idempotent](/reference/rules/components-must-be-idempotent): React components are assumed to always return the same output with respect to their props. +* [Props and State are immutable](/reference/rules/props-and-state-are-immutable): A component's props and state are immutable "snapshots" with respect to a single render. +* [Never call component functions directly](/reference/rules/never-call-component-functions-directly): TODO React must orchestrate rendering +* [Never pass around Hooks as regular values](/reference/rules/never-pass-around-hooks-as-regular-values): TODO +* [Only call Hooks at the top level](/reference/rules/only-call-hooks-at-the-top-level): TODO +* [Only call Hooks from React functions](/reference/rules/only-call-hooks-from-react-functions): TODO +* [Values are immutable after being passed to JSX](/reference/rules/values-are-immutable-after-being-passed-to-jsx): TODO +* [Return values and arguments to Hooks are immutable](/reference/rules/return-values-and-arguments-to-hooks-are-immutable): TODO diff --git a/src/content/reference/rules/never-call-component-functions-directly.md b/src/content/reference/rules/never-call-component-functions-directly.md new file mode 100644 index 00000000000..6f2984d096f --- /dev/null +++ b/src/content/reference/rules/never-call-component-functions-directly.md @@ -0,0 +1,7 @@ +--- +title: Never call component functions directly +--- + + +TODO + \ No newline at end of file diff --git a/src/content/reference/rules/never-pass-around-hooks-as-regular-values.md b/src/content/reference/rules/never-pass-around-hooks-as-regular-values.md new file mode 100644 index 00000000000..31e9e8d596c --- /dev/null +++ b/src/content/reference/rules/never-pass-around-hooks-as-regular-values.md @@ -0,0 +1,7 @@ +--- +title: Never pass around hooks as regular values +--- + + +TODO + \ No newline at end of file diff --git a/src/content/reference/rules/only-call-hooks-at-the-top-level.md b/src/content/reference/rules/only-call-hooks-at-the-top-level.md new file mode 100644 index 00000000000..9cad7b75d00 --- /dev/null +++ b/src/content/reference/rules/only-call-hooks-at-the-top-level.md @@ -0,0 +1,7 @@ +--- +title: Only call Hooks at the top level +--- + + +TODO + \ No newline at end of file diff --git a/src/content/reference/rules/only-call-hooks-from-react-functions.md b/src/content/reference/rules/only-call-hooks-from-react-functions.md new file mode 100644 index 00000000000..9123802260d --- /dev/null +++ b/src/content/reference/rules/only-call-hooks-from-react-functions.md @@ -0,0 +1,7 @@ +--- +title: Only call Hooks from React functions +--- + + +TODO + \ No newline at end of file diff --git a/src/content/reference/rules/props-and-state-are-immutable.md b/src/content/reference/rules/props-and-state-are-immutable.md new file mode 100644 index 00000000000..b8dbedcff9d --- /dev/null +++ b/src/content/reference/rules/props-and-state-are-immutable.md @@ -0,0 +1,69 @@ +--- +title: Props and State are immutable +--- + + +A component's props and state are immutable [snapshots](learn/state-as-a-snapshot) with respect to a single render. + + +--- + +You can think of the props and state values as snapshots that are updated after rendering. For this reason, you don't modify the props or state variables directly: instead you use the setter function provided to you to tell React that state needs to update the next time the component is rendered. + +## Props {/*props*/} +When followed, this rule allows React to understand that values that flow from props aren't mutated when they're passed as arguments to functions. Mutating props may also indicate a bug in your app – changing values on the props object doesn't cause the component to update, leaving your users with an outdated UI. + +```js {2} +function Post({ item }) { + item.url = new Url(item.url, base); // ❌ never mutate props directly + return {item.title}; +} +``` + +```js {2} +function Post({ item }) { + const url = new Url(item.url, base); // ✅ make a copy instead + return {item.title}; +} +``` + +## State {/*state*/} +`useState` returns an immutable tuple of the state variable and a setter to update that state. + +```js +const [stateVariable, setter] = useState(0); +``` + +Rather than updating the state variable in-place, we need to update it using the setter function that is returned by `useState`. Changing values on the state variable doesn't cause the component to update, leaving your users with an outdated UI. Using the setter function informs React that the state has changed, and that we need to queue a re-render to update the UI. + +```js {5} +function Counter() { + const [count, setCount] = useState(0); + + function handleClick() { + count = count + 1; // ❌ never mutate state directly + } + + return ( + + ); +} +``` + +```js {5} +function Counter() { + const [count, setCount] = useState(0); + + function handleClick() { + setCount(count + 1); // ✅ use the setter function returned by useState + } + + return ( + + ); +} +``` \ No newline at end of file diff --git a/src/content/reference/rules/return-values-and-arguments-to-hooks-are-immutable.md b/src/content/reference/rules/return-values-and-arguments-to-hooks-are-immutable.md new file mode 100644 index 00000000000..586b0a62621 --- /dev/null +++ b/src/content/reference/rules/return-values-and-arguments-to-hooks-are-immutable.md @@ -0,0 +1,7 @@ +--- +title: Return values and arguments to Hooks are immutable +--- + + +TODO + \ No newline at end of file diff --git a/src/content/reference/rules/side-effects-must-run-outside-of-render.md b/src/content/reference/rules/side-effects-must-run-outside-of-render.md new file mode 100644 index 00000000000..bd1db1f8eac --- /dev/null +++ b/src/content/reference/rules/side-effects-must-run-outside-of-render.md @@ -0,0 +1,68 @@ +--- +title: Side effects must run outside of render +--- + + +Side effects should not run in render, as React can render components multiple times to create the best possible user experience. + + +--- + +While render must be kept pure, side effects are necessary at some point in order for your app to do anything interesting, like showing something on the screen! The key point of this rule is that side effects should not run in render, as React can render components multiple times. In most cases, you'll use event handlers to handle side effects. For example, you might have an event handler that displays a confirmation dialog after the user clicks a button. Using an event handler explicitly tells React that this code doesn't need to run during render, keeping render pure. If you've exhausted all options – and only as a last resort – you can also handle side effects using `useEffect`. + + +#### Why does render need to be pure? {/*why-does-render-need-to-be-pure*/} +UI libraries like React take care of when your code runs for you so that your application has great user experience. React is declarative: you tell React what to render in your component's logic, and React will figure out how best to display it to your user! + +When render is kept pure, React can understand how to prioritise which updates are most important for the user to see first. This is made possible because of render purity: since components don't have side effects in render, React can pause rendering components that aren't as important to update, and only come back to them later when it's needed. + +Concretely, this means that rendering logic can be run multiple times in a way that allows React to give your user a pleasant user experience. However, if your component has an untracked side effect – like modifying the value of a global variable during render – when React runs your rendering code again, your side effects will be triggered in a way that won't match what you want. This often leads to unexpected bugs that can degrade how your users experience your app. + + +For example, values that aren't created inside of a component shouldn't be mutated: + +```js +let items = []; // created outside of the component +function MyComponent({ seen }) { + items.push(seen); // ❌ mutated inside the component +} +``` + + +In general, mutation is not idiomatic in React. However, local mutation is absolutely fine: + +```js +function FriendList({ friends }) { + let items = []; // ✅ locally created and mutated + for (let i = 0; i < friends.length; i++) { + let friend = friends[i]; + items.push( + + ); + } + return
{items}
; +} +``` + +We created `items` while rendering and no other component "saw" it so we can mutate it as much as we like before handing it off as part of the render result. This component has no observable side effects, even though it mutates `items`. There is no need to contort your code to avoid local mutation. + +Similarly, lazy initialization is fine despite not being fully "pure": + +```js +function ExpenseForm() { + // Fine if it doesn't affect other components: + SuperCalculator.initializeIfNotReady(); + + // Continue rendering... +} +``` + +As long as calling a component multiple times is safe and doesn’t affect the rendering of other components, React doesn’t care if it’s 100% pure in the strict functional programming sense of the word. + +That said, side effects that are directly visible to the user are not allowed in the render logic of React components. In other words, merely calling a component function shouldn’t by itself produce a change on the screen. + +```js +function ProductDetailPage({ product }) { + document.window.title = product.title; // ❌ +} +``` \ No newline at end of file diff --git a/src/content/reference/rules/values-are-immutable-after-being-passed-to-jsx.md b/src/content/reference/rules/values-are-immutable-after-being-passed-to-jsx.md new file mode 100644 index 00000000000..8efad347eca --- /dev/null +++ b/src/content/reference/rules/values-are-immutable-after-being-passed-to-jsx.md @@ -0,0 +1,7 @@ +--- +title: Values are immutable after being passed to JSX +--- + + +TODO + \ No newline at end of file diff --git a/src/sidebarReference.json b/src/sidebarReference.json index e02fd94569f..4a1636746d7 100644 --- a/src/sidebarReference.json +++ b/src/sidebarReference.json @@ -10,6 +10,48 @@ "title": "Overview", "path": "/reference/react" }, + { + "title": "Rules of React", + "path": "/reference/rules", + "routes": [ + { + "title": "Side effects must run outside of render", + "path": "/reference/rules/side-effects-must-run-outside-of-render" + }, + { + "title": "Components must be idempotent", + "path": "/reference/rules/components-must-be-idempotent" + }, + { + "title": "Props and State are immutable", + "path": "/reference/rules/props-and-state-are-immutable" + }, + { + "title": "Never call component functions directly", + "path": "/reference/rules/never-call-component-functions-directly" + }, + { + "title": "Never pass around Hooks as regular values", + "path": "/reference/rules/never-pass-around-hooks-as-regular-values" + }, + { + "title": "Only call Hooks at the top level", + "path": "/reference/rules/only-call-hooks-at-the-top-level" + }, + { + "title": "Only call Hooks from React functions", + "path": "/reference/rules/only-call-hooks-from-react-functions" + }, + { + "title": "Values are immutable after being passed to JSX", + "path": "/reference/rules/values-are-immutable-after-being-passed-to-jsx" + }, + { + "title": "Return values and arguments to Hooks are immutable", + "path": "/reference/rules/return-values-and-arguments-to-hooks-are-immutable" + } + ] + }, { "title": "Hooks", "path": "/reference/react/hooks", From d3f678f205c12b379d18ec3f1fcc9467873859a6 Mon Sep 17 00:00:00 2001 From: Lauren Tan Date: Tue, 5 Mar 2024 16:00:44 -0500 Subject: [PATCH 02/37] More updates --- .../rules/components-must-be-idempotent.md | 6 +- src/content/reference/rules/index.md | 6 +- ...never-call-component-functions-directly.md | 33 ++++++++++- ...ver-pass-around-hooks-as-regular-values.md | 8 ++- .../rules/props-and-state-are-immutable.md | 10 ++-- ...side-effects-must-run-outside-of-render.md | 55 ++++++++++--------- src/sidebarReference.json | 2 +- 7 files changed, 80 insertions(+), 40 deletions(-) diff --git a/src/content/reference/rules/components-must-be-idempotent.md b/src/content/reference/rules/components-must-be-idempotent.md index e3a3464d381..c48bf822723 100644 --- a/src/content/reference/rules/components-must-be-idempotent.md +++ b/src/content/reference/rules/components-must-be-idempotent.md @@ -10,8 +10,9 @@ React components are assumed to always return the same output with respect to th Put simply, idempotence means that you [always get the same result everytime](learn/keeping-components-pure) you run that piece of code. -```js +```js {3} function NewsFeed({ items }) { + // ✅ Array.filter doesn't mutate `items` const filteredItems = items.filter(item => item.isDisplayed === true); return (
    @@ -25,7 +26,8 @@ This means that _all_ code that runs during render must also be idempotent in or ```js {2} function Clock() { - return
    {new Date()}
    // ❌ always returns a different result! + const date = new Date(); // ❌ always returns a different result! + return
    {date}
    } ``` diff --git a/src/content/reference/rules/index.md b/src/content/reference/rules/index.md index fb71ca08889..9ce47d7d898 100644 --- a/src/content/reference/rules/index.md +++ b/src/content/reference/rules/index.md @@ -3,7 +3,7 @@ title: Rules of React --- -JavaScript rendering libraries and frameworks like React have constraints or "rules" to make the programming model cohesive and easy to reason about, while also helping you prevent bugs in your code. The rules also have the added benefit of creating a safe space for React to optimise and run your code more efficiently. This page lists all the Rules of React. +JavaScript rendering libraries and frameworks like React have constraints or "rules" to make the programming model cohesive and easy to reason about, while also helping you prevent bugs in your code. The rules also have the added benefit of creating a safe space for React to optimize and run your code more efficiently. This page lists all the Rules of React. --- @@ -29,8 +29,8 @@ If the Rules of React are broken, at best the upcoming compiler will skip optimi ## Rules {/*rules*/} * [Side effects must run outside of render](/reference/rules/side-effects-must-run-outside-of-render): React can start and stop rendering components multiple times to create the best possible user experience. * [Components must be idempotent](/reference/rules/components-must-be-idempotent): React components are assumed to always return the same output with respect to their props. -* [Props and State are immutable](/reference/rules/props-and-state-are-immutable): A component's props and state are immutable "snapshots" with respect to a single render. -* [Never call component functions directly](/reference/rules/never-call-component-functions-directly): TODO React must orchestrate rendering +* [Props and state are immutable](/reference/rules/props-and-state-are-immutable): A component's props and state are immutable "snapshots" with respect to a single render. +* [Never call component functions directly](/reference/rules/never-call-component-functions-directly): Components should only be used in JSX. Don't call them as regular functions. * [Never pass around Hooks as regular values](/reference/rules/never-pass-around-hooks-as-regular-values): TODO * [Only call Hooks at the top level](/reference/rules/only-call-hooks-at-the-top-level): TODO * [Only call Hooks from React functions](/reference/rules/only-call-hooks-from-react-functions): TODO diff --git a/src/content/reference/rules/never-call-component-functions-directly.md b/src/content/reference/rules/never-call-component-functions-directly.md index 6f2984d096f..967192b06fe 100644 --- a/src/content/reference/rules/never-call-component-functions-directly.md +++ b/src/content/reference/rules/never-call-component-functions-directly.md @@ -3,5 +3,34 @@ title: Never call component functions directly --- -TODO - \ No newline at end of file +Components should only be used in JSX. Don't call them as regular functions. + + +--- + +React takes care of _when_ your code runs for you so that your application has a great user experience. It is declarative: you tell React what to render in your component's logic, and React will figure out how best to display it to your user. + +In order to do this, React must decide when your component function is called during rendering. In React, you do this using JSX. + +```js {2} +function BlogPost() { + return
    ; // ✅ Only use components in JSX +} +``` + +```js {3} +function BlogPost() { + ReactDOM.render( + Layout({ children: Article() }), // ❌ Never call them directly + /* ... */ + ); +} +``` + +Letting React orchestrate rendering allows a number of benefits: + +* **Components become more than functions.** React can augment them with features like _local state_ through Hooks that are tied to the component's identity in the tree. +* **Component types participate in reconcilation.** By letting React call your components, you also tell it more about the conceptual structure of your tree. For example, when you move from rendering `` to the `` page, React won’t attempt to re-use them. +* **React can enhance your user experience.** For example, it can let the browser do some work between component calls so that re-rendering a large component tree doesn’t block the main thread. +* **A better debugging story.** If components are first-class citizens that the library is aware of, we can build rich developer tools for introspection in development. +* **More efficient reconcilation.** React can decide exactly which components in the tree need re-rendering and skip over the ones that don't. That makes your app faster and more snappy. diff --git a/src/content/reference/rules/never-pass-around-hooks-as-regular-values.md b/src/content/reference/rules/never-pass-around-hooks-as-regular-values.md index 31e9e8d596c..8d758204569 100644 --- a/src/content/reference/rules/never-pass-around-hooks-as-regular-values.md +++ b/src/content/reference/rules/never-pass-around-hooks-as-regular-values.md @@ -3,5 +3,9 @@ title: Never pass around hooks as regular values --- -TODO - \ No newline at end of file +Hooks should only be called inside of components. Never pass it around as a regular value. + + +--- + +Hooks ... \ No newline at end of file diff --git a/src/content/reference/rules/props-and-state-are-immutable.md b/src/content/reference/rules/props-and-state-are-immutable.md index b8dbedcff9d..a52d0f2c99e 100644 --- a/src/content/reference/rules/props-and-state-are-immutable.md +++ b/src/content/reference/rules/props-and-state-are-immutable.md @@ -1,17 +1,17 @@ --- -title: Props and State are immutable +title: Props and state are immutable --- -A component's props and state are immutable [snapshots](learn/state-as-a-snapshot) with respect to a single render. +A component's props and state are immutable [snapshots](learn/state-as-a-snapshot) with respect to a single render. Never mutate them directly. --- -You can think of the props and state values as snapshots that are updated after rendering. For this reason, you don't modify the props or state variables directly: instead you use the setter function provided to you to tell React that state needs to update the next time the component is rendered. +You can think of the props and state values as snapshots that are updated after rendering. For this reason, you don't modify the props or state variables directly: instead you pass new props, or use the setter function provided to you to tell React that state needs to update the next time the component is rendered. ## Props {/*props*/} -When followed, this rule allows React to understand that values that flow from props aren't mutated when they're passed as arguments to functions. Mutating props may also indicate a bug in your app – changing values on the props object doesn't cause the component to update, leaving your users with an outdated UI. +When followed, this rule allows React to understand that values that flow from props aren't mutated when they're passed as arguments to functions, allowing certain optimizations to be made. Mutating props may also indicate a bug in your app – changing values on the props object doesn't cause the component to update, leaving your users with an outdated UI. ```js {2} function Post({ item }) { @@ -28,7 +28,7 @@ function Post({ item }) { ``` ## State {/*state*/} -`useState` returns an immutable tuple of the state variable and a setter to update that state. +`useState` returns the state variable and a setter to update that state. ```js const [stateVariable, setter] = useState(0); diff --git a/src/content/reference/rules/side-effects-must-run-outside-of-render.md b/src/content/reference/rules/side-effects-must-run-outside-of-render.md index bd1db1f8eac..de6c494d899 100644 --- a/src/content/reference/rules/side-effects-must-run-outside-of-render.md +++ b/src/content/reference/rules/side-effects-must-run-outside-of-render.md @@ -8,30 +8,23 @@ Side effects should not run in render, as React can render components multiple t --- -While render must be kept pure, side effects are necessary at some point in order for your app to do anything interesting, like showing something on the screen! The key point of this rule is that side effects should not run in render, as React can render components multiple times. In most cases, you'll use event handlers to handle side effects. For example, you might have an event handler that displays a confirmation dialog after the user clicks a button. Using an event handler explicitly tells React that this code doesn't need to run during render, keeping render pure. If you've exhausted all options – and only as a last resort – you can also handle side effects using `useEffect`. +While render must be kept pure, side effects are necessary at some point in order for your app to do anything interesting, like showing something on the screen! The key point of this rule is that side effects should not run in render, as React can render components multiple times. In most cases, you'll use [event handlers](learn/responding-to-events) to handle side effects. + +For example, you might have an event handler that displays a confirmation dialog after the user clicks a button. Using an event handler explicitly tells React that this code doesn't need to run during render, keeping render pure. If you've exhausted all options – and only as a last resort – you can also handle side effects using `useEffect`. #### Why does render need to be pure? {/*why-does-render-need-to-be-pure*/} -UI libraries like React take care of when your code runs for you so that your application has great user experience. React is declarative: you tell React what to render in your component's logic, and React will figure out how best to display it to your user! +UI libraries like React take care of when your code runs for you so that your application has a great user experience. React is declarative: you tell React what to render in your component's logic, and React will figure out how best to display it to your user! -When render is kept pure, React can understand how to prioritise which updates are most important for the user to see first. This is made possible because of render purity: since components don't have side effects in render, React can pause rendering components that aren't as important to update, and only come back to them later when it's needed. +When render is kept pure, React can understand how to prioritize which updates are most important for the user to see first. This is made possible because of render purity: since components don't have side effects in render, React can pause rendering components that aren't as important to update, and only come back to them later when it's needed. Concretely, this means that rendering logic can be run multiple times in a way that allows React to give your user a pleasant user experience. However, if your component has an untracked side effect – like modifying the value of a global variable during render – when React runs your rendering code again, your side effects will be triggered in a way that won't match what you want. This often leads to unexpected bugs that can degrade how your users experience your app. -For example, values that aren't created inside of a component shouldn't be mutated: - -```js -let items = []; // created outside of the component -function MyComponent({ seen }) { - items.push(seen); // ❌ mutated inside the component -} -``` - +## When is it okay to have mutation? {/*mutation*/} +One common example of a side effect is mutation, which refers to changing the value of a non-[primitive](https://developer.mozilla.org/en-US/docs/Glossary/Primitive) value. In general, while mutation is not idiomatic in React, _local_ mutation is absolutely fine: -In general, mutation is not idiomatic in React. However, local mutation is absolutely fine: - -```js +```js {2} function FriendList({ friends }) { let items = []; // ✅ locally created and mutated for (let i = 0; i < friends.length; i++) { @@ -44,25 +37,37 @@ function FriendList({ friends }) { } ``` -We created `items` while rendering and no other component "saw" it so we can mutate it as much as we like before handing it off as part of the render result. This component has no observable side effects, even though it mutates `items`. There is no need to contort your code to avoid local mutation. +There is no need to contort your code to avoid local mutation. In particular, [`Array.map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map) could also be used here for brevity, but there is nothing wrong with creating a local array and then pushing items into it during render. + +Even though it looks like we are mutating `items`, the key point to note is that this code only does so locally – the mutation isn't "remembered" when the component is rendered again. In other words, `items` only stays around as long as the component does. Because `items` is always recreated every time `` is rendered, the component will always returns the same result. + +On the other hand, if `items` was created outside of the component, it holds on to its previous values and remembers changes: + +```js {1} +let items = []; // ❌ created outside of the component +function FriendList({ friends }) { + // Push `friends` into `items`... + return
    {items}
    ; +} +``` + +When `` runs again, we will continue appending `friends` to `items` every time that component is run, leading to multiple duplicated results. This version of `` has observable side effects during render and **breaks the rule**. Similarly, lazy initialization is fine despite not being fully "pure": -```js +```js {2} function ExpenseForm() { - // Fine if it doesn't affect other components: - SuperCalculator.initializeIfNotReady(); - + SuperCalculator.initializeIfNotReady(); // ✅ Fine if it doesn't affect other components // Continue rendering... } ``` -As long as calling a component multiple times is safe and doesn’t affect the rendering of other components, React doesn’t care if it’s 100% pure in the strict functional programming sense of the word. - -That said, side effects that are directly visible to the user are not allowed in the render logic of React components. In other words, merely calling a component function shouldn’t by itself produce a change on the screen. +Side effects that are directly visible to the user are not allowed in the render logic of React components. In other words, merely calling a component function shouldn’t by itself produce a change on the screen. -```js +```js {2} function ProductDetailPage({ product }) { document.window.title = product.title; // ❌ } -``` \ No newline at end of file +``` + +As long as calling a component multiple times is safe and doesn’t affect the rendering of other components, React doesn’t care if it’s 100% pure in the strict functional programming sense of the word. It is more important that [components must be idempotent](/reference/rules/components-must-be-idempotent). diff --git a/src/sidebarReference.json b/src/sidebarReference.json index 4a1636746d7..926902efd0c 100644 --- a/src/sidebarReference.json +++ b/src/sidebarReference.json @@ -23,7 +23,7 @@ "path": "/reference/rules/components-must-be-idempotent" }, { - "title": "Props and State are immutable", + "title": "Props and state are immutable", "path": "/reference/rules/props-and-state-are-immutable" }, { From 2c72c1385f5fdf825ec56ef95e9ec7247c49638e Mon Sep 17 00:00:00 2001 From: Lauren Tan Date: Wed, 6 Mar 2024 16:37:39 -0500 Subject: [PATCH 03/37] First draft of "hooks as regular values" --- src/content/reference/rules/index.md | 10 ++-- ...ver-pass-around-hooks-as-regular-values.md | 49 ++++++++++++++++++- 2 files changed, 52 insertions(+), 7 deletions(-) diff --git a/src/content/reference/rules/index.md b/src/content/reference/rules/index.md index 9ce47d7d898..aeb751731fb 100644 --- a/src/content/reference/rules/index.md +++ b/src/content/reference/rules/index.md @@ -15,13 +15,11 @@ We strongly recommend using Strict Mode alongside React's ESLint plugin to help #### Why are rules necessary in React? {/*why-are-rules-necessary-in-react*/} -You can think of React's constraints like the grammatical rules and patterns of languages: they constrain what we can do with words, so that we can correctly and efficiently communicate our thoughts. These rules have been used in the design of all of React's features over the years. React's Strict Mode enforces several of these rules at runtime in DEV mode, and with the release of React's upcoming compiler, more rules will now be statically checked to help you find more bugs as well as allow for correct optimisation of your code. +You can think of React's constraints like the grammatical rules and patterns of languages: they constrain what we can do with words, so that we can correctly and efficiently communicate our thoughts. -The upcoming compiler automatically makes writing simple and intuitive React code run efficiently by default. It understands JavaScript semantics and relies on the Rules of React in order to correctly and precisely optimise your codebase. +These rules have been used in the design of all of React's features over the years. React's Strict Mode enforces several of these rules at runtime in DEV mode, and with the release of React's upcoming compiler, more rules will now be statically checked to help you find more bugs as well as allow for correct optimisation of your code. -However, while the compiler can detect most cases of Rules of React breakages, because of the dynamic and flexible nature of JavaScript, it is not possible to exhaustively detect all edge cases. Future iterations of the compiler will continue to improve its static detection, but it's important to understand and follow the Rules of React so that your code continues to run correctly and efficiently even if it's not possible to statically detect. - -If the Rules of React are broken, at best the upcoming compiler will skip optimising the components that broke the rules; and at worst, if the breakage is not statically detectable the compiled code may break in unexpected ways. +The Rules of React are proven rules used at companies like Meta that help you maintain an application and codebase that scales with you. When followed, your codebase becomes easier to understand and maintain, is less buggy, and helps React ensure your code runs efficiently by default. --- @@ -31,7 +29,7 @@ If the Rules of React are broken, at best the upcoming compiler will skip optimi * [Components must be idempotent](/reference/rules/components-must-be-idempotent): React components are assumed to always return the same output with respect to their props. * [Props and state are immutable](/reference/rules/props-and-state-are-immutable): A component's props and state are immutable "snapshots" with respect to a single render. * [Never call component functions directly](/reference/rules/never-call-component-functions-directly): Components should only be used in JSX. Don't call them as regular functions. -* [Never pass around Hooks as regular values](/reference/rules/never-pass-around-hooks-as-regular-values): TODO +* [Never pass around Hooks as regular values](/reference/rules/never-pass-around-hooks-as-regular-values): Hooks should only be called inside of components. Never pass it around as a regular value. * [Only call Hooks at the top level](/reference/rules/only-call-hooks-at-the-top-level): TODO * [Only call Hooks from React functions](/reference/rules/only-call-hooks-from-react-functions): TODO * [Values are immutable after being passed to JSX](/reference/rules/values-are-immutable-after-being-passed-to-jsx): TODO diff --git a/src/content/reference/rules/never-pass-around-hooks-as-regular-values.md b/src/content/reference/rules/never-pass-around-hooks-as-regular-values.md index 8d758204569..9a69d84c1f2 100644 --- a/src/content/reference/rules/never-pass-around-hooks-as-regular-values.md +++ b/src/content/reference/rules/never-pass-around-hooks-as-regular-values.md @@ -8,4 +8,51 @@ Hooks should only be called inside of components. Never pass it around as a regu --- -Hooks ... \ No newline at end of file +Hooks allow you to augment a component with React features. They should always be called as a function, and never passed around as a regular value. This enables _local reasoning_, or the ability for developers to understand everything a component can do by looking at that component in isolation. + +```js {2} +function ChatInput() { + const useDataWithLogging = withLogging(useData); // ❌ + const data = useDataWithLogging(); +} +``` + +```js {2} +function ChatInput() { + const data = useDataWithLogging(); // ✅ +} +``` + +Hooks should be immutable and not be mutated. Instead of mutating a hook dynamically, create a static version of the hook with the desired functionality. + +```js +function useDataWithLogging() { + // ... Logic should go in here +} +``` + +Hooks should also not be dynamically used: for example, instead of doing dependency injection in a component: + +```js {2} +function ChatInput() { + return + ); +} +``` + +```js {5} +function Counter() { + const [count, setCount] = useState(0); + + function handleClick() { + setCount(count + 1); // ✅ use the setter function returned by useState + } + + return ( + + ); +} +``` \ No newline at end of file diff --git a/src/content/reference/rules/components-must-be-idempotent.md b/src/content/reference/rules/components-must-be-idempotent.md deleted file mode 100644 index c48bf822723..00000000000 --- a/src/content/reference/rules/components-must-be-idempotent.md +++ /dev/null @@ -1,36 +0,0 @@ ---- -title: Components must be idempotent ---- - - -React components are assumed to always return the same output with respect to their props. This is known as [idempotency](https://stackoverflow.com/questions/1077412/what-is-an-idempotent-operation). - - ---- - -Put simply, idempotence means that you [always get the same result everytime](learn/keeping-components-pure) you run that piece of code. - -```js {3} -function NewsFeed({ items }) { - // ✅ Array.filter doesn't mutate `items` - const filteredItems = items.filter(item => item.isDisplayed === true); - return ( -
      - {filteredItems.map(item =>
    • {item.text}
    • } -
    - ); -} -``` - -This means that _all_ code that runs during render must also be idempotent in order for this rule to hold. For example, this line of code is not idempotent (and therefore, neither is the component) and breaks this rule: - -```js {2} -function Clock() { - const date = new Date(); // ❌ always returns a different result! - return
    {date}
    -} -``` - -`new Date()` is not idempotent as it always returns the current date and changes its result every time it's called. When you render the above component, the time displayed on the screen will stay stuck on the time that the component was rendered. Similarly, functions like `Math.random()` also aren't idempotent, because they return different results every time they're called, even when the inputs are the same. - -Try building a component that displays the time in real-time in our [challenge](learn/keeping-components-pure#challenges) to see if you follow this rule! diff --git a/src/content/reference/rules/index.md b/src/content/reference/rules/index.md index b9fc84bf6f7..2bddc2b1bf2 100644 --- a/src/content/reference/rules/index.md +++ b/src/content/reference/rules/index.md @@ -22,15 +22,7 @@ These rules have been used in the design of all of React's features over the yea The Rules of React are proven rules used at companies like Meta that help you maintain an application and codebase that scales with you. When followed, your codebase becomes easier to understand and maintain, is less buggy, and helps React ensure your code runs efficiently by default. ---- - -## Rules {/*rules*/} -* [Side effects must run outside of render](/reference/rules/side-effects-must-run-outside-of-render): React can start and stop rendering components multiple times to create the best possible user experience. -* [Components must be idempotent](/reference/rules/components-must-be-idempotent): React components are assumed to always return the same output with respect to their props. -* [Props and state are immutable](/reference/rules/props-and-state-are-immutable): A component's props and state are immutable "snapshots" with respect to a single render. -* [Never call component functions directly](/reference/rules/never-call-component-functions-directly): Components should only be used in JSX. Don't call them as regular functions. -* [Never pass around Hooks as regular values](/reference/rules/never-pass-around-hooks-as-regular-values): Hooks should only be called inside of components. Never pass it around as a regular value. -* [Only call Hooks at the top level](/reference/rules/only-call-hooks-at-the-top-level): Don’t call Hooks inside loops, conditions, or nested functions. Instead, always use Hooks at the top level of your React function, before any early returns. -* [Only call Hooks from React functions](/reference/rules/only-call-hooks-from-react-functions): Don’t call Hooks from regular JavaScript functions. -* [Values are immutable after being passed to JSX](/reference/rules/values-are-immutable-after-being-passed-to-jsx): Don't mutate values after they've been used in JSX. Move the mutation before the JSX is created. -* [Return values and arguments to Hooks are immutable](/reference/rules/return-values-and-arguments-to-hooks-are-immutable): TODO +* [Components and Hooks must be pure](/reference/rules/components-and-hooks-must-be-pure) +* [React orchestrates Components and Hooks](/reference/rules/react-orchestrates-components-and-hooks) +* [Rules of Hooks](/reference/rules/rules-of-hooks) +* [Rules of JSX](/reference/rules/rules-of-jsx) \ No newline at end of file diff --git a/src/content/reference/rules/never-call-component-functions-directly.md b/src/content/reference/rules/never-call-component-functions-directly.md deleted file mode 100644 index 967192b06fe..00000000000 --- a/src/content/reference/rules/never-call-component-functions-directly.md +++ /dev/null @@ -1,36 +0,0 @@ ---- -title: Never call component functions directly ---- - - -Components should only be used in JSX. Don't call them as regular functions. - - ---- - -React takes care of _when_ your code runs for you so that your application has a great user experience. It is declarative: you tell React what to render in your component's logic, and React will figure out how best to display it to your user. - -In order to do this, React must decide when your component function is called during rendering. In React, you do this using JSX. - -```js {2} -function BlogPost() { - return
    ; // ✅ Only use components in JSX -} -``` - -```js {3} -function BlogPost() { - ReactDOM.render( - Layout({ children: Article() }), // ❌ Never call them directly - /* ... */ - ); -} -``` - -Letting React orchestrate rendering allows a number of benefits: - -* **Components become more than functions.** React can augment them with features like _local state_ through Hooks that are tied to the component's identity in the tree. -* **Component types participate in reconcilation.** By letting React call your components, you also tell it more about the conceptual structure of your tree. For example, when you move from rendering `` to the `` page, React won’t attempt to re-use them. -* **React can enhance your user experience.** For example, it can let the browser do some work between component calls so that re-rendering a large component tree doesn’t block the main thread. -* **A better debugging story.** If components are first-class citizens that the library is aware of, we can build rich developer tools for introspection in development. -* **More efficient reconcilation.** React can decide exactly which components in the tree need re-rendering and skip over the ones that don't. That makes your app faster and more snappy. diff --git a/src/content/reference/rules/never-pass-around-hooks-as-regular-values.md b/src/content/reference/rules/never-pass-around-hooks-as-regular-values.md deleted file mode 100644 index 9a69d84c1f2..00000000000 --- a/src/content/reference/rules/never-pass-around-hooks-as-regular-values.md +++ /dev/null @@ -1,58 +0,0 @@ ---- -title: Never pass around hooks as regular values ---- - - -Hooks should only be called inside of components. Never pass it around as a regular value. - - ---- - -Hooks allow you to augment a component with React features. They should always be called as a function, and never passed around as a regular value. This enables _local reasoning_, or the ability for developers to understand everything a component can do by looking at that component in isolation. - -```js {2} -function ChatInput() { - const useDataWithLogging = withLogging(useData); // ❌ - const data = useDataWithLogging(); -} -``` - -```js {2} -function ChatInput() { - const data = useDataWithLogging(); // ✅ -} -``` - -Hooks should be immutable and not be mutated. Instead of mutating a hook dynamically, create a static version of the hook with the desired functionality. - -```js -function useDataWithLogging() { - // ... Logic should go in here -} -``` - -Hooks should also not be dynamically used: for example, instead of doing dependency injection in a component: - -```js {2} -function ChatInput() { - return - ); -} -``` - -```js {5} -function Counter() { - const [count, setCount] = useState(0); - - function handleClick() { - setCount(count + 1); // ✅ use the setter function returned by useState - } - - return ( - - ); -} -``` \ No newline at end of file diff --git a/src/content/reference/rules/react-orchestrates-components-and-hooks.md b/src/content/reference/rules/react-orchestrates-components-and-hooks.md new file mode 100644 index 00000000000..8d9edd20522 --- /dev/null +++ b/src/content/reference/rules/react-orchestrates-components-and-hooks.md @@ -0,0 +1,95 @@ +--- +title: React orchestrates Components and Hooks +--- + + +TODO + + + + +--- + +## Never call component functions directly {/*never-call-component-functions-directly*/} +Components should only be used in JSX. Don't call them as regular functions. + +React takes care of _when_ your code runs for you so that your application has a great user experience. It is declarative: you tell React what to render in your component's logic, and React will figure out how best to display it to your user. + +In order to do this, React must decide when your component function is called during rendering. In React, you do this using JSX. + +```js {2} +function BlogPost() { + return
    ; // ✅ Only use components in JSX +} +``` + +```js {3} +function BlogPost() { + ReactDOM.render( + Layout({ children: Article() }), // ❌ Never call them directly + /* ... */ + ); +} +``` + +Letting React orchestrate rendering allows a number of benefits: + +* **Components become more than functions.** React can augment them with features like _local state_ through Hooks that are tied to the component's identity in the tree. +* **Component types participate in reconcilation.** By letting React call your components, you also tell it more about the conceptual structure of your tree. For example, when you move from rendering `` to the `` page, React won’t attempt to re-use them. +* **React can enhance your user experience.** For example, it can let the browser do some work between component calls so that re-rendering a large component tree doesn’t block the main thread. +* **A better debugging story.** If components are first-class citizens that the library is aware of, we can build rich developer tools for introspection in development. +* **More efficient reconcilation.** React can decide exactly which components in the tree need re-rendering and skip over the ones that don't. That makes your app faster and more snappy. + +## Never pass around hooks as regular values {/*never-pass-around-hooks-as-regular-values*/} + +Hooks should only be called inside of components. Never pass it around as a regular value. + +Hooks allow you to augment a component with React features. They should always be called as a function, and never passed around as a regular value. This enables _local reasoning_, or the ability for developers to understand everything a component can do by looking at that component in isolation. + +```js {2} +function ChatInput() { + const useDataWithLogging = withLogging(useData); // ❌ + const data = useDataWithLogging(); +} +``` + +```js {2} +function ChatInput() { + const data = useDataWithLogging(); // ✅ +} +``` + +Hooks should be immutable and not be mutated. Instead of mutating a hook dynamically, create a static version of the hook with the desired functionality. + +```js +function useDataWithLogging() { + // ... Logic should go in here +} +``` + +Hooks should also not be dynamically used: for example, instead of doing dependency injection in a component: + +```js {2} +function ChatInput() { + return ); } -``` \ No newline at end of file +``` + +## Return values and arguments to Hooks are immutable {/*return-values-and-arguments-to-hooks-are-immutable*/} + +Once values are passed to a Hook, neither the calling code nor the Hook should +modify them. Like props in JSX, values become immutable when passed to a Hook. + +```js {4} +function useIconStyle(icon) { + const theme = useContext(ThemeContext); + if (icon.enabled) { + // ❌ never mutate hook arguments directly + icon.className = computeStyle(icon, theme); + } + return icon; +} +``` + +```js {4} +function useIconStyle(icon) { + const theme = useContext(ThemeContext); + // ✅ make a copy instead + let newIcon = { ...icon }; + if (icon.enabled) { + newIcon.className = computeStyle(icon, theme); + } + return newIcon; +} +``` + +The custom Hook might have used the hook arguments as dependencies to memoize +values inside it. + +```js {4} +function useIconStyle(icon) { + const theme = useContext(ThemeContext); + + return useMemo(() => { + let newIcon = { ...icon }; + if (icon.enabled) { + newIcon.className = computeStyle(icon, theme); + } + return newIcon; + }, [icon, theme]); +} +``` + +Modifying the hook arguments after the hook call can cause issues, so it's important to avoid doing that. + +```js {4} +style = useIconStyle(icon); // `style` is memoized based on `icon` +icon.enabled = false; // ❌ never mutate hook arguments directly +style = useIconStyle(icon); // previously memoized result is returned +``` + +```js {4} +style = useIconStyle(icon); // `style` is memoized based on `icon` +icon = { ...icon, enabled: false }; // ✅ make a copy instead +style = useIconStyle(icon); // new value of `style` is calculated +``` + +Similarly, it's important to not modify the return values of hooks, as they have been memoized. \ No newline at end of file From 62b0fa1084718e2c263f8cff96718f42ae9afbb5 Mon Sep 17 00:00:00 2001 From: Lauren Tan Date: Tue, 12 Mar 2024 12:14:31 -0400 Subject: [PATCH 12/37] Combine rules of jsx --- .../components-and-hooks-must-be-pure.md | 50 ++++++++++++++++--- src/content/reference/rules/index.md | 9 ++-- ...react-orchestrates-components-and-hooks.md | 31 ++++++------ src/content/reference/rules/rules-of-hooks.md | 5 +- src/content/reference/rules/rules-of-jsx.md | 49 ------------------ src/sidebarReference.json | 4 -- 6 files changed, 67 insertions(+), 81 deletions(-) delete mode 100644 src/content/reference/rules/rules-of-jsx.md diff --git a/src/content/reference/rules/components-and-hooks-must-be-pure.md b/src/content/reference/rules/components-and-hooks-must-be-pure.md index 6592491b623..9e9999448fe 100644 --- a/src/content/reference/rules/components-and-hooks-must-be-pure.md +++ b/src/content/reference/rules/components-and-hooks-must-be-pure.md @@ -3,7 +3,7 @@ title: Components and Hooks must be pure --- -TODO +[Purity](/learn/keeping-components-pure) makes your code easier to understand and debug. @@ -76,7 +76,7 @@ function FriendList({ friends }) { There is no need to contort your code to avoid local mutation. In particular, [`Array.map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map) could also be used here for brevity, but there is nothing wrong with creating a local array and then pushing items into it during render. -Even though it looks like we are mutating `items`, the key point to note is that this code only does so locally – the mutation isn't "remembered" when the component is rendered again. In other words, `items` only stays around as long as the component does. Because `items` is always recreated every time `` is rendered, the component will always returns the same result. +Even though it looks like we are mutating `items`, the key point to note is that this code only does so locally – the mutation isn't "remembered" when the component is rendered again. In other words, `items` only stays around as long as the component does. Because `items` is always recreated every time `` is rendered, the component will always return the same result. On the other hand, if `items` was created outside of the component, it holds on to its previous values and remembers changes: @@ -175,8 +175,7 @@ function Counter() { ## Return values and arguments to Hooks are immutable {/*return-values-and-arguments-to-hooks-are-immutable*/} -Once values are passed to a Hook, neither the calling code nor the Hook should -modify them. Like props in JSX, values become immutable when passed to a Hook. +Once values are passed to a Hook, you should not modify them. Like props in JSX, values become immutable when passed to a Hook. ```js {4} function useIconStyle(icon) { @@ -201,8 +200,7 @@ function useIconStyle(icon) { } ``` -The custom Hook might have used the hook arguments as dependencies to memoize -values inside it. +The custom Hook might have used the hook arguments as dependencies to memoize values inside it. ```js {4} function useIconStyle(icon) { @@ -232,4 +230,42 @@ icon = { ...icon, enabled: false }; // ✅ make a copy instead style = useIconStyle(icon); // new value of `style` is calculated ``` -Similarly, it's important to not modify the return values of hooks, as they have been memoized. \ No newline at end of file +Similarly, it's important to not modify the return values of hooks, as they may have been memoized. + +## Values are immutable after being passed to JSX {/*values-are-immutable-after-being-passed-to-jsx*/} + +Don't mutate values after they've been used in JSX. Move the mutation before the JSX is created. + +When you use JSX in an expression, React may eagerly evaluate the JSX before the component finishes rendering. This means that mutating values after they've been passed to JSX can lead to outdated UIs, as React won't know to update the component's output. + +```js {4} +function Page({ colour }) { + const styles = { colour, size: "large" }; + const header =
    ; + styles.size = "small"; // ❌ styles was already used in the JSX above! + const footer =