Home > Enterprise >  Is it good practice to put functions inside useEffect?
Is it good practice to put functions inside useEffect?

Time:12-17

The question is:

Assuming I have a fairly extensive logic in the component. Should I put all this logic inside useEffects or should I move these functions outside useEffect and use useCallback?

I can't find any peer-reviewed approach to this topic.

Below is an example of what I mean:

  1. functions inside useEffect approach
  useEffect(() => {
    const fun1 = () => {
      /**
       * 50 lines of code
       */
    }
    fun1()
  }, [var1, var2])

  useEffect(() => {
    const fun2 = () => {
      /**
       * 50 lines of code
       */
    }
    fun2()
  }, [var3, var4])
  1. functions outside useEffect approach
  const fun1 = useCallback(() => {
    /**
     * 50 lines of code
     */
  }, [var1, var2])

  const fun2 = useCallback(() => {
    /**
     * 50 lines of code
     */
  }, [var3, var4])

  useEffect(() => {
    fun1()
  }, [fun1])

  useEffect(() => {
    fun2()
  }, [fun2])

Let's assume it's even more complicated. Which approach is preferred?

CodePudding user response:

Assuming I have a fairly extensive logic in the component.

You should probably move the logic out of the component and into a hook since that is one of the main problems that hooks attempt to solve.

Aside from that, your second method introduces an additional dependency of useCallback. What do you gain from doing that? Not much. But a lot is lost (more complexity plus an additional dependency).

CodePudding user response:

There are many ways to refactor a closure within the effect hook. Because you haven't shown your function code, consider the common example of the counter component:

Given that the following utility function is in scope...

function isSingular (n: number): boolean {
  return Math.abs(n) === 1;
}
function Example (): React.ReactElement {
  const [count, setCount] = useState(0);
  const [message, setMessage] = useState('');

  useEffect(() => {
    const updateMessage = () => {
      setMessage(`You clicked ${count} time${isSingular(count) ? '' : 's'}`);
    };
    updateMessage();
  }, [count, setMessage]);

  return (
    <div>
      <div>{message}</div>
      <button onClick={() => setCount(n => n   1)}>Increment</button>
    </div>
  );
}

The component uses two state variables, count and message (which is computed from count). A closure updateMessage is defined in the effect hook's callback which encapsulates count and setMessage. There are many ways to refactor this:

Important: No matter which method you use, if it involves the use of a dependency array, make sure the list of dependencies is correct and exhaustive.


You can define the closure outside the effect hook and pass it directly as the callback (this is the one you asked about):

function Example (): React.ReactElement {
  const [count, setCount] = useState(0);
  const [message, setMessage] = useState('');

  const updateMessage = () => {
    setMessage(`You clicked ${count} time${isSingular(count) ? '' : 's'}`);
  };

  useEffect(updateMessage, [count, setMessage]);

  return // the same JSX...
}

<div id="root"></div><script src="https://unpkg.com/[email protected]/umd/react.development.js"></script><script src="https://unpkg.com/[email protected]/umd/react-dom.development.js"></script><script src="https://unpkg.com/@babel/[email protected]/babel.min.js"></script><script>Babel.registerPreset('tsx', {presets: [[Babel.availablePresets['typescript'], {allExtensions: true, isTSX: true}]]});</script>
<script type="text/babel" data-type="module" data-presets="tsx,react">

// You'd use ESM:
// import ReactDOM from 'react-dom';
// import {default as React, useEffect, useState} from 'react';

// This snippet uses UMD:
const {useEffect, useState} = React;

function isSingular (n: number): boolean {
  return Math.abs(n) === 1;
}

function Example (): React.ReactElement {
  const [count, setCount] = useState(0);
  const [message, setMessage] = useState('');

  const updateMessage = () => {
    setMessage(`You clicked ${count} time${isSingular(count) ? '' : 's'}`);
  };

  useEffect(updateMessage, [count, setMessage]);

  return (
    <div>
      <div>{message}</div>
      <button onClick={() => setCount(n => n   1)}>Increment</button>
    </div>
  );
}

ReactDOM.render(<Example />, document.getElementById('root'));

</script>


You can define it as an impure function rather than a closure:

function updateMessage (
  count: number,
  setString: React.Dispatch<React.SetStateAction<string>>,
): void {
  setString(`You clicked ${count} time${isSingular(count) ? '' : 's'}`);
};

function Example (): React.ReactElement {
  const [count, setCount] = useState(0);
  const [message, setMessage] = useState('');

  useEffect(() => updateMessage(count, setMessage), [count, setMessage]);

  return // the same JSX...
}

You can extract to a custom hook everything related to the message state:

function useMessage (count: number): string {
  return `You clicked ${count} time${isSingular(count) ? '' : 's'}`;
};

function Example (): React.ReactElement {
  const [count, setCount] = useState(0);
  const message = useMessage(count);

  return // the same JSX...
}

You can use the memo hook:

function Example (): React.ReactElement {
  const [count, setCount] = useState(0);
  const message = useMemo(() => `You clicked ${count} time${isSingular(count) ? '' : 's'}`, [count]);

  return // the same JSX...
}

Hopefully that gives you some perspective which you can then apply to your component's code.

  • Related