Intro to React useEffect Hook

An introduction on how to use useEffect Hook

What is a useEffect Hook?

TLDR

React useEffect is an alternative to the "old" class lifecycle methods/hooks.

It can be used to manage side effects, such as network requests, or to run a piece of code when the component is mounted, updated, or unmounted.

Longer version

Prior to React v16.8, we can only enable a component
to react to state changes using lifecycle methods.

How to define a useEffect

useEffect(() => { //(1) declaration
  // (2)effect
  return () => {
    // (3)cleanup 
  }
}, /* (4)dependency array */)

Here's an explanation of the above code:

  1. We can declare an effect by calling either React.useEffect or useEffect
  2. effect is the function that will be called when the component is mounted OR when the dependency array changes.
  3. cleanup is the function that will be called when the effect "unmounted".
  4. dependency array is the array of values that will be passed to the effect function.
    • If there is no dependency array, the effect will be called every time the component is mounted.
    • If the array is empty, the effect will be called only once when the component is mounted.
    • If the array is not empty, the effect will be called every time the component is mounted and the dependency array changes.

Examples

Increment a counter every second until it reaches 10

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

  useEffect(() => {
    if (count < 10) {
      const interval = setInterval(() => {
        setCount(prev => prev + 1)
      }, 1000)

      // cleanup function
      return () => clearInterval(interval)
    }
  }, [count])

  // Render the component
}

Basic fetch from an API

function App() {
  const [data, setData] = useState([])

  useEffect(() => {
    fetch('https://jsonplaceholder.typicode.com/users')
      .then(res => res.json())
      .then(data => setData(data))
  }, [])

  // Render the component
}

Fetching with loading indicator + error handling - then/catch/finally

function App() {
  const [data, setData] = React.useState()
  const [error, setError] = React.useState()
  const [isLoading, setIsLoading] = React.useState(false)

  React.useEffect(() => {
    setIsLoading(true)

    fetchData()
      .then(data => {
        setError(null)
        setData(data)
      })
      .catch(data => {
        // handle error case anyway you want
        setError(data)
        setData(null)
      })
      .finally(() => setIsLoading(false))
  }, [])

  // Render the component
}

Fetching with loading indicator + error handling - async/await

function App() {
  const [data, setData] = React.useState()
  const [error, setError] = React.useState()
  const [isLoading, setIsLoading] = React.useState(false)

  React.useEffect(() => {
    // yeah, this is weird
    (async () => {
      try {
        setIsLoading(true)
        const data = await fetchData()
        setError(null)
        setData(data)
      } catch(e) {
        // handle error case anyway you want
        setError(e)
        setData(null)
      }
      setIsLoading(false)
    })()
  }, [])

  // Render the component
}

Store a value in localStorage everytime the key or value changes

function App({ name }) {
  const [value, setValue] = useState(() => localStorage.getItem(name))

  useEffect(() => {
    localStorage.setItem(name, value)
  }, [name, value])
  // Ignore old keys for now

  // Render the component
}

OR mimic a class lifecycle method

Check this blog on how to convert a class lifecycle methods to useEffect hooks

Additional Note

  • useEffect can only be used in functional components
  • The order of useEffect declarations are important.
  • useEffect in a custom hook is a great way to promote side effect reusability. I will discuss this in another blog.

Conclusion

Compared to the old lifecycle methods, useEffect is much more powerful and flexible, making it an ideal choice when managing a side-effect.

If you find it useful and you want to support me

25