From 741dc48dad6dbcdd1c8a3c02c201fa728863aa41 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Thu, 3 Feb 2022 18:01:56 +0000 Subject: [PATCH] Dry version --- beta/src/pages/reference/usestate.md | 743 ++------------------------- 1 file changed, 32 insertions(+), 711 deletions(-) diff --git a/beta/src/pages/reference/usestate.md b/beta/src/pages/reference/usestate.md index 48799f15c..da061532d 100644 --- a/beta/src/pages/reference/usestate.md +++ b/beta/src/pages/reference/usestate.md @@ -1,763 +1,84 @@ --- -title: useState() +title: useState --- -The `useState` Hook lets your component ["remember" information that changes over time](/learn/state-a-components-memory) (called state). It returns two values: the current state, and the function that lets you update it. +`useState` is a React Hook that lets you declare a state variable. ```js -const [state, setState] = useState(initialState); +let [state, setState] = useState(initialState); ``` -## Declaring a state variable {/*declaring-a-state-variable*/} +## `useState()` {/*usestate*/} -You can declare one or more [state variables](/learn/state-a-components-memory) at the top level of your component: +### Arguments {/*arguments*/} - +* `initialState`: The value you want the state to be initially. It can be a value of any type, but there is a special behavior for functions. If you pass a function, React will not store it, but will *call* your function instead, and store its return value as the initial state. The `initialState` argument is ignored after the initial render. - +### Returns {/*returns*/} -Pass any value that you want the state to be initially. +`useState` returns an array with exactly two values: -You can also pass an initializer function. +* `state`: The current state. +* [`setState`](#setstate): A function that lets you update the state to a different value and trigger a re-render. - +By convention, we use array destructuring to give them names like `[thing, setThing]`. - - -This is the current state value that you can display in JSX. - - - - - -This is the function that lets you update the state later. - - - -```js [[2, 4, "name"], [3, 4, "setName"], [1, 4, "'Taylor'"], [2, 6, "age"], [3, 6, "setAge"], [1, 6, "28"]] -import { useState } from 'react'; +### Example {/*example*/} +```js function Form() { const [name, setName] = useState('Taylor'); - const [age, setAge] = useState(28); - // ... ``` - +### Caveats {/*caveats*/} -This `[` and `]` syntax is called [array destructuring](/learn/a-javascript-refresher#array-destructuring) and it lets you read values from an array. The array returned by `useState` always has exactly two items--it's a pair. By convention, name them like `[thing, setThing]`. +* `useState` is a Hook, so you can't call it inside loops or conditions. -## Using state {/*using-state*/} +## `setState()` {/*setstate*/} -First, declare the state variables you need. Then, update them on interaction and display them in your JSX: - - - - - -Call `useState` and pass the initial state to it. React will store the state that you passed, and give it back to you. - - - - - -To change the state, call the state setter function with the next state value. React will put that value into state instead. - -You can also pass an updater function. - - - - - -Use the state in your JSX. - - - -```js [[1, 4, "const [count, setCount] = useState(0);"], [2, 7, "setCount(count + 1);"], [3, 12, "count"]] -import { useState } from 'react'; - -function Counter() { - const [count, setCount] = useState(0); - - function handleClick() { - setCount(count + 1); - } - - return ( - - ); -} -``` - - - - +`setState` lets you update the state to a different value and trigger a re-render: ```js -import { useState } from 'react'; - -export default function Counter() { - const [count, setCount] = useState(0); - - function handleClick() { - setCount(count + 1); - } - - return ( - - ); -} +setState(nextState); ``` - +### Arguments {/*setstate-arguments*/} - +* `nextState`: The value that you want the state to be. It can be a value of any type, but there is a special behavior for functions. If you pass a function, React will not store it, but will *call* your function instead during the next render with the pending state, and store its return value. -Calling `setState` [only affects the next render](/learn/state-as-a-snapshot) and **does not change state in the already running code:** +### Returns {/*setstate-returns*/} -```js {4} -function handleClick() { - console.log(count); // 0 - setCount(count + 1); // Request a re-render with 1 - console.log(count); // Still 0! -} -``` +`setState` does not have a return value. - - - -### Text field (string) {/*text-field-string*/} - -In this example, the `text` state variable holds a string. When you type, `handleChange` reads the latest input value from the browser input DOM element, and calls `setText` to update the state. This allows you to display the current `text` below. - - +### Example {/*setstate-example*/} ```js -import { useState } from 'react'; - -export default function MyInput() { - const [text, setText] = useState('hello'); - - function handleChange(e) { - setText(e.target.value); - } - - return ( - <> - -

You typed: {text}

- - - ); -} -``` - -
- - - -### Checkbox (boolean) {/*checkbox-boolean*/} - -In this example, the `liked` state variable holds a boolean. When you click the input, `setLiked` updates the `liked` state variable with whether the browser checkbox input is checked. The `liked` variable is used to render the text below the checkbox. - - - -```js -import { useState } from 'react'; - -export default function MyCheckbox() { - const [liked, setLiked] = useState(true); - - function handleChange(e) { - setLiked(e.target.checked); - } - - return ( - <> - -

You {liked ? 'liked' : 'did not like'} this.

- - ); -} -``` - -
- - - -### Form (two variables) {/*form-two-variables*/} - -You can declare more than one state variable in the same component. Each state variable is completely independent. - - - -```js -import { useState } from 'react'; - -export default function Form() { +function Form() { const [name, setName] = useState('Taylor'); const [age, setAge] = useState(28); - return ( - <> - setName(e.target.value)} - /> - -

Hello, {name}. You are {age}.

- - ); -} -``` - -```css -button { display: block; margin-top: 10px; } -``` - -
- - - - -### Form (object) {/*form-object*/} - -In this example, the `form` state variable holds an object. Each input has a change handler that calls `setForm` with the next state of the entire form. The `{ ...form }` spread syntax ensures that [the state object is replaced rather than mutated.](/learn/updating-objects-in-state) - - - -```js -import { useState } from 'react'; - -export default function Form() { - const [form, setForm] = useState({ - firstName: 'Barbara', - lastName: 'Hepworth', - email: 'bhepworth@sculpture.com', - }); - - return ( - <> - - - -

- {form.firstName}{' '} - {form.lastName}{' '} - ({form.email}) -

- - ); -} -``` - -```css -label { display: block; } -input { margin-left: 5px; } -``` - -
- - - -### 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.](/learn/updating-arrays-in-state) - - - -```js App.js -import { useState } from 'react'; -import AddTodo from './AddTodo.js'; -import TaskList from './TaskList.js'; - -let nextId = 3; -const initialTodos = [ - { id: 0, title: 'Buy milk', done: true }, - { id: 1, title: 'Eat tacos', done: false }, - { id: 2, title: 'Brew tea', done: false }, -]; - -export default function TaskApp() { - const [todos, setTodos] = useState(initialTodos); - - function handleAddTodo(title) { - setTodos([ - ...todos, - { - id: nextId++, - title: title, - done: false - } - ]); + function handleChange(e) { + setName(e.target.value); } - - function handleChangeTodo(nextTodo) { - setTodos(todos.map(t => { - if (t.id === nextTodo.id) { - return nextTodo; - } else { - return t; - } - })); + + function handleClick() { + setAge(a => a + 1); } - function handleDeleteTodo(todoId) { - setTodos( - todos.filter(t => t.id !== todoId) - ); - } - - return ( - <> - - - - ); -} -``` - -```js AddTodo.js -import { useState } from 'react'; - -export default function AddTodo({ onAddTodo }) { - const [title, setTitle] = useState(''); - return ( - <> - setTitle(e.target.value)} - /> - - - ) -} -``` - -```js TaskList.js -import { useState } from 'react'; - -export default function TaskList({ - todos, - onChangeTodo, - onDeleteTodo -}) { - return ( -
    - {todos.map(todo => ( -
  • - -
  • - ))} -
- ); -} - -function Task({ todo, onChange, onDelete }) { - const [isEditing, setIsEditing] = useState(false); - let todoContent; - if (isEditing) { - todoContent = ( - <> - { - onChange({ - ...todo, - title: e.target.value - }); - }} /> - - - ); - } else { - todoContent = ( - <> - {todo.title} - - - ); - } - return ( - - ); -} -``` - -```css -button { margin: 5px; } -li { list-style-type: none; } -ul, li { margin: 0; padding: 0; } -``` - -
- - - -
- -## Special cases {/*special-cases*/} - -### Passing the same value to `setState` {/*passing-the-same-value-to-setstate*/} - -If you pass the current state to `setState`, React **will skip re-rendering the component and its children**: - -```js -setCount(count); // Won't trigger a re-render -``` - -React uses the [`Object.is()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is) algorithm to compare the values. - -This is a performance optimization. React might still need to call your component function in some cases, but it will discard the result and bail out of re-rendering the child tree if the state has not changed. - -### Passing a state updater to `setState` {/*passing-a-state-updater-to-setstate*/} - -Instead of passing the next state, **you may pass a function to `setState`.** Such a function, like `c => c + 1` in this example, is called an "updater". React will call your updater during the next render to calculate the final state. - - - - - -Usually, when you set state, you replace it. - - - - - -But you can pass an updater function to transform it. - - - -```js [[1, 2, "123"], [2, 4, "c => c + 1"]] -function handleClick() { - setCount(123); - - setCount(c => c + 1); // Result: 124 -} -``` - - - -This is useful when you want to update state based on the state _you have just set_ (like `123` above) and that has not yet been reflected to the screen. By using updaters, you can [queue multiple updates](/learn/queueing-a-series-of-state-updates) on top of each other: - - - - - -You get the latest state with the previously queued updates applied to it. For example, if `count` was `0` and you call `setCount(c => c + 1)` three times in a row, then the pending `c` state will be `0` in the first updater, `1` in the second one, and `2` in the third one, and `3` is the final state. - - - - - -You return the next state you want to see on the screen. - - - -```js [[1, 3, "c", 0], [2, 3, "c + 1"], [1, 6, "c", 0], [2, 6, "c + 1"], [1, 9, "c", 0], [2, 9, "c + 1"]] -function handleClick() { - // 0 => 1 - setCount(c => c + 1); - - // 1 => 2 - setCount(c => c + 1); - - // 2 => 3 - setCount(c => c + 1); -} -``` - - - -React puts the updater functions in a queue and runs them during the next render. You can think of a regular `setState(something)` call as a `setState(() => something)` call where the pending state is not used. - - - -**Updaters need to be [pure functions that only calculate and return the next state](/learn/keeping-components-pure).** Don't "do" things or set state from the updater functions. React runs updater functions **twice in development only** in Strict Mode to stress-test them. This shouldn't affect pure functions, so it helps find accidental impurities. - - - - - -You might hear a recomendation to always write code like `setCount(c => c + 1)` if the state you're setting is calculated from the previous state. There is no harm in it, but it is also not always necessary. - -In most cases, there is no difference between these two approaches. React always makes sure that for intentional user actions, like clicks, the `count` state variable would be updated before the next click. This means there is no risk of a click handler seeing a "stale" `count` at the beginning of the event handler. - -However, if you do multiple updates within the same event, updaters can be helpful. They're also helpful if accessing the state variable itself is inconvenient (you might run into this when optimizing re-renders). - -If you prefer consistency over slightly more verbose syntax, it's reasonable to always write an updater if the state you're setting is calculated from the previous state. If it's calculated from the previous state of some *other* state variable, you might want to combine them into one object and [use a reducer](/learn/extracting-state-logic-into-a-reducer). - - - -### Passing an initializer to `useState` {/*passing-an-initializer-to-usestate*/} - -The initial state that you pass to `useState` is only used for the initial render. For the next renders, this argument is ignored. If creating the initial state is expensive, it is wasteful to create and throw it away many times. **You can pass a function to `useState` to calculate the initial state.** React will only run it during the initialization. - - - - - -Most often, you will provide the initial state during render. - - - - - -But you can also give React a function that calculates the initial state instead. React will only call that function when initializing the component, and won't call it again. - - - -```js [[1, 2, "''"], [2, 5, "() => createInitialTodos()"]] -function TodoList() { - const [text, setText] = useState(''); - - const [todos, setTodos] = useState( - () => createInitialTodos() - ); - // ... ``` - +### Caveats {/*caveats*/} -This is a performance optimization. Initializers let you avoid creating large objects or arrays on re-renders. +* Calling `setState()` only updates the state for the next render. If you read the `state` variable after calling `setState()`, you will still get the value that was on the screen before your call. - +* If you call `setState()` with the value that's identical to the current `state`, as determined by an [`Object.is`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is) comparison, React will bail out of re-rendering the component and its children. However, in some cases React may still need to call your component before discarding the result. -**Initializers need to be [pure functions that only calculate and return the initial state](/learn/keeping-components-pure).** Don't "do" things or set state from the initializer functions. React runs initializer functions **twice in development only** in Strict Mode to stress-test them. This shouldn't affect pure functions, so it helps find accidental impurities. - - - - -### Passing an updater to setState {/*passing-an-updater-to-setstate*/} - -In this example, the `increment` method increments the counter with `setNumber(n => n + 1)`. Verify both "+3" and "+1" buttons work. The "+3" button calls `increment()` three times, which enqueues three separate state updates to the `number`: - - - -```js -import { useState } from 'react'; - -export default function Counter() { - const [number, setNumber] = useState(0); - - function increment() { - setNumber(n => n + 1) - } - - return ( - <> -

{number}

- - - - ); -} -``` - -```css -button { display: inline-block; margin: 10px; font-size: 20px; } -h1 { display: inline-block; margin: 10px; width: 30px; text-align: center; } -``` - -
- -Notice how if you change the code to `setNumber(number + 1)`, the "+3" button no longer works. This is because `number` [always refers to what's currently on the screen](/learn/state-as-a-snapshot). When you call `setNumber(number + 1)`, `number` stays the same until the next render. - - - -### Passing an initializer to useState {/*passing-an-initializer-to-usestate*/} - -In this example, the initial state is populated with an array. Recreating this array during every render would be wasteful, so we pass a function to `useState`. React calls the initializer to figure out what the initial state should be, and puts it in `todos`. - - - -```js -import { useState } from 'react'; - -function createInitialTodos() { - const initialTodos = []; - for (let i = 0; i < 50; i++) { - initialTodos.push({ - id: i, - text: 'Item #' + i - }); - } - return initialTodos; -} - -export default function TodoList() { - const [todos, setTodos] = useState(createInitialTodos); - - return ( -
    - {todos.map(item => ( -
  • - {item.text} -
  • - ))} -
- ); -} -``` - -
- - - -### Preventing re-renders with same state {/*preventing-re-renders-with-same-state*/} - -In this example, the two buttons switch the `tab` state between `'home'` and `'about'`. This sandbox has a logging utility function that helps us visualize what React is doing. Initially, React renders the Home tab. (Double renders are due to [Strict Mode](/reference/strictmode).) - -If you press "Home" _again_, neither the Home component nor its children will re-render. This is because you're calling `setTab('home')` but `tab` is already `'home'`. That's the special behavior of `setState` when the value is the same. - - - -```js -import { useState } from 'react'; - -export default function MySite() { - const [tab, setTab] = useState('home'); - debugLog('rendered MySite'); - - return ( - <> - - {tab === 'home' && } - {tab === 'about' && } - - ); -} - -function Home() { - debugLog('rendered Home'); - return

Home sweet home

-} - -function About() { - debugLog('rendered About'); - return

About me

-} - -function debugLog(text) { - const message = document.createElement('p'); - message.textContent = text; - message.style.fontFamily = 12; - message.style.color = 'grey'; - document.body.appendChild(message); -} -``` - -```css -button { display: inline-block; margin: 10px; font-size: 20px; } -``` - -
- -You'll notice that if you switch to About and then press About again, you might get an extra log of the parent component render. React may still need to call the parent component in some cases, but it won't re-render any of the children. - - - -