'useReducer - keydown handler re-evaluates initial state on each render
https://codesandbox.io/s/suspicious-chebyshev-vy4mf2
I have a useReducer hook and useEffect with a keydown event handler. The issue is that on each re-render resulting from handling the keyboard button press, the component function re-runs the function obtaining initial state.
The visible incorrect result is the random number logged in console on each render (I expect no logging). In other words, the goal here is to stop getInitialState function running on every render.
import { useEffect, useReducer } from "react";
import "./styles.css";
interface IAction {
type: string;
payload: string;
}
export default function App() {
const reducer = (state: string, action: IAction) => {
switch (action.type) {
case "KEYDOWN":
return action.payload;
default:
return state;
}
};
const getInitialState = () => {
const rand = Math.random().toString();
console.log(rand);
return rand;
};
const [state, dispatch] = useReducer(reducer, getInitialState());
const keyDownHandler = (e: KeyboardEvent): void => {
dispatch({
type: "KEYDOWN",
payload: e.key
});
};
useEffect(() => {
document.addEventListener("keydown", keyDownHandler);
return () => document.removeEventListener("keydown", keyDownHandler);
}, [state, dispatch]);
return <div className="App">{state}</div>;
}
Solution 1:[1]
You were calling the getInitialState on every render, that doesn't mean it was getting re-initialized.
You can avoid this by running the initializer in the 3rd argument position which is designed for function usage.
You should also create the function for the reducer outside of the render function, otherwise it re-runs more often than it should.
const { useEffect, useReducer } = React;
interface IAction {
type: string;
payload: string;
}
const reducer = (state: string, action: IAction) => {
switch (action.type) {
case "KEYDOWN":
return action.payload;
default:
return state;
}
};
function App() {
const getInitialState = () => {
const rand = Math.random().toString();
console.log(rand);
return rand;
};
const [state, dispatch] = useReducer(reducer, null, getInitialState);
const keyDownHandler = (e: KeyboardEvent): void => {
dispatch({
type: "KEYDOWN",
payload: e.key
});
};
useEffect(() => {
document.addEventListener("keydown", keyDownHandler);
return () => document.removeEventListener("keydown", keyDownHandler);
}, [state, dispatch]);
return <div className="App">{state}</div>;
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Zachary Haber |
