Forms
Forms in Preact work much the same as they do in HTML. You render a control, and attach an event listener to it.
The main difference is that in most cases the value is not controlled by the DOM node, but by Preact.
Controlled & Uncontrolled Components
When talking about form controls you'll often encounter the words "Controlled Component" and "Uncontrolled Component". The description refers to the way data flow is handled. The DOM has a bidirectional data flow, because every form control will manage the user input themselves. A simple text input will always update it's value when a user typed into it.
A framework like Preact in contrast generally has a unidirectional data flow. The component doesn't manage the value itself there, but something else higher up in the component tree.
// Uncontrolled, because Preact doesn't set the value
<input onInput={myEventHandler} />;
// Controlled, because Preact manages the input's value now
<input value={someValue} onInput={myEventHandler} />;Run in REPLGenerally, you should try to use Controlled Components at all times. However, when building standalone Components or wrapping third-party UI libraries, it can still be useful to simply use your component as a mount point for non-preact functionality. In these cases, "Uncontrolled" Components are nicely suited to the task.
One gotcha to note here is that setting the value to
undefinedornullwill essentially become uncontrolled.
Creating A Simple Form
Let's create a simple form to submit todo items. For this we create a <form>-Element and bind an event handler that is called whenever the form is submitted. We do a similar thing for the text input field, but note that we are storing the value in our class ourselves. You guessed it, we're using a controlled input here. In this example it's very useful, because we need to display the input's value in another element.
class TodoForm extends Component {
state = { value: '' };
onSubmit = e => {
alert("Submitted a todo");
e.preventDefault();
}
onInput = e => {
const { value } = e.target;
this.setState({ value })
}
render(_, { value }) {
return (
<form onSubmit={this.onSubmit}>
<input type="text" value={value} onInput={this.onInput} />
<p>You typed this value: {value}</p>
<button type="submit">Submit</button>
</form>
);
}
}Run in REPLSelect Input
A <select>-Element is a little more involved, but works similar to all other form controls:
class MySelect extends Component {
state = { value: '' };
onChange = e => {
this.setState({ value: e.target.value });
}
onSubmit = e => {
alert("Submitted " + this.state.value);
e.preventDefault();
}
render(_, { value }) {
return (
<form onSubmit={this.onSubmit}>
<select value={value} onChange={this.onChange}>
<option value="A">A</option>
<option value="B">B</option>
<option value="C">C</option>
</select>
<button type="submit">Submit</button>
</form>
);
}
}Run in REPLCheckboxes & Radio Buttons
Checkboxes and radio buttons (<input type="checkbox|radio">) can initially cause confusion when building controlled forms. This is because in an uncontrolled environment, we would typically allow the browser to "toggle" or "check" a checkbox or radio button for us, listening for a change event and reacting to the new value. However, this technique does not transition well into a world view where the UI is always updated automatically in response to state and prop changes.
Walk-Through: Say we listen for a "change" event on a checkbox, which is fired when the checkbox is checked or unchecked by the user. In our change event handler, we set a value in
stateto the new value received from the checkbox. Doing so will trigger a re-render of our component, which will re-assign the value of the checkbox to the value from state. This is unnecessary, because we just asked the DOM for a value but then told it to render again with whatever value we wanted.
So, instead of listening for a input event we should listen for a click event, which is fired any time the user clicks on the checkbox or an associated <label>. Checkboxes just toggle between Boolean true and false, so clicking the checkbox or the label, we'll just invert whatever value we have in state, triggering a re-render, setting the checkbox's displayed value to the one we want.
Checkbox Example
class MyForm extends Component {
toggle = e => {
let checked = !this.state.checked;
this.setState({ checked });
};
render(_, { checked }) {
return (
<label>
<input
type="checkbox"
checked={checked}
onClick={this.toggle}
/>
</label>
);
}
}Run in REPL