~/Home ~/Notes ~/Categories

React State Patterns

24 August, 2020  ·   React
  https://www.udemy.com/course/modern-react-bootcamp/

Note: These notes are taken from Colt steele’s Modern React Bootcamp course

Setting State Using State

We know that setState() is asynchronous…

So: it’s risky to assume previous call has finished when you call it. Also, React will sometimes batch (squash together) calls to setState together into one for performance reasons. If a call to setState() depends on current state, the safest thing is to use the alternate “callback form”.

setState Callback Form

this.setState(callback)

Instead of passing an object, pass it a callback with the current state as a parameter. The callback should return an object representing the new state.

1this.setState((curState) => ({ count: curState.count + 1 }));

Abstracting State Updates

The fact that you can pass a function to this.setState lends itself nicely to a more advanced pattern called functional setState.

Basically you can describe your state updates abstractly as separate functions. But why would you do this?

1// elsewhere in the code
2function incrementCounter(prevState) {
3  return { count: prevState.count + 1 };
4}
5// somewhere in the component
6this.setState(incrementCounter);

Because testing your state changes is as simple as testing a plain function:

1expect(incrementCounter({ count: 0 })).toEqual({ count: 1 });

This pattern also comes up all the time in Redux!

Here is a nice opinionated article on the subject of using functional setState

Mutable Data Structures in State

Mutable Data Structures

We know how to set state to primitives: mainly numbers and strings. But component state also commonly includes objects, arrays, and arrays of objects.

1this.state = {
2  // store an array of todo objects
3  todos: [
4    { task: "do the dishes", done: false, id: 1 },
5    { task: "vacuum the floor", done: true, id: 2 },
6  ],
7};

You have to be extra careful modifying your array of objects!

1completeTodo(id) {
2  const theTodo = this.state.todos.find(t => t.id === id);
3  theTodo.done = true; // NOOOOO -> WRONG WAY
4
5  this.setState({
6    todos: this.state.todos // bad -> VERY BAD WAY TO SET LIKE THIS
7  });
8}

Why? It’s a long story…

Mutating nested data structures in your state can cause problems w/ React. (A lot of the time it’ll be fine, but that doesn’t matter. Just don’t do it!)

Immutable State Updates

A much better way is to make a new copy of the data structure in question. We can use any pure function to do this…

 1completeTodo(id) {
 2
 3  // Array.prototype.map returns a new array
 4  const newTodos = this.state.todos.map(todo => {
 5    if (todo.id === id) {
 6      // make a copy of the todo object with done -> true
 7      return { ...todo, done: true };
 8    }
 9    return todo;  // old todos can pass through
10  });
11
12  this.setState({
13    todos: newTodos // setState to the new array
14  });
15}

Pure functions such as .map, .filter, and .reduce are your friends. So is the spread operator.

There is a slight efficiency cost due to the O(N) space/time required to make a copy, but it’s almost always worth it to ensure that your app doesn’t have extremely difficult to detect bugs due to mischevious side effects.

Immutable State Summary

Designing State

Designing the state of a React application (or any modern web app) is a challenging skill! It takes practice and time! However, there are some easy best-practices that we can talk about in this section to give you a jump-start.

Minimize Your State

In React, you want to try to put as little data in state as possible.

Litmus test

Bad Example of State Design

Let’s pretend we’re modelling a Person…

1this.state = {
2  firstName: "Matt",
3  lastName: "Lane",
4  birthday: "1955-01-08T07:37:59.711Z",
5  age: 64,
6  mood: "irate",
7};

Fixed Example of State Design

 1console.log(this.props);
 2{
 3  firstName: 'Matt',
 4  lastName: 'Lane',
 5  birthday: '1955-01-08T07:37:59.711Z',
 6  age: 64
 7}
 8
 9console.log(this.state);
10{
11  mood: 'insane'
12}

State Should Live On the Parent

Its better to support “downward data flow” philosophy of React. In general, it makes more sense for a parent component to manage state and have a bunch of “dumb” stateless child display components. This makes debugging easier, because the state is centralized. It’s easier to predict where to find state:

Is the current component stateless? Find out what is rendering it. There’s the state.

Todo Example:

 1class TodoList extends Component {
 2  constructor(props) {
 3    super(props);
 4    this.state = {
 5      todos: [
 6        { task: "do the dishes", done: false, id: 1 },
 7        { task: "vacuum the floor", done: true, id: 2 },
 8      ],
 9    };
10  }
11  /* ... lots of other methods ... */
12  render() {
13    return (
14      <ul>
15        {this.state.todos.map((t) => (
16          <Todo {...t} />
17        ))}
18      </ul>
19    );
20  }
21}

TodoList is a smart parent with lots of methods, while the individual Todo items are just <li> tags with some text and styling.

 react  javascript  es6
Intro to State↠ ↞React Events