0

I know the useEffect hook with an empty dependency array is the equivalent (but not the same) to the componentDidMount life cycle hook. I also know that useEffect should include all dependencies in the array, which may or may not cause additional unintended renders.

So I've read about how useReducer is the cheat mode of hooks for cases where you want to reference an external value without that value triggering a re-render. I want to know the effective differences between putting an effect in a reducer function compared to calling it in useEffect with intentionally left out dependencies.

Take the following example (omitted returns for brevity). Let's assume the goal is to perform an action only with the initial count given to the component, while allowing the count to change freely.

// Component mounting with a suppressed warning
function ComponentFoo(props) {
  const { count } = props;

  useEffect(() => {
    console.log("Component mounted with count", count);
  }, []); // eslint-disable-line
}
// Component mounting without warnings
function ComponentBar(props) {
  const { count } = props;

  const [, dispatchMount] = useReducer(() => {
    console.log("Component mounted with count", count);
  }, undefined);

  useEffect(dispatchMount, []);
}

From what I can tell, the above two components create the same results — They mount and log only the initial value of count without logging changes to it. While I feel the second approach is much more "correct" because it doesn't require suppressing an ESLint warning, is it really any different? Are there any caveats to doing the former that aren't also present in the latter?

Is this latter use of useReducer a poor pattern altogether, and if so, how would you instead achieve the result? I do have a feeling that this may be abusing useReducer in some way since it ignores the state aspect of it.

Perhaps it is a viable solution to create some state using the initial value and pass that as a legitimate dependency to useEffect? (Even though this perhaps needlessly duplicates the value)

// Component mounting without warnings
function ComponentBaz(props) {
  const [count] = useState(props.count);

  useEffect(() => {
    console.log("Component mounted with count", count);
  }, [count]);
}

I'd be interested to hear your thoughts on this and what solutions are appropriate, thanks.

Magnus Bull
  • 1,027
  • 1
  • 10
  • 21
  • The `useEffect` with empty dependency is the clear correct usage for side-effects on the initial render, it's basically right in the name, "useEffect". The version you have with `useReducer` still requires the `useEffect` so you've only introduced unnecessary complexity, and as you state, it's an abuse since you're not actually dealing with any state. It's a straight up anti-pattern in React to store passed props in local state. The fact that you *might* need to disable the linter for that line is only an issue with the linter, not the hook, and it's only a warning that you're free to ignore. – Drew Reese Jul 01 '21 at 09:35
  • 1
    I mean, if you really just want to log a value once when the component mounts and you don't care about legitimate hook use and dependencies you can use the `useState` hook sans the array destructuring and log a value in the initializer function, i.e. `useState(() => console.log(42));`. – Drew Reese Jul 01 '21 at 09:39
  • @DrewReese interesting use of `useState`. I hadn't considered that :) – Magnus Bull Jul 01 '21 at 10:01

0 Answers0