Controlled fields
By default, all form fields are uncontrolled because that's the default behavior of the browser. For a simple login or contact form this is also quite sufficient.
Why controlled?
As soon as your forms become more complex, for example you set initial values or change the values of a form field via setValue
, it becomes necessary that you control your fields yourself. For example, depending on which HTML form field you use, you may need to set the value
, checked
or selected
attributes.
Text input example
For a text input field you simply add the value
attribute and pass the value of the field or in case of undefined
or null
an empty string:
<Field name="firstName">
{(field, props) => (
<input
{...props}
type="text"
// Pass value or empty string
value={useComputed(() => field.value.value || '')}
/>
)}
</Field>
Exception for files
The HTML <input type="file" />
element is an exception because it cannot be controlled. However, you have the possibility to control the UI around it. For inspiration you can use the code of our FileInput
component from our playground.
Numbers and dates
To make the fields of numbers and dates controlled, further steps are required, because the <input />
element native understands only strings as value.
Number input example
Since not every input into an <input type="number" />
field is a valid number, for example when typing floating numbers, the value may be NaN
in between. You have to catch this case, otherwise the whole input will be removed when NaN
is passed. It is best to encapsulate this logic in a separate component as described here.
import { useSignal, useSignalEffect } from '@preact/signals';
import { forwardRef } from 'preact/compat';
type NumberInputProps = { … };
export const NumberInput = forwardRef<HTMLInputElement, NumberInputProps>(
({ value, ...props }, ref) => {
// Update signal if value is not `NaN`
const input = useSignal<number | undefined>(undefined);
useSignalEffect(() => {
if (!Number.isNaN(value.value)) {
input.value = value.value;
}
});
return <input {...props} ref={ref} type="number" value={input} />;
}
);
Date input example
A date or a number representing a date must be converted to a string before it can be passed to an <input type="date" />
field, for example. Since it is a calculated value, you can use for this.
import { useComputed } from '@preact/signals';
import { forwardRef } from 'preact/compat';
type DateInputProps = { … };
export const DateInput = forwardRef<HTMLInputElement, DateInputProps>(
({ value, ...props }, ref) => {
// Transform date or number to string
const input = useComputed(() =>
value.value &&
!Number.isNaN(
typeof value.value === 'number'
? value.value
: value.value.getTime()
)
? new Date(value.value).toISOString().split('T', 1)[0]
: ''
);
return <input {...props} ref={ref} type="date" value={input} />;
}
);