Skip to content

Commit ceaef0a

Browse files
committed
[wip] Add a few Rules of React docs
1 parent 265fa26 commit ceaef0a

11 files changed

+293
-0
lines changed
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
---
2+
title: Components must be idempotent
3+
---
4+
5+
<Intro>
6+
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).
7+
</Intro>
8+
9+
---
10+
11+
Put simply, idempotence means that you [always get the same result everytime](learn/keeping-components-pure) you run that piece of code.
12+
13+
```js
14+
function NewsFeed({ items }) {
15+
const filteredItems = items.filter(item => item.isDisplayed === true);
16+
return (
17+
<ul>
18+
{filteredItems.map(item => <li>{item.text}</li>}
19+
</ul>
20+
);
21+
}
22+
```
23+
24+
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:
25+
26+
```js {2}
27+
function Clock() {
28+
return <div>{new Date()}</div> // ❌ always returns a different result!
29+
}
30+
```
31+
32+
`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.
33+
34+
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!

src/content/reference/rules/index.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
---
2+
title: Rules of React
3+
---
4+
5+
<Intro>
6+
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.
7+
</Intro>
8+
9+
---
10+
11+
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.
12+
13+
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.
14+
15+
<DeepDive>
16+
#### Why are rules necessary in React? {/*why-are-rules-necessary-in-react*/}
17+
18+
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.
19+
20+
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.
21+
22+
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.
23+
24+
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.
25+
</DeepDive>
26+
27+
---
28+
29+
## Rules {/*rules*/}
30+
* [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.
31+
* [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.
32+
* [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.
33+
* [Never call component functions directly](/reference/rules/never-call-component-functions-directly): TODO React must orchestrate rendering
34+
* [Never pass around Hooks as regular values](/reference/rules/never-pass-around-hooks-as-regular-values): TODO
35+
* [Only call Hooks at the top level](/reference/rules/only-call-hooks-at-the-top-level): TODO
36+
* [Only call Hooks from React functions](/reference/rules/only-call-hooks-from-react-functions): TODO
37+
* [Values are immutable after being passed to JSX](/reference/rules/values-are-immutable-after-being-passed-to-jsx): TODO
38+
* [Return values and arguments to Hooks are immutable](/reference/rules/return-values-and-arguments-to-hooks-are-immutable): TODO
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
title: Never call component functions directly
3+
---
4+
5+
<Intro>
6+
TODO
7+
</Intro>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
title: Never pass around hooks as regular values
3+
---
4+
5+
<Intro>
6+
TODO
7+
</Intro>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
title: Only call Hooks at the top level
3+
---
4+
5+
<Intro>
6+
TODO
7+
</Intro>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
title: Only call Hooks from React functions
3+
---
4+
5+
<Intro>
6+
TODO
7+
</Intro>
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
---
2+
title: Props and State are immutable
3+
---
4+
5+
<Intro>
6+
A component's props and state are immutable [snapshots](learn/state-as-a-snapshot) with respect to a single render.
7+
</Intro>
8+
9+
---
10+
11+
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.
12+
13+
## Props {/*props*/}
14+
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.
15+
16+
```js {2}
17+
function Post({ item }) {
18+
item.url = new Url(item.url, base); // ❌ never mutate props directly
19+
return <Link url={item.url}>{item.title}</Link>;
20+
}
21+
```
22+
23+
```js {2}
24+
function Post({ item }) {
25+
const url = new Url(item.url, base); // ✅ make a copy instead
26+
return <Link url={url}>{item.title}</Link>;
27+
}
28+
```
29+
30+
## State {/*state*/}
31+
`useState` returns an immutable tuple of the state variable and a setter to update that state.
32+
33+
```js
34+
const [stateVariable, setter] = useState(0);
35+
```
36+
37+
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.
38+
39+
```js {5}
40+
function Counter() {
41+
const [count, setCount] = useState(0);
42+
43+
function handleClick() {
44+
count = count + 1; // ❌ never mutate state directly
45+
}
46+
47+
return (
48+
<button onClick={handleClick}>
49+
You pressed me {count} times
50+
</button>
51+
);
52+
}
53+
```
54+
55+
```js {5}
56+
function Counter() {
57+
const [count, setCount] = useState(0);
58+
59+
function handleClick() {
60+
setCount(count + 1); // ✅ use the setter function returned by useState
61+
}
62+
63+
return (
64+
<button onClick={handleClick}>
65+
You pressed me {count} times
66+
</button>
67+
);
68+
}
69+
```
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
title: Return values and arguments to Hooks are immutable
3+
---
4+
5+
<Intro>
6+
TODO
7+
</Intro>
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
---
2+
title: Side effects must run outside of render
3+
---
4+
5+
<Intro>
6+
Side effects should not run in render, as React can render components multiple times to create the best possible user experience.
7+
</Intro>
8+
9+
---
10+
11+
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`.
12+
13+
<DeepDive>
14+
#### Why does render need to be pure? {/*why-does-render-need-to-be-pure*/}
15+
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!
16+
17+
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.
18+
19+
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.
20+
</DeepDive>
21+
22+
For example, values that aren't created inside of a component shouldn't be mutated:
23+
24+
```js
25+
let items = []; // created outside of the component
26+
function MyComponent({ seen }) {
27+
items.push(seen); // ❌ mutated inside the component
28+
}
29+
```
30+
31+
32+
In general, mutation is not idiomatic in React. However, local mutation is absolutely fine:
33+
34+
```js
35+
function FriendList({ friends }) {
36+
let items = []; // ✅ locally created and mutated
37+
for (let i = 0; i < friends.length; i++) {
38+
let friend = friends[i];
39+
items.push(
40+
<Friend key={friend.id} friend={friend} />
41+
);
42+
}
43+
return <section>{items}</section>;
44+
}
45+
```
46+
47+
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.
48+
49+
Similarly, lazy initialization is fine despite not being fully "pure":
50+
51+
```js
52+
function ExpenseForm() {
53+
// Fine if it doesn't affect other components:
54+
SuperCalculator.initializeIfNotReady();
55+
56+
// Continue rendering...
57+
}
58+
```
59+
60+
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.
61+
62+
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.
63+
64+
```js
65+
function ProductDetailPage({ product }) {
66+
document.window.title = product.title; //
67+
}
68+
```
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
title: Values are immutable after being passed to JSX
3+
---
4+
5+
<Intro>
6+
TODO
7+
</Intro>

src/sidebarReference.json

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,48 @@
1010
"title": "Overview",
1111
"path": "/reference/react"
1212
},
13+
{
14+
"title": "Rules of React",
15+
"path": "/reference/rules",
16+
"routes": [
17+
{
18+
"title": "Side effects must run outside of render",
19+
"path": "/reference/rules/side-effects-must-run-outside-of-render"
20+
},
21+
{
22+
"title": "Components must be idempotent",
23+
"path": "/reference/rules/components-must-be-idempotent"
24+
},
25+
{
26+
"title": "Props and State are immutable",
27+
"path": "/reference/rules/props-and-state-are-immutable"
28+
},
29+
{
30+
"title": "Never call component functions directly",
31+
"path": "/reference/rules/never-call-component-functions-directly"
32+
},
33+
{
34+
"title": "Never pass around Hooks as regular values",
35+
"path": "/reference/rules/never-pass-around-hooks-as-regular-values"
36+
},
37+
{
38+
"title": "Only call Hooks at the top level",
39+
"path": "/reference/rules/only-call-hooks-at-the-top-level"
40+
},
41+
{
42+
"title": "Only call Hooks from React functions",
43+
"path": "/reference/rules/only-call-hooks-from-react-functions"
44+
},
45+
{
46+
"title": "Values are immutable after being passed to JSX",
47+
"path": "/reference/rules/values-are-immutable-after-being-passed-to-jsx"
48+
},
49+
{
50+
"title": "Return values and arguments to Hooks are immutable",
51+
"path": "/reference/rules/return-values-and-arguments-to-hooks-are-immutable"
52+
}
53+
]
54+
},
1355
{
1456
"title": "Hooks",
1557
"path": "/reference/react/hooks",

0 commit comments

Comments
 (0)