fmg
fmg

Reputation: 925

How can I make this react input component idiomatic?

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.

Here's a CodePen.

Upvotes: 0

Views: 90

Answers (1)

Japsz
Japsz

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

Related Questions