Skip to content

Commit

Permalink
More on state lesson: Clarifies what setState stands for (#27544)
Browse files Browse the repository at this point in the history
* adds clarification to setState

* fix grammar and formatting

* Apply suggestions from code review

Co-authored-by: MaoShizhong <[email protected]>

---------

Co-authored-by: MaoShizhong <[email protected]>
  • Loading branch information
cakegod and MaoShizhong authored Mar 4, 2024
1 parent 978ba0e commit 124101f
Showing 1 changed file with 27 additions and 27 deletions.
54 changes: 27 additions & 27 deletions react/states_and_effects/more_on_state.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,28 +12,27 @@ This section contains a general overview of topics that you will learn in this l

### How to structure state

Managing and structuring state effectively is by far one of the most crucial parts of building your application. If not done correctly it can become a source of bugs and headaches.
Managing and structuring state effectively is by far one of the most crucial parts of building your application. If not done correctly, it can become a source of bugs and headaches.

The assignment items go through the topic thoroughly, but as a general rule of thumb: don't put values in state that can be calculated using existing values, state, and/or props.

#### State should not be mutated

Mutating state is a no-go area in React as it leads to unpredictable results. Primitives are already immutable, but if you are using reference type values i.e. arrays and objects, never mutate them. According to React documentation, we should treat state as if it was *immutable*.
In order for us to change state, we should always use the `setState` function. Make sure to run the following example locally and see the difference for yourself.
Mutating state is a no-go area in React as it leads to unpredictable results. Primitives are already immutable, but if you are using reference-type values, i.e., arrays and objects, never mutate them. According to the React documentation, we should treat state as if it was *immutable*. To change state, we should always use the `setState` function, which in the case of the example below is the `setPerson` function.

```jsx
function Person() {
const [person, setPerson] = useState({ name: 'John', age: 100 });
const [person, setPerson] = useState({ name: "John", age: 100 });

// BAD - Don't do this!
const handleIncreaseAge = () =>{
const handleIncreaseAge = () => {
// mutating the current state object
person.age = person.age + 1;
setPerson(person);
};

// GOOD - Do this!
const handleIncreaseAge = () =>{
const handleIncreaseAge = () => {
// copy the existing person object into a new object
// while updating the age property
const newPerson = { ...person, age: person.age + 1 };
Expand All @@ -54,25 +53,25 @@ function Person() {

#### Objects and arrays in state

In the above example, notice how we *create* a new object, and then copy the existing state values into the new object while providing a new value for `age`.
That is because if we don't provide a new object to `setState` it is not guaranteed to re-render the page. Therefore we should always provide a new Object for `setState` to trigger a re-render. `setState` uses [Object.is](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is) to determine if the previous state is the same.
In the above example, notice how we *create* a new object and then copy the existing state values into the new object while providing a new value for `age`.
That is because if we don't provide a new object to `setState` it is not guaranteed to re-render the page. Therefore, we should always provide a new Object for `setState` to trigger a re-render. `setState` uses [Object.is](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is) to determine if the previous state is the same.

As for nested objects and arrays, state can get tricky fast since you will have to copy the nested items as well. Be careful when using them.

</div>

### How state updates

State updates are asynchronous. What this implies is whenever you call the `setState` function, React will apply the update in the **next** component render. This concept takes a while to wrap your head around. With a lot of practice, you'll get the hang of it in no time.
State updates are asynchronous. What this implies is that whenever you call the `setState` function, React will apply the update in the **next** component render. This concept takes a while to wrap your head around. With a lot of practice, you'll get the hang of it in no time.

Remember, state variables aren't reactive, the component is. This can be understood by the fact that calling `setState` re-renders the entire component instead of just changing the state variable on the fly.
Remember, state variables aren't reactive; the component is. This can be understood by the fact that calling `setState` re-renders the entire component instead of just changing the state variable on the fly.

```jsx
function Person() {
const [person, setPerson] = useState({ name: 'John', age: 100 });
const [person, setPerson] = useState({ name: "John", age: 100 });

const handleIncreaseAge = () =>{
console.log("in handleIncreaseAge (before setPerson call): ", person)
const handleIncreaseAge = () => {
console.log("in handleIncreaseAge (before setPerson call): ", person);
setPerson({ ...person, age: person.age + 1 });
// we've called setPerson, surely person has updated?
console.log("in handleIncreaseAge (after setPerson call): ", person);
Expand All @@ -94,21 +93,21 @@ function Person() {

These are the logs:

![browser console of above code snippet](https://cdn.statically.io/gh/TheOdinProject/curriculum/bd3063e12816ac241f73daeffa600ca89e56c443/react/states_and_effects/more_on_state/imgs/00.png)
![browser console of the above code snippet](https://cdn.statically.io/gh/TheOdinProject/curriculum/bd3063e12816ac241f73daeffa600ca89e56c443/react/states_and_effects/more_on_state/imgs/00.png)

Uh oh, what is happening? Let's break it down (ignore the double `console.logs` for the render case, this is covered in the upcoming lessons).
Uh-oh, what is happening? Let's break it down (ignore the double `console.logs` for the render case; this is covered in the upcoming lessons).

1. The component renders for the first time. The `person` state variable is initialized to `{ name: 'John', age: 100 }`. The "during render" `console.log` prints the state variable.
1. The button is clicked invoking `handleIncreaseAge`. Interestingly, the `console.log` before and after the `setPerson` call prints the same value.
1. The component re-renders. The `person` state variable is updated to `{ name: 'John', age: 101 }`.

The `person` state stays the same throughout the current render of the component. This is what "state as a snapshot" refers to. The `setState` call triggers a component re-render and the `person` state is updated to the new value.
The `person` state stays the same throughout the current render of the component. This is what "state as a snapshot" refers to. The `setState` call triggers a component re-render, and the `person` state is updated to the new value.

<div class="lesson-note lesson-note--warning" markdown="1">

#### The unexpected infinite loop

The following is an infinite loop, can you guess why? Drop by in our [Discord chatroom](https://discord.com/invite/fbFCkYabZB), tell us why, and score a brownie point!
The following is an infinite loop; can you guess why? Drop by in our [Discord chatroom](https://discord.com/invite/fbFCkYabZB), tell us why, and score a brownie point!

```jsx
function Component() {
Expand Down Expand Up @@ -148,49 +147,50 @@ const handleIncreaseAge = () => {

When a callback is passed to the `setState` function, it ensures that the latest state is passed in as an argument to the callback.

Using an updater is not always necessary. If you want to change the state using your previous state, and you prefer consistency over verbosity then you might consider using an updater.
Using an updater is not always necessary. If you want to change the state using your previous state and you prefer consistency over verbosity, then you might consider using an updater.

<div class="lesson-note" markdown=1>

#### React batches state updates

There are two `setPerson` calls in the above example, and from what we've learned so far, a `setState` call triggers a component re-render. So, the component should re-render twice, right? You would say yes, but React is smart. Wherever possible React batches the state updates. Here the component only re-renders once. We'd encourage you to use `console.log`s to verify this.
There are two `setPerson` calls in the above example, and from what we've learned so far, a `setState` call triggers a component re-render. So, the component should re-render twice, right? You would say yes, but React is smart. Wherever possible, React batches the state updates. Here, the component only re-renders once. We'd encourage you to use `console.log`s to verify this.

</div>

### Controlled components

There are native HTML elements that maintain their own internal state. The `input` element is a great example. You type into an `input` and it updates its own value on every keystroke. For many use-cases, you would like to *control* the value of the `input` element i.e. set its value yourself. This is where controlled components come in.
There are native HTML elements that maintain their own internal state. The `input` element is a great example. You type into an `input` and it updates its own value on every keystroke. For many use-cases, you would like to *control* the value of the `input` element, i.e., set its value yourself. This is where controlled components come in.

```jsx
function CustomInput() {
const [value, setValue] = useState('');
const [value, setValue] = useState("");

return (
<input
type="text"
value={value}
onChange={(event) => setValue(event.target.value)}
type="text"
value={value}
onChange={(event) => setValue(event.target.value)}
/>
);
}
```

Instead of letting the `input` maintain its own state, we define our own state using the `useState` hook. We then set the `value` prop of the `input` to the state variable and update the state variable on every `onChange` event. Now, every time the user types something in the input, React will ensure you have the latest comment/review/post (whatever the user was typing) in `value`.
Instead of letting the `input` maintain its own state, we define our own state using the `useState` hook. We then set the `value` prop of the `input` to the state variable and update the state variable on every `onChange` event. Now, every time the user types something in the input, React will ensure you have the latest comment, review, or post (whatever the user was typing) in `value`.

This pattern is extremely useful wherever you need user input i.e. typing in a textbox, toggling a checkbox etc. Contrarily, yes, the `input` element can be left uncontrolled and access its value through some other method. You don't need to worry about it yet as it is covered later on in the course. For now, control your components!
This pattern is extremely useful wherever you need user input, i.e., typing in a textbox, toggling a checkbox, etc. Contrarily, yes, the `input` element can be left uncontrolled and access its value through some other method. You don't need to worry about it yet, as it will be covered later on in the course. For now, control your components!

### Assignment

<div class="lesson-content__panel" markdown="1">

1. Read the following articles from the React documentation:

- [State as a Snapshot](https://react.dev/learn/state-as-a-snapshot)
- [Choosing the State Structure](https://react.dev/learn/choosing-the-state-structure)
- [Sharing State Between Components](https://react.dev/learn/sharing-state-between-components)

1. Update the `Person` component we've been using above.
- Add two separate input fields for first name and last name. The updated full name should be displayed on every keystroke on either of the two input fields.
- Add two separate input fields for the first name and the last name. The updated full name should be displayed on every keystroke in either of the two input fields.
- There are many ways you can do this. Keep in mind what you've learned in this lesson while coding it out.

</div>
Expand Down

0 comments on commit 124101f

Please sign in to comment.