Search code examples
javascriptreactjsdom-eventsreact-hookssetstate

React - fire callback based on state and first onClick event only


I have an accordion item and I am trying to only fire the method doSomething the first time the item is open:

const AccordionItem = ({index, a, b, c}: Props) => {
  const [isOpen, setIsOpen] = useState(false);
  const [counter, setCounter] = useState(0);

  if (isOpen) {
    setCounter(counter + 1);
  }

  if (counter === 1) {
    doSomething();
  }

  return (
    <Accordion>
      <Title onClick={() => setIsOpen(!isOpen)}>
      ...
    </Accordion>
  );

}

When I click the title, doSomething fires twice and I get an error: Uncaught Invariant Violation: Too many re-renders.

Why is it re-rendering like crazy?

How does doSomething fire twice if the condition is counter === 1?

I tried using useEffect to fire the event once but that didn't make sense as it fired on componentDidMount which wasn't triggered by the first click.

I would like to understand what is going on with useState, my logic and any pointers on how I can debug the re-renders.


Solution

  • Why is it re-rendering like crazy?

    I think that is because you are changing the state in the render function so that trigger a side-effect.

    I tried using useEffect to fire the event once but that didn't make sense as it fired on componentDidMount which wasn't triggered by the first click.

    useEffect its also componentDidUpdate event

    And you can handle the event in an auxiliary function "toggle"

    function App() {
      const [isOpen, setIsOpen] = useState(false);
      const [counter, setCounter] = useState(0);
    
      function toggle() {
        if (!isOpen) {
          setCounter(counter + 1);
        }
        setIsOpen(!isOpen);
      }
    
      useEffect(() => {
        if (counter === 1) {
          doSomething();
        }
      }, [counter]);
    
      return (
        <div className="App">
          <button onClick={() => toggle()}>Click here</button>
        </div>
      );
    }
    

    The "[counter]" says that whenever the counter variable change, the callback function is going to be executed.

     useEffect(() => {
           if (counter === 1) {
             doSomething();
           }
        }, [counter]);