Blog

The mistakes I made using React

Edit this Post Opens in new tab

Picture by Tai Bui

a cat sitting on a laptop keyboard

In this blog post I’m gonna share all the mistakes I made, using React. Lately there is a lot of dicussions going on around using Reacts useEffect. See my Today-I-learned posts missusing useEffect and data fetching in React 18.

The React team published a brand new documention on using React, which is fantastic. It’s still in beta but I could already refresh a lot of my knowledge. The best part of the documentation is the following section: you might not need an effect Opens in new tab .

It’s a whole page on when to not use useEffect and how to use useEffect. It has so many great real-world examples. I noticed immediately that I use a lot of patterns which were not the best way to solve things.

This made me realize I need to adjust my best practices for react Opens in new tab . In this blog post I’m gonna share everything I thought I did right when using React. But noticed now, I can solve it in a better way, according to the new react docs.


Table of Contents:


Updating state based on props or state

A common pattern I often used, was updating state when specific props changed. I almost always used an effect for this. Listen when this props change, fire the effect, update it. Guess what: You don’t need an effect for that! It’s unnecessary! And, as I learned now: inefficient! To quote the react docs:

"When something can be calculated from the existing props or state, don’t put it in state. Instead calculate it during rendering."

Don’t ❌:

// ...

const [street, setStreet] = React.useState('main street')
const [zipcode, setZipcode] = React.useState('70180')

const [fullAdress, setFulladress] = React.usestate('')

React.useEffect(() => {
  setFulladress(`${street} ${zipcode}`)
}, [street, zipcode])

// ...

Do ✅:

// ...

const [street, setStreet] = React.useState('main street')
const [zipcode, setZipcode] = React.useState('70180')

const fullAdress = `${street} ${zipcode}`

// ...

Don’t use useEffect for expensive caching

In this example we get three values as props, and based on them run an effect. Inside this effect we run a function to sort our data and set it into state.

Don’t ❌:

const MyProfile = ({ name, lastname, hobbies }) => {
  const [contactDetails, setContactDetails = useState(null)

	useEffect(() => {
		setContactDetails(getContactSortedByHobbiesAndName(name, lastname, hobbies)
	}, [name, lastname, hobbies])
}

This is, again, unnecessary and inefficient. We shouldn’t use useEffect to cache something like that. We don’t need to! We can remove the state, the whole useEffect and once more calculate this value during render!

Do ✅:

const MyProfile = ({ name, lastname, hobbies }) => {
  const contactDetails = getContactSortedByHobbiesAndName(
    name,
    lastname,
    hobbies
  )
}

As you may have already noticed, this is almost the same pattern as in the first example. But the React docs add another improvement to it. Imagine our function here is doing some expensive filtering by comparing thousands of rows. With a small adjustion we can add free caching to that, without using useEffect!

Even better ✅✅:

const MyProfile = ({ name, lastname, hobbies }) => {
  const contactDetails = useMemo(() => {
    return getContactSortedByHobbiesAndName(name, lastname, hobbies)
  }, [name, lastname, hobbies])
}

By adding useMemo we can cache this expensive filter or sort operation. It tells React that it does not need to re-run the inner function unless name, lastname or hobbies changes.

Use a cleanup function when doing a fetch in useEffect

I already talked a bit about how I used useEffect wrong in a today-i-learned post. The key takeaways from this were, that data fetching in react works, but you need a cleanup function. Otherwise you will face race conditions.

A classic data fetching in useEffect example, which I’ve done a lot, could look like this:

Don’t ❌:

const Searchbar = ({ searchTerm }) => {
  const [data, setData] = useState(null)

  useEffect(() => {
    const response = fetchFromSearchAPI(searchTerm)
    const data = response.json()
    setData(data)
  }, [searchTerm])
}

There is one big problem here. If a user triggers this search a few times and kicks off separate fetches, there is nothing implemented to handle stale responses. This might result in “wrong” results, which is a race condition. As the react docs put it: two different requests “raced” against each other and came in a different order than you expected.

The best way to fix this, is to add a cleanup function:

Do ✅:

const Searchbar = ({ searchTerm }) => {
  const [data, setData] = useState(null)

  useEffect(() => {
    let reset = false
    const response = fetchFromSearchAPI(searchTerm)
    const data = response.json()

    if (!reset) {
      setData(data)
    }

    return () => {
      reset = true
    }
  }, [searchTerm])
}

This ensures, React will only take the last response from the API and your component will avoid race conditions.

Avoid passing data to the parent

Another pattern I used almost every time when working in a React project is passing data from the child to the parent. Imagine you have a child component, which fetches some data. You pass a callback function to it as props to get the data to the parent.

Don’t ❌:

const Header = () => {
  const [data, setData] = useState(null)

  return <SearchBar onChange={setData} />
}

const Searchbar = ({ onChange }) => {
  const data = fetchSearchResults()

  useEffect(() => {
    if (data) {
      onChange(data)
    }
  }, [onChange, data])
}

This pattern seems weird at first. But it helped me for several specific use case a lot, where it made sense to me to fetch the data in the child component. But the obvious improvement here is to lift state up.

Do ✅:

const Header = () => {
  const data = fetchSearchResults()

  return <Searchbar data={data} />
}

const Searchbar = ({ data }) => {
  // ...
}

The data gets fetched in the parent component and then passed down to the child component. As the React docs Opens in new tab put it: React is all about one-way data flow down the component hierarchy. Makes error tracing a lot easier. Even though there were some arguments for the child data fetching, it’s always better to lift it up.

Conclusion

There are a lot of key take aways from this article. The main thing, that got stuck in my head is, to think twice whenever I write useEffect in the future. In most cases I don’t need to! The other key take aways are:

  • if it’s possible, calculate something during render and not in an effect
  • you don’t need effects to transform data for rendering
  • if your code should run because it should be displayed to the user, use an effect
  • if you fetch data deep down in your component tree, but the parent also needs this state, lift it up
  • if you want to fetch data in an useEffect, add a cleanup function

The new react docs talk about a lot more situations where you might not need an effect, with better examples and even some challenges for you. It’s awesome that they took the time to write something so detailed and helpful. I’m glad I could rethink some patterns which I thought were the best practices.

Grab a cup of coffee (or your favorite beverage) and take your time to read through it: you might not need an effect Opens in new tab . It’s so worth it and I learned a lot.


I hope you enjoyed this post and learned something new. If you have any questions, feel free to reach out to me on Twitter Opens in new tab or via Email Opens in new tab.

If you want to support me, you can buy me a coffee. I would be very happy about it!

☕️ Buy me a coffee ☕️

I wish you a wonderful day! Marco