1

In the code below, a keypress event detector is implemented through useEffect which attempts to update index through the useReducer function. However, the console.log output doesn't seem to show that the update works even though the button component's value updates.

On the other hand, when the button is clicked, the index variable seems to update as indicated by the console.log and the content of the button also updates as expected.

Why is there such a disparity and - if I needed to access the updated index variable after a keypress, how could I go about doing it?

The code is below:

const { useEffect, useReducer } = React;

const indexReducer = (state, action) => {
  if (action === "decrease") {
    return state-1
  } else if (action === "increase") {
    return state+1
  } else if (action === "reset") {
    return 0
  }
}

function App() {
  const [counter, setCounter] = useReducer(indexReducer, 0)
  const incrementCounter = () => {
    console.log(counter)
    setCounter("increase")
  }

  useEffect(() => {
    document.addEventListener("keydown", incrementCounter, false);

    return () => {
      document.removeEventListener("keydown", incrementCounter, false);
    };
  }, []);
  return (
    <div className="App">
      <button onClick={incrementCounter}>{"COUNT: " + counter}</button>
    </div>
  );
}

ReactDOM.render(<App />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.0/umd/react-dom.production.min.js"></script>
Nick Parsons
  • 45,728
  • 6
  • 46
  • 64
An0n1m1ty
  • 448
  • 4
  • 17

1 Answers1

2

incrementCounter is redefined on every render, and has a closure around the value of counter at the time it is defined. In the keydown handler's case, this is 0 (since your effect only runs once). Notice that the the onClick version is also always n-1 behind the button text.

You can move your logging into your reducer:

const { useEffect, useReducer } = React;

const indexReducer = (state, action) => {
  if (action === "decrease") {
    return state-1
  } else if (action === "increase") {
    console.log(state+1)
    return state+1
  } else if (action === "reset") {
    return 0
  }
}

function App() {
  const [counter, setCounter] = useReducer(indexReducer, 0)
  const incrementCounter = () => {
    setCounter("increase")
  }

  useEffect(() => {
    document.addEventListener("keydown", incrementCounter, false);

    return () => {
      document.removeEventListener("keydown", incrementCounter, false);
    };
  }, []);
  return (
    <div className="App">
      <button onClick={incrementCounter}>{"COUNT: " + counter}</button>
    </div>
  );
}

ReactDOM.render(<App />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.0/umd/react-dom.production.min.js"></script>

Or use a functional setState:

const { useEffect, useState } = React;

function App() {
  const [counter, setCounter] = useState(0)
  const incrementCounter = () => {
    setCounter(counter => {
      console.log(counter + 1)
      return counter + 1         
    })
  }

  useEffect(() => {
    document.addEventListener("keydown", incrementCounter, false);

    return () => {
      document.removeEventListener("keydown", incrementCounter, false);
    };
  }, []);
  return (
    <div className="App">
      <button onClick={incrementCounter}>{"COUNT: " + counter}</button>
    </div>
  );
}

ReactDOM.render(<App />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.0/umd/react-dom.production.min.js"></script>
shabs
  • 718
  • 3
  • 10
  • I see what you mean. Would you be able to suggest a method in which I can still use keypresses but have the updated `counter` – An0n1m1ty Jun 06 '21 at 09:28
  • You could log in your reducer instead of your handler, or use the functional version of useState to store the counter and log there. – shabs Jun 06 '21 at 09:39
  • I'm sorry, I'm really new to react. Could you please provide an example? I'm not quite sure what you mean – An0n1m1ty Jun 06 '21 at 09:48
  • No need to apologize. I've edited my answer. – shabs Jun 06 '21 at 17:08