Can you setState in useEffect?

Can you and should you useState inside a useEffect hook?

The answer is yes, you can set state inside useEffect, but you shouldn’t as it can cause additional renders or an infinite loop error.

Let’s look at some bad examples of using useState inside useEffect.

Problem #1: Using useState to communicate with the parent component

Here’s a common practice that I’ve seen in the work place. Where a child component may accept a callback functions.

And depending on the state the child component, it may do the side effect such as onOpen() or onClose().


export default function ChildComp({onOpen, onClose }) {
  const { isOpen, setOpen } = useState(false);

  useEffect(() => {
    if (isOpen) {
      onOpen();
    } else {
      onClose();
    }
  }, [isOpen])

  return (
    <div className="App">
      <button
        onClick={() => setOpen(!isOpen)}>
          Toggle View
      </button>
    </div>
  );
}

One of the problems with this method is that you may be causing additional renders due to the state change from the child to parent component.

A better solution, is to move these kind of functions closer to the event handlers when these changes happen. Here’s an example of better code.


export default function App({onOpen, onClose}) {
  const { isOpen, setOpen } = useState(false);

  function handleToggleView() {
    // Forward seeing the next value is `isOpen`
    const nextIsOpen = !isOpen;

    // Change state value
    setOpen(!isOpen);

    // Use `nextIsOpen` to handle side effects
    if (nextIsOpen) {
      onOpen();
    } else {
      onClose();
    }
  }

  return (
    <div className="App">
      <button
        onClick={handleToggleView}>
          Toggle View
      </button>
    </div>
  );
}

The solution now removes the use of useEffect and is forward seeing the next value of the state variable isOpen by assigning it to a variable called nextIsOpen.

The handler functions of onOpen() and onClose() are now happening within the event handler.

Problem #2: Infinite loops

Another problem with setting state within a useEffect() hook is that you can cause an infinite loop.


const { isOpen, setOpen } = useState(false);

useEffect(() => {
  setOpen(!isOpen)
});

This can cause some real issues, like crashing your React application.

In the case that you need to set state inside a useEffect hook, add the array dependency. Let’s look at some examples:


const { isOpen, setOpen } = useState(false);

// Happens once
useEffect(() => {
  setOpen(!isOpen)
}, []);

// Happens when `isOpen` is modified
useEffect(() => {
  if (!isOpen) {
    setOpen(true)
  }
}, [isOpen]);

A better solution than the code above is to just move it closer to the event handler as possible. Whatever decides that sets isOpen to equal true, that’s where that logic should live.

The point of the article is that you really don’t need useEffect 90% of the time.

I like to tweet about React and post helpful code snippets. Follow me there if you would like some too!