24-Dec

React

UseEffects only for side effects

I have created numerous bugs by using useEffect wrong, and I have learned the hard way how to solve them. As a result of my errors I have acquired a set of best practices I like to follow, and I like to share them with you. Happy reading!

3 min read

·

By Caroline Odden

·

December 24, 2023

The useEffect hook is a superb way to synchronize your component with some external system. However, useEffect is often erroneously used for updating local state in components. Updating state is not a side effect; therefore, useEffect should not be used in that case. So, what is a side effect? A side effect is something that React can't control, something that is outside your component. Some examples include fetching data from an API or timing functions like setInterval and setTimeout.

I have encountered numerous bugs by using useEffect for updating local state, and I wondered if useEffect is bad. In my research, I took the time to read the React documentation and found an excellent article about You might not need an effect. I would like to recap some of the cases I have benefited from the most, and I strongly recommend both reading the documentation and keeping these in mind when writing useEffects.

Updating state

I've seen countless examples where an useEffect listens to some variables and then computes a new value, updating another state. This is unnecessary. It is better to declare and calculate the value directly in your component:

// Combining state into one variable
const [value1, setValue1] = useState()
const [value2, setValue2] = useState()

const value = computeSomething(value1, value2)

If the computation is expensive, you can use useMemo.

Resetting state

This is my favorite trick. Instead of creating an useEffect that listens to some props or state for changes and then changes your state, you can pass a key to your component. If the key changes, React will create a new instance of your component with the initial values for your state.

function ProfileList() {
  const profile = useProfile()
  
  return (
    <Profile key={profile.id} />
  )
}

function Profile() {
  const [name, setName] = useState()
  
  return (...)
}

Often, you only use the key property when looping through a list because this helps React re-rendering your list items when something changes, but this applies to single components as well!

Adjusting state

It's typical to want to adjust your state based on some props without resetting the whole component. Let's say you have a form with some pre-filled values, but you want that to change the input value based on some other props. If you save your props value in a local state and compare the updated props value with the stored previous prop value, you can update your state directly in your component.

function Form({ defaultInput }: Props) {
    const [input, setInput] = useState<string>(defaultPlaceholder)

    const [prevDefaultInput, setPrevDefaultInput = useState(defaultPlaceholder)

    // Instead of having it in a useEffect:
    if (defaultInput !== prevDefaultInput) {
        setInput(defaultInput)
    }
  
    return (...)
}

Keep event logic in event handlers

As far as possible, you should keep your event logic close to the event handlers. Don't put it in useEffects just because you save some lines of code. If a user is clicking something, typing something, or whatever, and if you, for example, want to give some feedback to the user, this logic should be in functions passed to the onClick or other event attributes. This will avoid buggy UI as you have more control over when the event logic is run.

function MyComponent() {
    const handleClick = () => {
        alert('You clicked me')
    }
  
    return (
        <button onClick={handleClick}>Click me</button>
    )
}

___

Hope you learned something about useEffects, and how you should avoid using them necessarily)!

Up next...

Loading…