Etika49
Etika49

Reputation: 752

react useEffect hook cleanup

I am trying to understand how is exactly react useEffect cleanup used and for what purpose. Could you please help me fix my code by adding the cleanup

as far as I am able to understand it's used in case of API calls in order to close a subscription. Still unable to clarify why it used and how exactly.

useEffect(() => {
        async function onLoad() {
        
            try {
              
              const examSessions = await loadSessions(id);
              setSessions(examSessions);
              console.log(`examSession`,examSessions)
            } catch (e) {
            //   onError(e);
                console.log(e)
            }
        
            setIsLoading(false);
        }


        onLoad()
    }, [id])



    function loadSessions(id) {
        const result = API.get("vce", `/session/${id}`)
        return result
    }

Upvotes: 0

Views: 594

Answers (2)

Aleks
Aleks

Reputation: 984

You don't need to use cleanup hook here. You are not subscribed to anything. When you subscribed in a useEffect to something and don't unsubscribe on a component destruction, this subscription will continue listening to remote source, leaching device memory and user data.

To test it, you can just attach say a click listener to a button, mount/unmount a component (without page refresh) and see in chrome dev tools how the list of subscriptions/listeners grows.

Edit: Try this

useEffect(() => {
        let unmounted = false;
        async function onLoad() {
        
            try {
              
              const examSessions = await loadSessions(id);
              setSessions(examSessions);
              console.log(`examSession`,examSessions)
            } catch (e) {
            //   onError(e);
                console.log(e)
            }
        
            if(!unmounted)setIsLoading(false);
        }


        onLoad()
        return ()=> unmounted = true;
    }, [id])

Upvotes: 0

zero298
zero298

Reputation: 26920

Without the cleanup, your effects will continue doing whatever they were doing forever even though the component has become unmounted.

This is important for complex components that might be responsible for setting up network things like Web Socket connections or requesting data using fetch. If you didn't cancel or close those, those resources would still be open and possibly sending signals.

Consider the following example where we can toggle a Foo component that ticks up. With the cleanup commented out, we still see "Tick" even though it has unmounted because we don't take the time to clean up the interval.

In fact, if you re-toggle the Foo back on, you'll start to see even more "Tick"s in console because there are now 2+ intervals ticking about. Imagine if that was a Web Socket and we were scheduling a new heartbeat tick every time we remounted; the server would be very angry at our apps heart palpitations.

const {
  useState,
  useEffect,
  createElement
} = React;
const {
  render
} = ReactDOM;

const Foo = () => {
  const [t, setT] = useState(0);

  useEffect(() => {
    const interval = setInterval(() => {
      console.log("Tick");
      setT(currT => currT + 1);
    }, 1000);

    // Without the following, you will see "Tick" forever because it isn't cleaned up
    //return () => clearInterval(interval);

  }, []);

  return createElement("div", {}, t);
};

const App = () => {
  const [should, setShould] = useState(true);

  return createElement("div", null, 
    createElement("button", {
      onClick: () => setShould(currShould => !currShould)
    }, "toggle"),
    should ? createElement(Foo) : null
  );
};

render(
  createElement(App),
  document.getElementById('app')
);
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>

<div id="app"></div>

Compare that to the following example which does have the cleanup which will stop showing "Tick" whenever you toggle the Foo component.

const {
  useState,
  useEffect,
  createElement
} = React;
const {
  render
} = ReactDOM;

const Foo = () => {
  const [t, setT] = useState(0);

  useEffect(() => {
    const interval = setInterval(() => {
      console.log("Tick");
      setT(currT => currT + 1);
    }, 1000);

    // Without the following, you will see "Tick" forever because it isn't cleaned up
    return () => clearInterval(interval);

  }, []);

  return createElement("div", {}, t);
};

const App = () => {
  const [should, setShould] = useState(true);

  return createElement("div", null, 
    createElement("button", {
      onClick: () => setShould(currShould => !currShould)
    }, "toggle"),
    should ? createElement(Foo) : null
  );
};

render(
  createElement(App),
  document.getElementById('app')
);
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>

<div id="app"></div>

Upvotes: 1

Related Questions