I want to make my own input elements behave like default <input />
in React, allowing it to be either controlled (with value
) or uncontrolled (with defaultValue
).
I implemented this in TypeScript and everything went smoothly until I added another prop to my component. It messed up the relationship between value
, defaultValue
and onChange
.
Here’s my implementation before adding an extra prop:
type OnChangeCallback = (newValue: string) => void;
interface Uncontrolled {
defaultValue?: string;
onChange?: OnChangeCallback;
}
interface Controlled {
value: string;
onChange: OnChangeCallback;
}
type Input = Uncontrolled | Controlled;
const input1: Input = {}; // OK
const input2: Input = {
defaultValue: "Hello, world!",
}; // OK
const input3: Input = {
onChange: newValue => console.log(`New value is ${newValue}`),
}; // OK
const input4: Input = {
value: "Hello, world!",
}; // Error: Missing onChange
const input5: Input = {
value: "Hello, world!",
onChange: newValue => console.log(`New value is ${newValue}`),
}; //OK
Then I added a prop named label
, just to experiment:
type Input = (Uncontrolled | Controlled) & {
label: string;
};
Now, TypeScript allowed for a component with value
without onChange
:
const why: Input = {
label: "Oh dear",
value: "Hello, world!",
}; //OK?
Where did the requirement for onChange
when there’s value
go when I just added another property?