I wrote a program that adds or subtracts a unit to count by pressing the up and down keys on the keyboard.
The problem is that the program does not work properly. When you press the key, look at the console, which runs several times, while each time you press the key, it only has to run once.
This is a problem that crashes after pressing the up or down buttons a few times, please help
import React, { useState } from "react";
export default function Example() {
const [count, setCount] = useState(0);
document.addEventListener("keydown", function (e) {
switch (e.keyCode) {
case 38:
setCount(count + 1);
console.log(count);
break;
case 40:
setCount(count - 1);
console.log(count);
break;
}
});
return (
<div>
<p>You clicked {count} times</p>
</div>
);
}
You are adding an event handler every time the component is rendered. The component is re-rendered whenever the state changes. It's an exponential loop, hence your application "crashes".
useEffect to attach the event listener once the component is mounted and remove it again once the component is unmounted.setCount to access the current count.export default function Example() {
const [count, setCount] = useState(0);
useEffect(() => {
const keyDownCallback = function (e) {
switch (e.keyCode) {
case 38:
setCount((count) => count + 1);
break;
case 40:
setCount((count) => count - 1);
break;
}
};
document.addEventListener("keydown", keyDownCallback);
return () => document.removeEventListener("keydown", keyDownCallback);
}, []);
return (
<div>
<p>You clicked {count} times</p>
</div>
);
}
Documentation
Well each time your component re-renders, it attaches that event listener again and again and calls setCount too many times and re-renders component too many times. Therefore your program falls into infinite loop. I think you should try useEffect and useLayoutEffect hooks.
Edit
import React from "react";
export default function App() {
const [count, setCount] = React.useState(0);
const handleUpdateCount = React.useCallback(
(value) => setCount((prevCount) => prevCount + value),
[setCount]
);
React.useEffect(() => {
const keyDownCallback = (e) => {
switch (e.keyCode) {
case 38:
handleUpdateCount(1);
break;
case 40:
handleUpdateCount(-1);
break;
default:
console.log("default");
}
};
document.addEventListener("keydown", keyDownCallback);
return () => document.removeEventListener("keydown", keyDownCallback);
}, [handleUpdateCount]);
return (
<div>
<p>You clicked {count} times</p>
</div>
);
}
In these way, we prevent from extra re-renders.
And about useLayoutEffect, this hook runs given callback after all DOM mutations, But according to official React documentation, We should tend to use useEffect as possible. https://reactjs.org/docs/hooks-reference.html
You have to put document.addEventListener in an useEffect or it will add an event listener each time component re-render
useEffect(() => {
document.addEventListener("keydown", function (e) {
switch (e.keyCode) {
case 38:
setCount(count + 1);
console.log(count);
break;
case 40:
setCount(count - 1);
console.log(count);
break;
}
});
}, [])