1
votes

I am playing around with React Hooks. When the button is clicked, I want to increment a counter. After the counter was incremented, the application should not allow further increments until clicked is reset to false.

I came up with this:

function App() {
  const [counter, setCounter] = useState(0);
  const [clicked, setClicked] = useState(false);

  useEffect(() => {
    if (clicked) {
      setCounter(counter + 1);
      setTimeout(() => {
        setClicked(false);
      }, 2000);
    }
  }, [clicked]);

  return (
    <div className="App">
      <p>Clicked: {String(clicked)}</p>
      <p>Counter: {counter}</p>
      <button type="button" onClick={() => setClicked(true)}>
        Click me
      </button>
    </div>
  );
}

It actually works. However React is complaining with following warning:

React Hook useEffect has a missing dependency: 'counter'. Either include it or remove the dependency array. You can also do a functional update 'setCounter(c => ...)' if you only need 'counter' in the 'setCounter' call. (react-hooks/exhaustive-deps)

When I add the counter to the dependencies, useEffect will get into an infinite loop, because clicked is true and setCounter was called from within useEffect.

I want the counter only to be incremented, when clicked changed from false to true. That works if the dependency list only contains clicked, but React complains about that.

Try out for yourself: https://codesandbox.io/s/dreamy-shadow-7xesm

2
Just a question, what's wrong with doing setCounter(counter => counter + 1) this? - Kamran Nazir
the application should not allow further increments until clicked is reset to false when does this happen? is the timeout request required? - Semi-Friends

2 Answers

8
votes

Try replacing setCounter(counter + 1) with this:

setCounter(counter => counter + 1)

Like the warning says. Should solve it.

1
votes
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";

import "./styles.css";

function App() {
  const [counter, setCounter] = useState(0);
  const [clicked, setClicked] = useState(false);

  useEffect(() => {
    if (clicked) {
      setClicked(false);
      setCounter(counter + 1);
    }
  }, [clicked, counter]);

  return (
    <div className="App">
      <p>Clicked: {String(clicked)}</p>
      <p>Counter: {counter}</p>
      <button type="button" onClick={() => setClicked(true)}>
        Click me
      </button>
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

your problem with infinite loop will be gone if you remove the timeout (btw what is it for?) are you trying to implement a debounce or throttle? I can edit this answer to achieve what you want