2

I understand a bit about the useEffect hook but I think there’s still more knowledge to grasp. Some of which are not in the documentation. Please any contribution will help a lot y’all.

Some of my questions are:

  1. Does useEffect get called on initial render even in production just like in development?

  2. If it does, how do we control this the best way?

  3. How can we use a clean up function on this Hook?

  4. How can we make asynchronous calls in useEffect?

My attempts on useEffect usually makes me feel like a bad developer

Drizzle
  • 198
  • 2
  • 11

2 Answers2

2

useEffect is a very powerful hook. Regarding your question:

  1. useEffect(() => (), []) - this version without params will be called once on initial rendering
  2. you can control useEffect with params [] and based on these params you can place some logic inside the callback function.
  3. clean up function used before unmount of your component, it is a good place to remove listeners or close connection to resources like Databases, Camera and etc.
  4. Example of async call
useEffect(() => {
  // declare the data fetching function
  const fetchData = async () => {
    const data = await fetch('https://yourapi.com');
  }

  // call the function
  fetchData()
    // make sure to catch any error
    .catch(console.error);
}, [])
Kirill Novikov
  • 2,576
  • 4
  • 20
  • 33
  • Your answer 1 buggles me Kirill. Even with params, useEffect runs once on initial render. This is an issue I think – Drizzle Dec 03 '22 at 09:36
  • 1
    yep with params, it runs but inside the hook you can specify your logic with if condition and run it only if your param equals the expected value. – Kirill Novikov Dec 03 '22 at 10:05
2

Please take a look at react docs and react beta docs.

  1. It always runs when your component mounts, after the first render regardless of the environment. In development mode when strict mode is on, it runs twice:

When Strict Mode is on, React will run one extra development-only setup+cleanup cycle before the first real setup. This is a stress-test that ensures that your cleanup logic “mirrors” your setup logic and that it stops or undoes whatever the setup is doing. If this causes a problem, you need to implement the cleanup function.

  1. I'm not really sure what you mean by controlling it the best way. Your effect or setup code runs whenever the component mounts. Maybe How to handle the Effect firing twice in development? can help you. You sometimes might want to prevent the effect to be executed when the component mounts, you can skip the effect by using a ref. See this stackoverflow question

  2. The function you return in the useEffect does the clean up for you. See. For instance if you add an event listener inside useEffect, you remove the listener inside the function you return inside of it. See this link

  useEffect(() => {
    const listener = () => { /* Do something */ };

    window.addEventListener("click", listener);

    return () => {
      window.removeEventListener("click", listener);
    };
  }, []);
  1. Yes you can. See this stackoverflow question and fetching data in docs
  useEffect(() => {
    async function asyncFunction() {
      /* Do something */
    }

    asyncFunction();
  }, []);

Update:

Take a look at You Might Not Need an Effect . It explains some situations which you might not need an effect at all.

Removing unnecessary Effects will make your code easier to follow, faster to run, and less error-prone.

Update 2:

You can probably skip this part for now, but it might help you to have a better grasp of useEffect, event handlers and what to expect in the future.

Separating Events from Effects tries to explain the differences between effects and event handlers, why distinguishing between those two is important and using event handlers inside effects.

Event handlers only re-run when you perform the same interaction again. Unlike event handlers, Effects re-synchronize if some value they read, like a prop or a state variable, is different from what it was during the last render. Sometimes, you also want a mix of both behaviors: an Effect that re-runs in response to some values but not others. This page will teach you how to do that.

Sometimes, you might use an event handler which has access to the props or the state inside an effect. But you don't want the useEffect to be triggered every time the values used in the event handler change. The following example is taken form useEffect shouldn’t re-fire when event handlers change .

function Chat({ selectedRoom }) {
  const [muted, setMuted] = useState(false);
  const theme = useContext(ThemeContext);

  useEffect(() => {
    const socket = createSocket('/chat/' + selectedRoom);
    socket.on('connected', async () => {
      await checkConnection(selectedRoom);
      showToast(theme, 'Connected to ' + selectedRoom);
    });
    socket.on('message', (message) => {
      showToast(theme, 'New message: ' + message);
      if (!muted) {
        playSound();
      }
    });
    socket.connect();
    return () => socket.dispose();
  }, [selectedRoom, theme, muted]); //  Re-runs when any of them change
  // ...
}

As you see, you do not want to reconnect every time theme or muted variables change. The only time you want the effect(connecting and disconnecting from the server) to run is when the selectedRoom value changes.

So the react team has proposed a RFC: useEvent which provides

A Hook to define an event handler with an always-stable function identity.

useEvent is an experimental and unstable API that has not yet been added to the React(stable versions) ye, so you can’t use it yet.

This might be off-topic but probably helps you to understand React and its lifecycles better: There is this issue useCallback() invalidates too often in practice issue on GitHub . One workaround would be to create a custom hook that returns a function that its identity is stable and won't change on re-renders:

function useEventCallback(fn) {
  let ref = useRef();
  useLayoutEffect(() => {
    ref.current = fn;
  });
  return useCallback(() => (0, ref.current)(), []);
}

Or you could use the use-event-callback package.

Note that useEventCallback does not mimic useEvent precisely:

A high-fidelty polyfill for useEvent is not possible because there is no lifecycle or Hook in React that we can use to switch .current at the right timing. Although use-event-callback is “close enough” for many cases, it doesn't throw during rendering, and the timing isn’t quite right. We don’t recommend to broadly adopt this pattern until there is a version of React that includes a built-in useEvent implementation.

c0m1t
  • 1,089
  • 1
  • 7
  • 16
  • Thank you c0m1t you did much on this question. Can you give more on the answer 3 you gave. How can I write a clean up function for an api call made with useEffect? – Drizzle Dec 03 '22 at 09:33
  • 1
    It really depends on your code and it is not in the scope of this question. See [how to abort the request](https://stackoverflow.com/questions/31061838/how-do-i-cancel-an-http-fetch-request) and [avoid race conditions](https://dev.to/nas5w/avoiding-race-conditions-when-fetching-data-with-react-hooks-4pi9). You might want to take a look at [react-query](https://tanstack.com/query/v4/) which handles lots of things for you. – c0m1t Dec 03 '22 at 10:25