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>
);
}
CodePudding user response:
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".
- Use
useEffect
to attach the event listener once the component is mounted and remove it again once the component is unmounted. - Provide a callback to
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
CodePudding user response:
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
CodePudding user response:
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;
}
});
}, [])