I'm working on an online ide in which I have to add key shortcuts to run code, simply Ctrl Enter
to execute piece of code. I am done with the adding shortcuts part, but the main issue is that whenever I use the shortcuts the states that are getting passed, are not updated, and the initial ones are used instead.
I have tried to reproduce my situation in closest way possible in the following code:
import "./styles.css";
import { useState, useEffect } from "react";
export default function App() {
let [name, setName] = useState("John Doe");
useEffect(() => {
let handleShortcutKeys = (evt) => {
if (evt.ctrlKey) {
if (evt.key === "Enter") {
alert(name);
}
}
};
window.addEventListener("keyup", handleShortcutKeys);
return () => {
window.removeEventListener("keyup", handleShortcutKeys);
};
}, []);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<input
type="text"
placeholder="enter something..."
value={name}
onChange={(evt) => setName(evt.target.value)}
/>
<br />
<br />
<button onClick={() => alert(name)}>Alert Name</button>
</div>
);
}
You can play with this snippet here: https://codesandbox.io/s/event-listener-bug-mcve-u0zvff
It is a very basic code, but I don't know what is the error exactly, try to change the name and then, first try using the button Alert Name and then try to press Ctrl Enter
you will be able to see the difference.
Thanks in advance!
CodePudding user response:
You can use onKeyup
in input
.
import "./styles.css";
import { useState} from "react";
export default function App() {
const [name, setName] = useState("John Doe");
const handleShortcutKeys = (evt) => {
if (evt.ctrlKey) {
if (evt.key === "Enter") {
alert(name);
}
}
};
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<input
type="text"
placeholder="enter something..."
value={name}
onChange={(evt) => setName(evt.target.value)}
onKeyUp={handleShortcutKeys}
/>
<br />
<br />
<button onClick={() => alert(name)}>Alert Name</button>
</div>
);
}
Hope this would be helpful for you.
CodePudding user response:
When you register the event listener, it gets the value of the state at that moment. and since the useEffect
dependencies are empty it will run only once.
so you could simple add the name
dependency to the useEffect
dependencies.
useEffect(() => {
let handleShortcutKeys = (evt) => {
if (evt.ctrlKey) {
if (evt.key === "Enter") {
alert(name);
}
}
};
window.addEventListener("keyup", handleShortcutKeys);
return () => {
window.removeEventListener("keyup", handleShortcutKeys);
};
}, [name]);
alternatively you could move the event handler out of the useEffect
and wrap it in an useCallback
hook
const handleShortcutKeys = useCallback((evt) => {
if (evt.ctrlKey) {
if (evt.key === "Enter") {
alert(name);
}
}
}, [name])
useEffect(() => {
window.addEventListener("keyup", handleShortcutKeys);
return () => {
window.removeEventListener("keyup", handleShortcutKeys);
};
}, [handleShortcutKeys]);
CodePudding user response:
When an event listener is registered, it picks the value of the state variables, that were there at the moment it was registered. That's why it is giving you the same value always. To fix it, you can either add name
, in the useEffect
dependency array, or use a ref
, instead of state variable like this:
import "./styles.css";
import { useState, useEffect, useRef } from "react";
export default function App() {
const nameRef = useRef("John Doe");
useEffect(() => {
const handleShortcutKeys = (evt) => {
if (evt.ctrlKey) {
if (evt.key === "Enter") {
alert(nameRef.current);
}
}
};
window.addEventListener("keyup", handleShortcutKeys);
return () => {
window.removeEventListener("keyup", handleShortcutKeys);
};
}, []);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<input
type="text"
placeholder="enter something..."
defaultValue={nameRef.current}
onChange={(evt) => {
nameRef.current = evt.target.value;
}}
/>
<br />
<br />
<button onClick={() => alert(nameRef.current)}>Alert Name</button>
</div>
);
}
Sandbox link.