Five principles of React state management
Tags: react, frontend
Published: 2024-03-24 03:42:41

If you want to read this article in Japanese here is the link: https://zenn.dev/takuyakikuchi/articles/225bc3d88d8538

## TL;DR

**5 principles**
1. Always combine states that change together into one
2. Manage related states to avoid conflicts 
3. Do not assign values to states that can be calculated from existing states or props
4. Avoid duplication
5. Avoid deep nesting, make it flat

## Introduction

> Structuring state well can make a difference between a component that is pleasant to modify and debug, and one that is a constant source of bugs.
> *[Choosing the State Structure](https://react.dev/learn/choosing-the-state-structure)*

Recently, I had the opportunity to refactor a React application's duplication of states and the associated improper use of `useEffect()`.
I thought that if we had been aware of the five principles described in the article [Choosing the State Structure](https://react.dev/learn/choosing-the-state-structure), we could have prevented some of these technical debts.
The purpose of this article is to disseminate those five principles.
It is based on the key points of the original article, but organized in the author's own way to make it more accessible. Therefore, please note that it contains expressions based on the author's interpretation.

## Five principles

### 1. Always combine states that change together into one

From *[Group related state](https://react.dev/learn/choosing-the-state-structure#group-related-state)*

As in the coordinate example below, values that always change together should be managed in a single state.
This way, you don't have to worry about synchronization of values.

```jsx
  // 👎 `x` and `y` always change together, but are managed in separate states
- const [x, setX] = useState(0);
- const [y, setY] = useState(0);

  // 👍 combine into one state.
+ const [position, setPosition]=useState({ x: 0, y: 0 }
```

### 2. Manage related states to avoid conflicts 

From *[Avoid contradictions in state](https://react.dev/learn/choosing-the-state-structure#avoid-contradictions-in-state)*

To avoid contradictions in state, consider what values to keep in what states.
Avoiding contradictions reduces the risk of bugs.

```jsx
export default function FeedbackForm() {
  // 👎 `isSending` and `isSent` can never be `true` at the same time, but that improbable state is possible
- const [isSending, setIsSending] = useState(false);
- const [isSent, setIsSent] = useState(false);

  // 👍 replace with a single state with one of the valid values ('typing' (initial state), 'sending', or 'sent')
+ const [status, setStatus] = useState('typing');

  async function handleSubmit(e) {
    e.preventDefault();
- setIsSending(true);
+ setStatus('sending');
    await sendMessage(text);
- setIsSending(false);
- setIsSent(true); + setStatus('sent'); await
+ setStatus('sent'); }
  }
  ...
```

### 3. Do not assign values to states that can be calculated from existing states or props

From *[Avoid redundant state](https://react.dev/learn/choosing-the-state-structure#avoid-redundant-state)*

Reducing unnecessary state management simplifies the code.
Computing computable values each time at render time eliminates the risk of out-of-sync, reducing complexity and the risk of bugs.

```jsx
export default function Form() {
  const [firstName, setFirstName] = useState('');
  const [lastName, setLastName] = useState('');

  // 👎 `fullName` can be computed from `firstName` and `lastName`, but we use state
- const [fullName, setFullName] = useState('');
  // 👍 compute `fullName` at render time.
+ const fullName = firstName + ' ' + lastName;

  function handleFirstNameChange(e) {
    setFirstName(e.target.value);
- setFullName(e.target.value + ' ' + lastName); }
  }

  function handleLastNameChange(e) {
    setLastName(e.target.value); }
- setFullName(firstName + ' ' + e.target.value); }
  }
  ...
}
```

Such a verbose state is also an example of inducing misuse of useEffect() 😣

```jsx
  const [firstName, setFirstName] = useState('');
  const [lastName, setLastName] = useState('');
  const [fullName, setFullName] = useState('');
  ...
  // 👎 Update the `fullName` state using `useEffect()`.
  useEffect(() => {
    setFullName(firstName + ' ' + lastName);
  }, [firstName, lastName]);
  ...
```
From *[You might not need an effect](https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state)*

Misuse of `useEffect()` can slow down your application due to extra re-rendering, make them more complex than necessary, and create the potential for bugs due to synchronization errors.

### 4. Avoid redundancy
From *[Avoid redundant state](https://react.dev/learn/choosing-the-state-structure#avoid-redundant-state)*

Reducing the number of duplicate states makes management easier. 
Keep only the necessary information in state.

```jsx
const initialItems = [
  { title: 'pretzels', id: 0 }, }
  { title: 'crispy seaweed', id: 1 }, }
  { title: 'granola bar', id: 2 }
];; }

export default function Menu() {
  const [items, setItems] = useState(initialItems);
   // 👎 Duplication occurs because the same object as one of the values of the `items` state is held in the `selectedItem`.
   // `items = [{ id: 0, title: 'pretzels'}, ...] `
   // `selectedItem = { id: 0, title: 'pretzels'}`
- const [selectedItem, setSelectedItem] = useState(items[0]);.
   // 👍 By keeping the `selectedId` in state, we avoid duplicates and keep only the state we need.
   // `items = [{ id: 0, title:'pretzels'}, ...] ;
   // `selectedId = 0`
+ const [selectedId, setSelectedId] = useState(0);

   // 👍 get selectedItem by searching the items array for the item with that ID.
+ const selectedItem = items.find(item =>
+ item.id === selectedId
+ ); 

  function handleItemChange(id, e) {
    setItems(items.map(item => {
      if (item.id === id) {
        return {
          ... .item,.
          title: e.target.value,.
        };
      } else {
        return item; }
      }
    }));
    // 👎 update `selectedItem` or it will be a bug
- setSelectedItem(...) ;
  }

  return (
    <>
      <h2>What's your travel snack?</h2> 
      <ul>
        {items.map((item, index) => (
          <li key={item.id}>
            <input
              value={item.title}
              onChange={e => {
                handleItemChange(item.id, e)
              }}
            />
            {' '}
            <button onClick={() => {
              setSelectedItem(item);
            }}>Choose</button>
          </li>
        ))}
      </ul>
      <p>You picked {selectedItem.title}. </p>
    </>
  );
}
```

### 5. Avoid deep nesting, make it flat

From *[Avoid deeply nested state](https://react.dev/learn/choosing-the-state-structure#avoid-deeply-nested-state)*

Complex nesting tends to be difficult to manage. If possible, rethink your data structure and make it flat.

```js
  // 👎 Deep nest
- export const initialTravelPlan = {
-   id: 0,
-   title: '(Root)',
-   childPlaces: [{
-     // 惑星レベル
-     id: 1,
-     title: 'Earth',
-     childPlaces: [{
-       // 大陸レベル
-       id: 2,
-       title: 'Africa',
-       childPlaces: [{
-         // 国レベル
-         id: 3,
-         title: 'Botswana',
-         childPlaces: []
-       }, { 
-         ...
-       }], 
-     }, {
-       // 大陸レベル
-       ...
-     }],
-   }, {
-     // 惑星レベル
-     ...
-   }],
- };
  // 👍 Make it flat
+ export const initialTravelPlan = {
+   0: {
+     id: 0,
+     title: '(Root)',
+     childIds: [1, 42, 46],
+   },
+   1: {
+     id: 1,
+     title: 'Earth',
+     childIds: [2, 10, 19, 26, 34]
+   },
+   2: {
+     id: 2,
+     title: 'Africa',
+     childIds: [3, 4, 5, 6 , 7, 8, 9]
+   }, 
+   3: {
+     id: 3,
+     title: 'Botswana',
+     childIds: []
+   },
+   ...
+ };
```

## Summary

**5 principles**
1. Always combine states that change together into one
2. Manage related states to avoid conflicts 
3. Do not assign values to states that can be calculated from existing states or props
4. Avoid duplication
5. Avoid deep nesting, make it flat

The Challenges section at the end of the original article provides a systematic understanding of these principles, and I encourage you to take the challenge!

https://react.dev/learn/choosing-the-state-structure

Happy coding!