0

Yes, I know this question have been asked zillion times, but none of the answers are fit to my code.

My useEffect() calls an outside function (showIncrement()) that logs my increment state value. The problem is showIncrement() is also used by a button, so I can't move it inside the useEffect() scope.

I know a few solutions to this:

  • re-create the function inside useEffect(), but then I have two identical functions
  • use the React useCallback() function, React documentation call it the last resort, and other answer in another question also don't recommend using it, so I'm not really sure

The question is, what is the best way to solve this problem? Is it safe to use useCallback()?

Here's my code:

const App = () => {
    const [increment, setIncrement] = React.useState(2);
    const showIncrement = React.useCallback(() => console.log(increment), [
        increment,
    ]);

    React.useEffect(() => {
        showIncrement();
    }, [showIncrement]);

    return (
        <div className="App">
            <button type="button" onClick={showIncrement}>
                Show Increment
            </button>
        </div>
    );
};
SnekNOTSnake
  • 1,157
  • 12
  • 24
  • What is the meaning of this `useEffect`? Are you trying to call `showIncrement` on every render or just on mount? – Dennis Vash Jul 04 '20 at 15:58
  • your button using the same function would not prevent hoisting it outside the component. That is only not an option when `showIncrement` uses anything from the component scope. – hotpink Jul 04 '20 at 15:58
  • @DennisVash The code above is just a simplified version of my code. It doesn't have any meaning in real projects. I just want to know how `useEffect` works. – SnekNOTSnake Jul 04 '20 at 16:01
  • You asking generic question, it depends on what is the use case, `useCallback` depends on the use case – Dennis Vash Jul 04 '20 at 16:02
  • Then read [this](https://overreacted.io/a-complete-guide-to-useeffect/) – hotpink Jul 04 '20 at 16:03
  • Now because `useEffect` runs on every render, this `useEffect` pretty useless, you can just call `showIncrement()` in function body instead. See https://stackoverflow.com/questions/59841800/react-useeffect-in-depth-use-of-useeffect/59841947#59841947 – Dennis Vash Jul 04 '20 at 16:13

2 Answers2

0

If showIncrement doesn't need access to update the state variables, the easiest way to fix this is to move it outside the component, so it won't be recreated on every render and have a stable reference:

const showIncrement = (increment) => console.log(increment);

const App = () => {
    const [increment, setIncrement] = React.useState(2);

    React.useEffect(() => {
        showIncrement(increment);
    }, [increment]);

    return (
        <div className="App">
            <button type="button" onClick={() => showIncrement(increment)}>
                Show Increment
            </button>
        </div>
    );
};

Another way is to use the useCallback hook:

const App = () => {
  const [increment, setIncrement] = React.useState(2);
  const showIncrement = React.useCallback(() => console.log(increment), [
    increment
  ]);

  React.useEffect(() => {
    showIncrement();
  }, [showIncrement]);

  return (
    <div className="App">
      <button type="button" onClick={showIncrement}>
        Show Increment
      </button>
    </div>
  );
};
Clarity
  • 10,730
  • 2
  • 25
  • 35
0

As I understand this situation, you want to:

  • Re-use showIncrement logic.
  • Run showIncrement on component's mount.
  • Not violate linter warnings.

The problem here is that showIncrement depends on state value increment, so either way, your useEffect has to include increment in dep array.

Usually, you go for useCallback when its dep array ([increment] in this case) not frequently changes.

So depending on your use case, since showIncrement triggered only onClick it seems like a good choice.

const App = () => {
  const [increment] = React.useState(2);
  const isMounted = useRef(false);

  const showIncrement = useCallback(() => {
    console.log(increment);
  }, [increment]);

  React.useEffect(() => {
    if (!isMounted.current) {
      showIncrement();
    }
  }, [showIncrement]);

  return (
    <div className="App">
      <button type="button" onClick={showIncrement}>
        Show Increment
      </button>
    </div>
  );
};
Dennis Vash
  • 50,196
  • 9
  • 100
  • 118