Input components
To make your code more readable, we recommend that you develop your own input components if you are not using a prebuilt UI library. There you can encapsulate logic to display error messages, for example.
If you're already a bit more experienced, you can use the input components we developed for our playground as a starting point. You can find the code in our GitHub repository here.
Why input components?
Currently, your fields might look something like this:
<Field name="email" validate={…}>
{(field, props) => (
<div>
<label for={field.name}>Email</label>
<input
{...props}
id={field.name}
value={field.value}
type="email"
required
/>
{field.error && <div>{field.error}</div>}
</div>
)}
</Field>
If CSS and a few more functionalities are added here, the code quickly becomes confusing. In addition, you have to rewrite the same code for almost every form field.
Our goal is to develop a TextInput
component so that the code ends up looking like this:
<Field name="email" validate={…}>
{(field, props) => (
<TextInput
{...props}
type="email"
label="Email"
value={field.value}
error={field.error}
required
/>
)}
</Field>
Create an input component
In the first step, you create a new file for the TextInput
component and, if you use TypeScript, define its properties.
import { JSX } from 'solid-js';
type TextInputProps = {
name: string;
type: 'text' | 'email' | 'tel' | 'password' | 'url' | 'date';
label?: string;
placeholder?: string;
value: string | undefined;
error: string;
required?: boolean;
ref: (element: HTMLInputElement) => void;
onInput: JSX.EventHandler<HTMLInputElement, InputEvent>;
onChange: JSX.EventHandler<HTMLInputElement, Event>;
onBlur: JSX.EventHandler<HTMLInputElement, FocusEvent>;
};
Component function
In the next step, add the component function to the file and use Solid's splitProps
utility to separate the properties of the HTML <input />
element from the rest.
import { JSX, splitProps } from 'solid-js';
type TextInputProps = { … };
export function TextInput(props: TextInputProps) {
const [, inputProps] = splitProps(props, ['value', 'label', 'error']);
}
JSX code
After that, next you can add the JSX code to the return statement.
import { JSX, splitProps } from 'solid-js';
type TextInputProps = { … };
export function TextInput(props: TextInputProps) {
const [, inputProps] = splitProps(props, ['value', 'label', 'error']);
return (
<div>
{props.label && (
<label for={props.name}>
{props.label} {props.required && <span>*</span>}
</label>
)}
<input
{...inputProps}
id={props.name}
value={props.value || ''}
aria-invalid={!!props.error}
aria-errormessage={`${props.name}-error`}
/>
{props.error && <div id={`${props.name}-error`}>{props.error}</div>}
</div>
);
}
Next steps
You can now build on this code and add CSS, for example. You can also follow the procedure to create other components such as Checkbox
, Slider
, Select
and FileInput
.
Tip: To make things easier and speed up development, you can build your components on top of the UI toolkit Kobalte. You can find a guide here.
Final code
Below is an overview of the entire code of the TextInput
component.
import { JSX, splitProps } from 'solid-js';
type TextInputProps = {
name: string;
type: 'text' | 'email' | 'tel' | 'password' | 'url' | 'date';
label?: string;
placeholder?: string;
value: string | undefined;
error: string;
required?: boolean;
ref: (element: HTMLInputElement) => void;
onInput: JSX.EventHandler<HTMLInputElement, InputEvent>;
onChange: JSX.EventHandler<HTMLInputElement, Event>;
onBlur: JSX.EventHandler<HTMLInputElement, FocusEvent>;
};
export function TextInput(props: TextInputProps) {
const [, inputProps] = splitProps(props, ['value', 'label', 'error']);
return (
<div>
{props.label && (
<label for={props.name}>
{props.label} {props.required && <span>*</span>}
</label>
)}
<input
{...inputProps}
id={props.name}
value={props.value || ''}
aria-invalid={!!props.error}
aria-errormessage={`${props.name}-error`}
/>
{props.error && <div id={`${props.name}-error`}>{props.error}</div>}
</div>
);
}