DEV Community

Cover image for React.useEffect hook explained with practical examples
CRUNCHSTACK
CRUNCHSTACK

Posted on

React.useEffect hook explained with practical examples

useEffect hook are very useful to manage side effects and to control lifecycle methods. However, many subtleties in its implementation can cause unexpected behavior, causing all kinds of bugs. 🐛


Lifecycle definition 📖📖📖

The life cycle is defined as the behavior of a component while it exists.

  1. Mounting : React.Element returned by the component is injected for the first time

  2. Updating : React.Element is updated according to the value of the shouldComponentUpdate() lifecycle method

  3. Unmounting : React.Element is removed from the DOM



Usage and definition ⚙️⚙️⚙️

To use useEffect, we need to import it into our file from React.

Then we can use it in a function component or in a custom hook. Let's use it in the first way. useEffect takes as parameters a callback and an array of dependencies.

import React, { useEffect } from "react"

function Effect() {
    useEffect(() => {
        console.log("I'm called on mounting")
    }, []);

    return (
        <div></div>
    )
}
Enter fullscreen mode Exit fullscreen mode

componentDidMount()/componentWillUnmount()

useEffect with an empty dependency array and a callback that returns a cleanup function is like using componentDidMount() and componentWillUnmount() in this way.

Cleanup function can be anonymous function as well.

componentDidMount() {
    console.log("Called on mounting");
}

componentWillUnmount() {
    console.log("Called on unmounting");
}
Enter fullscreen mode Exit fullscreen mode
useEffect(() => {
    console.log("Called on mounting");

    return function cleanup() {
        console.log("Called on unmounting");
    }
}, [])
Enter fullscreen mode Exit fullscreen mode

componentDidMount()/componentDidUpdate() with dependency check

useEffect with an array of dependencies is the same as using componentDidMount() and componentDidUpdate() together with a props and state comparison.

componentDidUpdate(prevProps, prevState, snapshot) {
    if (prevState.number === this.state.number)
        return;
    console.log("Called when number state change");
}

componentDidMount() {
    console.log("Called when number state change");
}
Enter fullscreen mode Exit fullscreen mode
useEffect(() => {
    console.log("Called when number state change")
}, [number])
Enter fullscreen mode Exit fullscreen mode

componentDidMount()/componentDidUpdate without dependency check

useEffect without an array of dependencies is like using componentDidMount() and componentDidUpdate() together without props and state comparison

componentDidUpdate(prevProps, prevState, snapshot) {
    console.log("Called when number state change");
}

componentDidMount() {
    console.log("Called when number state change");
}
Enter fullscreen mode Exit fullscreen mode
useEffect(() => {
    console.log("Called when number state change")
})
Enter fullscreen mode Exit fullscreen mode

componentDidUpdate() only

useEffect is not designed to handle componentDidUpdate() only.

You must check that the component has already been rendered with a reference. A custom hook is made for that, here it is

componentDidUpdate(prevProps, prevState, snapshot) {
    if (prevState.number === this.state.number)
        return;

    console.log("Called when number state change");
}
Enter fullscreen mode Exit fullscreen mode
function useUpdateEffect(cb, deps) {
    const mountedRef = useRef(false);

    useEffect(() => {
        if (!mountedRef.current) {
            mountedRef.current = true;
            return;
        }

        cb();
    }, deps);
}
Enter fullscreen mode Exit fullscreen mode

Usage 1 : useEffect to subscribe/unsubscribe to API 👨‍💻

Most of the time you have to subscribe to services to benefit from features.

Here we use the browser's setInterval API to manage a timer that updates every second.

The callback passed as parameter to useEffect is executed when the component is mounted, setInterval is launched at this time.

When the component is unmounted, we clean up our interval so that it does not run anymore.

function Timer() {
    const [date, setDate] = useState(null);
    const idRef = useRef(null);

    useEffect(() => {
        idRef.current = window.setInterval(() => {
            setDate(new Date().toLocaleTimeString());
        }, 1000);

        return function cleanup() => {
            window.clearInterval(idRef.current);
        }
    }, []);

    return (
        <div>
            {date}
        </div>
    );
}
Enter fullscreen mode Exit fullscreen mode

Usage 2 : useEffect to manage side effects when modifying local state. 👨‍💻👨‍💻👨‍💻

Side effects are common in React applications.

Here, we useEffect to verify that the number entered by the user is a number.

We perform this operation at the mounting and updating phases

function NumberInput() {
    const [ number, setNumber ] = useState('');
    const [ error, setError ] = useState(false); 

    const handleChange = useCallback(e => {
        const { value } = e.target;
        setNumber(value);
    }, []);

    const check = useCallback(number => {
        return number && Number.parseInt(number, 10);
    }, [number]);

    useEffect(() => {
        const checked = check(number);
        setError(!checked);
    }, [number])

    return (
        <div>
            <input type="text" value={number} onChange={handleChange} />
            {error ? `Please enter a number` : `You entered ${number}`}
        </div>
    )
}
Enter fullscreen mode Exit fullscreen mode

6.0 Pros and cons

useEffect is not a perfect tool and is not intended to completely replace life cycle management with Class Component

6.0.1 useEffect pros

  • Write more concise code ✔️
  • Separate concerns by using multiple useEffect in the same component ✔️
  • Extract logic from useEffect ✔️

6.0.2 useEffect cons

  • Don't have full control over the life cycle of the component ❌
  • There are subtleties to take into account that complicate the development a bit ❌

6.0.3 Lifecycle Class Component pros

  • Full lifecycle control ✔️
  • No ambiguity regarding behavior ✔️

6.0.4 Lifecycle Class Component cons

  • Much more verbose code ❌
  • Repeated code in different lifecycle functions. ❌
  • One must use a HOC to separate rendering logic from lifecycle and state management logic, which can be quite verbose ❌

Thank you for reading.

I hope you have learned a lot from this article and that like me, your mastery of front-end development has improved.

Do not hesitate to correct errors or ask me questions in the comments, I will respond to them as soon as possible

If you like my content and want to support me, don't hesitate :

  1. Subscribe to my Github, I push code almost every day

  2. Subscribe to my Frontend mentor profile, I make challenges and I'm quite active in the community

Top comments (0)