A neat little trick to avoid useEffect in React
Tags: react, javascript, webdev, typescript
Published: 2023-07-17 09:16:47

## Problem

I came across a peculiar problem once. For the sake of this article, let's go with this contrived example:

I have a component `MyDumbComponent.tsx` and it receives an id with initial state value and uses that state to fetch some data.
That state can also be manipulated inside same component:

```tsx
import { useEffect, useState } from 'react'
import todos from '../data/todos.json'

type Unpacked<T> = T extends (infer U)[] ? U : T

export default function MyDumbComponent({ initialId }: { initialId: number }) {
  const [id, setId] = useState(initialId)
  const [todoData, setTodoData] = useState<Unpacked<typeof todos> | null>(null)

  useEffect(() => {
    const allTodos = todos
    const selectedTodo = allTodos.find(todo => todo.id === id) ?? null
    setTodoData(selectedTodo)
  }, [id])

  return (
    <>
      <div>
        <code>
          <pre>{JSON.stringify(todoData, null, 2)}</pre>
        </code>
      </div>
      <small>Child id: {id}</small>
      <br />
      <br />
      <button onClick={() => setId(prev => prev + 1)}>+</button>
      <button onClick={() => id > 1 && setId(prev => prev - 1)}>-</button>
    </>
  )
}
```

When I clicked on `+` and `-` button, it would change the id and will fetch a new todo detail and show it to user.
[See Code example demo](https://codesandbox.io/p/sandbox/crazy-sun-ntzxw2?file=%2Fsrc%2FApp.tsx%3A1%2C1)

This worked perfectly fine and as expected. The issue came when I _wanted to update the id(props) in parent_.
My dumb common sense would say it should also re-render. But to my suprise it didn't. Here I updated the state:

```tsx
import { useState } from 'react'
import './App.css'
import MyDumbComponent from './components/MyDumbComponent'

function App() {
  const [count, setCount] = useState(1)

  return (
    <div>
      <p> Parent state variable: {count} </p>
      <button onClick={() => setCount(c => c + 1)}>
        Increment parent state
      </button>
      <br /> <br />
      <br />
      <MyDumbComponent initialId={count} />
    </div>
  )
}

export default App
```

Here it can be seen [React does not 'reload' child component if the prop is used as an initial value for state, even when the prop gets changed](https://codesandbox.io/p/sandbox/nervous-wood-vykwgj?file=%2Fsrc%2FApp.tsx%3A2%2C1-3%2C1)

At first, it can be unintuitive. The reason is that React updates a component when it's state changes or it's props changes.
If React just throw away and child and re-make a new one everytime one of it's props gets changed, it would also have to create new DOM nodes, create new ones, and set those.
These can be expensive, specially if the props change frequently or has a large number of such changing props. All of that is expensive and slow. So, React will re-use the component that was there since [it's the same type and at the same position in the tree](https://github.com/facebook/react/issues/385#issuecomment-25385419).

## Using useEffect as a state updater

I am guilty of using a second effect in this scenario😅
It would like: _Hmm.. so we need to do something based on when the prop is changed.. what gets fired when the prop changed... useEffect with that prop in dependency!!_
So, I would add this effect after the 1st one(imo the first [useEffect should be relaced with react-query or some other data fetching lib](https://www.shivamjha.io/blog/useEffect-firing-twice#fetching-data), too).
But none-the-less, this is how that would go:

```tsx
useEffect(() => {
  // Changing children's state whenever our prop `initialId` changes
  setId(initialId)
}, [initialId])
```

Here it can be seen [this appraoch of using an useEffect (_tongue-twister, right?_) to update the vale of state initialized with some prop works](https://codesandbox.io/p/sandbox/elegant-heyrovsky-t9smhj?file=%2Fsrc%2Fcomponents%2FMyDumbComponent.tsx%3A16%2C20)

But this solution can be better. The [useEffect updated the value of state in 2nd render](https://www.shivamjha.io/blog/useEffect-firing-twice#but-first-what-is-an-effect).
Also, it is a good rule of thumb to prevent using useEffect as long as one can. I have noticed this increases readability and prevent some bugs with not-very-cared use of useEffect.
This advice has helped me remembering this: useEffect should only be used when an external service / something outside the React paradigm (like custom listeners) need to be integrated with React.
So `useEffect` can be thought of as `useSyncronise`

## Solution: using Keys to "reload" a React Component

So, what is the way? Keys to the rescue!!🔑 If a component has a key and it changes, [React skips comparion and makes new fresh component](https://react.dev/learn/rendering-lists#why-does-react-need-keys)
So you can consider Keys as an "identity" or source of truth, if you will, for a component. Hence, if the Key changes, the component must be reloaded from scratch.
This is the same reason you need keys while rendering a list, so that React can differentiate you list items when (if) their position / order changes within that list.

So, in our case, we can just [pass the key to child component and it will be recreated from scratch](https://codesandbox.io/p/sandbox/dry-brook-4rfxgh?file=%2Fsrc%2FApp.tsx%3A16%2C24):

```tsx
import { useState } from 'react'
import './App.css'
import MyDumbComponent from './components/MyDumbComponent'

function App() {
  const [count, setCount] = useState(1)

  return (
    <div>
      <p> Parent state variable: {count} </p>
      <button onClick={() => setCount(c => c + 1)}>
        Increment parent state
      </button>
      <br /> <br />
      <br />
      <MyDumbComponent key={count} initialId={count} />
    </div>
  )
}

export default App
```

Conveniently, I found that the new React docs has an article on [resetting state with Keys](https://react.dev/reference/react/useState#resetting-state-with-a-key)

## Further Read:

- [Why does React needs Keys](https://react.dev/learn/rendering-lists#why-does-react-need-keys)
- [Understanding useEffect](https://www.shivamjha.io/blog/useEffect-firing-twice#what-is-useeffect)
- [Synchronizing with Effects](https://react.dev/learn/synchronizing-with-effects)
- [You Might Not Need an Effect](https://react.dev/learn/you-might-not-need-an-effect)
- [React's useEffect are reactive with isolated updates](https://react.dev/learn/lifecycle-of-reactive-effects#each-effect-represents-a-separate-synchronization-process)