Skip to content

Commit

Permalink
docs(en): merge reactjs.org/main into zh-hans.reactjs.org/main @ cfa3670
Browse files Browse the repository at this point in the history
 (reactjs#721)

* Sandpack error icon overlapping issue fix (reactjs#4302)

* sandpack error icon overlapping issue fix

* modified errorline css

* Improve font display (reactjs#4308)

* Generate Ids when there are none in local development (reactjs#4304)

* Generate Ids when there are no headings

* Tweak code

Co-authored-by: Dan Abramov <dan.abramov@gmail.com>

* [Beta] useState Troubleshooting (reactjs#4309)

* [Beta] useState Troubleshooting

* Tweaks

* tweak

* docs: phrasing a sentence (reactjs#4185)

* docs: phrasing a sentence (reactjs#4182)

* docs: phrasing a sentence

* Update extracting-state-logic-into-a-reducer.md

Co-authored-by: dan <dan.abramov@gmail.com>

* docs: fix a grammatical error (reactjs#4183)

Co-authored-by: dan <dan.abramov@gmail.com>

* Change "return statement" to "return keyword" (reactjs#4137)

* small fixes to stopwatch codesandbox (reactjs#4110)

* small fixes to stopwatch codesandbox

noticed that the explanation for the first stopwatch codesandbox mentions "update the time every 10 milliseconds" so updated the codesandbox to reflect that

also there's a small nuanced bug in the second stopwatch codesandbox where each call to `handleStart()` sets a new interval without checking if there's already one ongoing. 

Ie: If the user accidentally double clicks the start button, they set two intervals for updating `now` every 10ms and then intervalRef only retains the second interval ID. Thus, it's impossible to actually stop the timer because `handleStop()` will only clear the latest set interval while the original one will keep executing.

* Update referencing-values-with-refs.md

* Update referencing-values-with-refs.md

* Update referencing-values-with-refs.md

Co-authored-by: dan <dan.abramov@gmail.com>

* Resolve conflicts

Co-authored-by: Amaresh  S M <amaresh.suriya@freshworks.com>
Co-authored-by: Sha Mwe La <62544170+shamwela@users.noreply.github.com>
Co-authored-by: Strek <harish.sethuraman@freshworks.com>
Co-authored-by: Dan Abramov <dan.abramov@gmail.com>
Co-authored-by: Sofya Tuymedova <stuymedova@gmail.com>
Co-authored-by: Soichiro Miki <smiki-tky@umin.ac.jp>
Co-authored-by: Aayush Kumar <aayush.kumarmail@gmail.com>
Co-authored-by: KnowsCount <knowscount@gmail.com>
  • Loading branch information
9 people committed Feb 8, 2022
1 parent 49f53fe commit eccde34
Show file tree
Hide file tree
Showing 10 changed files with 308 additions and 55 deletions.
42 changes: 18 additions & 24 deletions beta/plugins/remark-header-custom-ids.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,32 +32,26 @@ module.exports = ({
visit(tree, 'heading', (node) => {
const children = node.children;
let tail = children[children.length - 1];

// A bit weird: this is to support MDX 2 comments in expressions,
// while we’re still on MDX 1, which doesn’t support them.
if (!tail || tail.type !== 'text' || tail.value !== '/}') {
return;
// Generate slugs on the fly (even if not specified in markdown)
// so that it's possible to copy anchor links in newly written content.
let id = slugs.slug(toString(node), maintainCase);
// However, for committed docs, we'll extract slug from the headers.
if (tail && tail.type === 'text' && tail.value === '/}') {
tail = children[children.length - 2];
if (tail && tail.type === 'emphasis') {
// Use custom ID instead.
id = toString(tail);
// Until we're on MDX 2, we need to "cut off" the comment syntax.
tail = children[children.length - 3];
if (tail && tail.type === 'text' && tail.value.endsWith('{/')) {
// Remove the emphasis and trailing `/}`
children.splice(children.length - 2, 2);
// Remove the `{/`
tail.value = tail.value.replace(/[ \t]*\{\/$/, '');
}
}
}

tail = children[children.length - 2];

if (!tail && tail.type !== 'emphasis') {
return;
}

const id = toString(tail);

tail = children[children.length - 3];

if (!tail || tail.type !== 'text' || !tail.value.endsWith('{/')) {
return;
}

// Remove the emphasis and trailing `/}`
children.splice(children.length - 2, 2);
// Remove the `{/`
tail.value = tail.value.replace(/[ \t]*\{\/$/, '');

const data = patch(node, 'data', {});

patch(data, 'id', id);
Expand Down
4 changes: 2 additions & 2 deletions beta/src/components/Layout/Toc.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export function Toc({
<li
key={`heading-${h.url}-${i}`}
className={cx(
'text-sm px-2 py-1 rounded-l-lg',
'text-sm px-2 rounded-l-lg',
selectedIndex === i
? 'bg-highlight dark:bg-highlight-dark'
: null,
Expand All @@ -55,7 +55,7 @@ export function Toc({
selectedIndex === i
? 'text-link dark:text-link-dark font-bold'
: 'text-secondary dark:text-secondary-dark',
'block hover:text-link dark:hover:text-link-dark'
'block hover:text-link dark:hover:text-link-dark leading-normal py-2'
)}
href={h.url}>
{h.text}
Expand Down
289 changes: 273 additions & 16 deletions beta/src/pages/apis/usestate.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ title: useState
- [Avoiding recreating the initial state](#avoiding-recreating-the-initial-state)
- [Resetting state with a key](#resetting-state-with-a-key)
- [Storing information from previous renders](#storing-information-from-previous-renders)
- [Troubleshooting](#troubleshooting)
- [I’ve updated the state, but logging gives me the old value](#ive-updated-the-state-but-logging-gives-me-the-old-value)
- [I've updated the state, but the screen doesn't update](#ive-updated-the-state-but-the-screen-doesnt-update)
- [I'm getting an error: "Too many re-renders"](#im-getting-an-error-too-many-re-renders)


## Reference {/*reference*/}

Expand Down Expand Up @@ -278,22 +283,6 @@ button { display: block; margin-top: 10px; }
Read [state as a component's memory](/learn/state-a-components-memory) to learn more.
<Gotcha>
Calling the `set` function only [affects the next render](/learn/state-as-a-snapshot) and **does not change state in the running code**:
```js {3,4}
function handleClick() {
console.log(count); // 0
setCount(count + 1); // Request a re-render with 1
console.log(count); // Still 0!
}
```
If you need the next state, you can save it in a variable before passing it to the `set` function.
</Gotcha>
---
### Updating state based on the previous state {/*updating-state-based-on-the-previous-state*/}
Expand Down Expand Up @@ -488,6 +477,118 @@ input { margin-left: 5px; }
<Solution />
### Form (nested object) {/*form-nested-object*/}
In this example, the state is more nested. When you update nested state, you need to create a copy of the object you're updating, as well as any objects "containing" it on the way upwards. Read [updating a nested object](/learn/updating-objects-in-state#updating-a-nested-object) to learn more.
<Sandpack>
```js
import { useState } from 'react';

export default function Form() {
const [person, setPerson] = useState({
name: 'Niki de Saint Phalle',
artwork: {
title: 'Blue Nana',
city: 'Hamburg',
image: 'https://i.imgur.com/Sd1AgUOm.jpg',
}
});

function handleNameChange(e) {
setPerson({
...person,
name: e.target.value
});
}

function handleTitleChange(e) {
setPerson({
...person,
artwork: {
...person.artwork,
title: e.target.value
}
});
}

function handleCityChange(e) {
setPerson({
...person,
artwork: {
...person.artwork,
city: e.target.value
}
});
}

function handleImageChange(e) {
setPerson({
...person,
artwork: {
...person.artwork,
image: e.target.value
}
});
}

return (
<>
<label>
Name:
<input
value={person.name}
onChange={handleNameChange}
/>
</label>
<label>
Title:
<input
value={person.artwork.title}
onChange={handleTitleChange}
/>
</label>
<label>
City:
<input
value={person.artwork.city}
onChange={handleCityChange}
/>
</label>
<label>
Image:
<input
value={person.artwork.image}
onChange={handleImageChange}
/>
</label>
<p>
<i>{person.artwork.title}</i>
{' by '}
{person.name}
<br />
(located in {person.artwork.city})
</p>
<img
src={person.artwork.image}
alt={person.artwork.title}
/>
</>
);
}
```
```css
label { display: block; }
input { margin-left: 5px; margin-bottom: 5px; }
img { width: 200px; height: 200px; }
```
</Sandpack>
<Solution />
### List (array) {/*list-array*/}
In this example, the `todos` state variable holds an array. Each button handler calls `setTodos` with the next version of that array. The `[...todos]` spread syntax, `todos.map()` and `todos.filter()` ensure the state array is replaced rather than mutated.
Expand Down Expand Up @@ -655,6 +756,93 @@ ul, li { margin: 0; padding: 0; }
<Solution />
### Writing concise update logic with Immer {/*writing-concise-update-logic-with-immer*/}
If updating arrays and objects without mutation feels tedious, you can use a library like [Immer](https://github.com/immerjs/use-immer) to reduce repetitive code. Immer lets you write concise code as if you were mutating objects, but under the hood it performs immutable updates:
<Sandpack>
```js
import { useState } from 'react';
import { useImmer } from 'use-immer';

let nextId = 3;
const initialList = [
{ id: 0, title: 'Big Bellies', seen: false },
{ id: 1, title: 'Lunar Landscape', seen: false },
{ id: 2, title: 'Terracotta Army', seen: true },
];

export default function BucketList() {
const [list, updateList] = useImmer(initialList);

function handleToggle(artworkId, nextSeen) {
updateList(draft => {
const artwork = draft.find(a =>
a.id === artworkId
);
artwork.seen = nextSeen;
});
}

return (
<>
<h1>Art Bucket List</h1>
<h2>My list of art to see:</h2>
<ItemList
artworks={list}
onToggle={handleToggle} />
</>
);
}

function ItemList({ artworks, onToggle }) {
return (
<ul>
{artworks.map(artwork => (
<li key={artwork.id}>
<label>
<input
type="checkbox"
checked={artwork.seen}
onChange={e => {
onToggle(
artwork.id,
e.target.checked
);
}}
/>
{artwork.title}
</label>
</li>
))}
</ul>
);
}
```
```json package.json
{
"dependencies": {
"immer": "1.7.3",
"react": "latest",
"react-dom": "latest",
"react-scripts": "latest",
"use-immer": "0.5.1"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject"
}
}
```
</Sandpack>
<Solution />
</Recipes>
Read [updating objects in state](/learn/updating-objects-in-state) and [updating arrays in state](/learn/updating-arrays-in-state) to learn more.
Expand Down Expand Up @@ -856,3 +1044,72 @@ button { margin-bottom: 10px; }
Note that if you call a `set` function while rendering, it must be inside a condition like `prevCount !== count`, and there must be a call like `setPrevCount(count)` inside of the condition. Otherwise, your component would re-render in a loop until it crashes. Also, you can only update the state of the *currently rendering* component like this. Calling the `set` function of *another* component during rendering is an error. Finally, your `set` call should still [update state without mutation](#updating-objects-and-arrays-in-state) -- this special case doesn't mean you can break other rules of [pure functions](/learn/keeping-components-pure).
This pattern can be hard to understand and is usually best avoided. However, it's better than updating state in an effect. When you call the `set` function during render, React will re-render that component immediately after your component exits with a `return` statement, and before rendering the children. This way, children don't need to render twice. The rest of your component function will still execute (and the result will be thrown away), but if your condition is below all the calls to Hooks, you may add `return null` inside it to restart rendering earlier.
---
## Troubleshooting {/*troubleshooting*/}
### I've updated the state, but logging gives me the old value {/*ive-updated-the-state-but-logging-gives-me-the-old-value*/}
Calling the `set` function **does not change state in the running code**:
```js {4,5,8}
function handleClick() {
console.log(count); // 0

setCount(count + 1); // Request a re-render with 1
console.log(count); // Still 0!

setTimeout(() => {
console.log(count); // Also 0!
}, 5000);
}
```
This is because [states behaves like a snapshot](/learn/state-as-a-snapshot). Updating state requests another render with the new state value, but does not affect the `count` JavaScript variable in your already running event handler.
If you need to use the next state, you can save it in a variable before passing it to the `set` function:
```js
const nextCount = count + 1;
setCount(nextCount);

console.log(count); // 0
console.log(nextCount); // 1
```
### I've updated the state, but the screen doesn't update {/*ive-updated-the-state-but-the-screen-doesnt-update*/}
React will **ignore your update if the next state is equal to the previous state,** as determined by an [`Object.is`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is) comparison. This usually happens when you change an object or an array in state directly:
```js {2}
obj.x = 10;
setObj(obj); // Doesn't do anything
```
You called `setObj` with the same `obj` object, so React bailed out of rendering. To fix this, you need to ensure that you're always [_replacing_ objects and arrays in state instead of _mutating_ them](#updating-objects-and-arrays-in-state):
```js
setObj({
...obj,
x: 10
});
```
### I'm getting an error: "Too many re-renders" {/*im-getting-an-error-too-many-re-renders*/}
You might get an error that says: `Too many re-renders. React limits the number of renders to prevent an infinite loop.` Typically, this means that you're unconditionally setting state *during render*, so your component enters a loop: render, set state (which causes a render), render, set state (which causes a render), and so on. Very often, this is caused by a mistake in specifying an event handler:
```js {1-2}
// 🚩 Wrong: calls the handler during render
return <button onClick={handleClick()}>Click me</button>

// ✅ Correct: passes down the event handler
return <button onClick={handleClick}>Click me</button>

// ✅ Correct: passes down an inline function
return <button onClick={(e) => handleClick(e)}>Click me</button>
```
If you can't find the cause of this error, click on the arrow next to the error in the console, and look through the JavaScript stack to find the specific `set` function call responsible for the error.
Loading

0 comments on commit eccde34

Please sign in to comment.