I have two input
s whose values reflect the state of a counter.
The first input
sets the state immediately as its value changes. This input
can represent other parts of the application changing state.
The second input
part of a component I made named LazyInput
. LazyInput
doesn't update state immediately, only when it's blurred, which I think gives a better usability. This input can represent the user input, like a timer to be set or the position of something on the screen.
However, I would like the LazyInput
not to behave "lazily" and actually update the counter immediately only when the value is changed via the ↑ ↓ keys.
The App
code (which includes the normal input
) is the following:
const {Fragment, useState, useEffect} = React;
function LazyInput({ name, value, onComplete }) {
const initialValue = value;
const [lazyValue, setLazyValue] = useState(value);
// Sync to state changed by the regular input
useEffect(() => {
setLazyValue(value);
}, [value]);
const handleKeyDown = e => {
const { key, target } = e;
switch (key) {
case "ArrowUp": {
setLazyValue(parseFloat(lazyValue) + 1);
onComplete(e);
break;
}
case "ArrowDown": {
setLazyValue(parseFloat(lazyValue) - 1);
onComplete(e);
break;
}
case "Escape": {
setLazyValue(initialValue);
target.blur();
break;
}
case "Enter": {
target.blur();
break;
}
default:
return;
}
};
return (
<input
name={name}
value={lazyValue}
onChange={e => setLazyValue(e.target.value)}
onKeyDown={e => handleKeyDown(e)}
onBlur={e => onComplete(e)}
/>
);
}
function App() {
const [counter, setCounter] = useState(0);
function handleCounterChange(e) {
setCounter(parseFloat(e.target.value));
}
return (
<Fragment>
<div>Counter: {counter}</div>
<LazyInput
name="counter"
value={counter}
onComplete={e => handleCounterChange(e)}
/>
<input
value={counter}
type="number"
onChange={e => handleCounterChange(e)}
/>
</Fragment>
);
}
ReactDOM.render(<App />, document.getElementById("app"));
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id="app"></div>
The problem is: pressing the ↑ ↓ keys fires the onComplete(e)
function before the setLazyValue()
– I suppose because it's asynchronous – losing sync with the state.
For the same reason, pressing Esc blurs the input
before resetting its value to initialValue
.
I know the setState()
class callback has been substituted by the useEffect()
hook, but:
- How do I call the
onComplete(e)
function – like any other event – fromuseEffect()
? - How do I do that only when the ↑ ↓ keys are pressed, but not for all other keys?
Thanks in advance for you help, here's an interactive sandbox.