Often I will have an input made of two inputs. For example, a slider with a number input, or a colour picker with a hex input. These components need to announce a change of state whenever the user is done manipulating them, but they need to inform each other of every change. Each needs to track changes in the other with a finer granularity than the parent.
For example, if the user drags the slider then the number input should represent the value of the slider at all times. When the user types in the number input, the slider should jump around and stay in sync. When the user releases the slider, then an onChange
callback should be fired from the component so that the parent can update the state.
For clarity: in the example below, if the user clicks "up" on the left input 10 times I would like to see each change reflected on the left input but only exactly 1 change in the parent, when the component looses focus.
Below is some code which implements this behaviour. This code does exactly what I want it to do. The behaviour is 100% correct for my use case. However, I do not want to have a useEffect
in this component. That's my question "how can I remove this useEffect?"
import "./styles.css";
import { useEffect, useState } from "react";
export default function App() {
const [state, setState] = useState(0);
// this is called whenever the user is "done" manipulating the compound input
const handleChange = (change) => {
console.log("change", change);
setState(change);
};
return (
<div className="App">
<CompoundInput value={state} onChange={handleChange} />
</div>
);
}
function CompoundInput(props) {
const [internalState, setInternalState] = useState(props.value);
// this is a _controlled_ component, so this internal state
// must also track the parent state
useEffect(() => {
setInternalState(props.value);
}, [props.value]);
// each input updates to reflect the state of the other input
// but does so without the parent knowing
return (
<>
<input
type="number"
value={internalState}
onChange={(e) => setInternalState(e.target.value)}
onBlur={(e) => props.onChange(e.target.value)}
/>
<input
type="number"
value={internalState}
onChange={(e) => setInternalState(e.target.value)}
onBlur={(e) => props.onChange(e.target.value)}
/>
</>
);
}