Reputation: 925
Here's a react input component:
function Input({ value, setValue }) {
return (
<div>
<input value={value} onChange={event => setValue(event.target.value)} />
<button onClick={() => setValue(value.toUpperCase())}>Capitalize</button>
</div>
);
}
It's just a vanilla input component together with a button that capitalizes the input's value. It's meant to be controlled by some parent component:
function Parent() {
let [value, setValue] = useState("");
return <Input value={value} setValue={setValue} />;
}
This works fine, but it's not idiomatic. To be useable as a "drop-in replacement" for a vanilla input component, it should take an onChange
prop, not setValue
, the relevant difference being that onChange
takes a synthetic event as an argument while setValue
takes a string. (I'd like the presence of the capitalize button to be "opaque" to a developer using this Input
component.)
I tried to idiomaticize this (see snippet below) by having the input element fire off a change event when the button is clicked, but the this doesn't cause onChange
to execute. (I assume that this is due to details of react's synthetic event system that I don't understand. I browsed a bunch of posts on this topic, but couldn't get the ideas I found to work.)
function AnotherInput({ value, onChange }) {
let input = useRef();
let handleClick = () => {
input.current.value = value.toUpperCase();
var event = new Event("change" /* also tried "input" */, {
bubbles: true
});
input.current.dispatchEvent(event); // onChange doesn't fire!
};
return (
<div>
<input value={value} ref={input} onChange={onChange} />
<button onClick={handleClick}>Capitalize</button>
</div>
);
}
Also, I feel I shouldn't have to use a ref
here because I don't want to modify the DOM directly; I just want to change the value in the controlling parent component.
Upvotes: 0
Views: 90
Reputation: 785
I made it work by simulating the event Object
on the Capitalize Button.
Parent Component:
function Parent() {
let [value, setValue] = useState("");
return <Input value={value} onChange={(e) => setValue(e.target.value)} />;
}
Input Component:
EDITED: I've managed to came up with a more elegant solution to the Input Component:
function Input({ value, onChange: inheritedOnChange }) {
return (
<div>
<input value={value} onChange={inheritedOnChange} />
<button value={value.toUpperCase()} onClick={inheritedOnChange}>Capitalize</button>
</div>
);
}
Note that i renamed the onChange
prop to inheritedOnChange
just for readability purposes. Preserving the onChange name at the destructuring should still work.
Upvotes: 1