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 { type QRL } from '@builder.io/qwik';
type TextInputProps = {
name: string;
type: 'text' | 'email' | 'tel' | 'password' | 'url' | 'date';
label?: string;
placeholder?: string;
value: string | undefined;
error: string;
required?: boolean;
ref: QRL<(element: HTMLInputElement) => void>;
onInput$: (event: Event, element: HTMLInputElement) => void;
onChange$: (event: Event, element: HTMLInputElement) => void;
onBlur$: (event: Event, element: HTMLInputElement) => void;
};
Component function
In the next step, add the component$
function to the file and destructure the properties of the HTML <input />
element from the rest.
import { component$ } from '@builder.io/qwik';
type TextInputProps = { … };
export const TextInput = component$(
({ label, error, ...props }: TextInputProps) => {
const { name, required } = props;
}
);
JSX code
After that, next you can add the JSX code to the return statement.
import { component$ } from '@builder.io/qwik';
type TextInputProps = { … };
export const TextInput = component$(
({ label, error, ...props }: TextInputProps) => {
const { name, required } = props;
return (
<div>
{label && (
<label for={name}>
{label} {required && <span>*</span>}
</label>
)}
<input
{...props}
id={name}
aria-invalid={!!error}
aria-errormessage={`${name}-error`}
/>
{error && <div id={`${name}-error`}>{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
.
Final code
Below is an overview of the entire code of the TextInput
component.
import { component$, type QRL } from '@builder.io/qwik';
type TextInputProps = {
name: string;
type: 'text' | 'email' | 'tel' | 'password' | 'url' | 'date';
label?: string;
placeholder?: string;
value: string | undefined;
error: string;
required?: boolean;
ref: QRL<(element: HTMLInputElement) => void>;
onInput$: (event: Event, element: HTMLInputElement) => void;
onChange$: (event: Event, element: HTMLInputElement) => void;
onBlur$: (event: Event, element: HTMLInputElement) => void;
};
export const TextInput = component$(
({ label, error, ...props }: TextInputProps) => {
const { name, required } = props;
return (
<div>
{label && (
<label for={name}>
{label} {required && <span>*</span>}
</label>
)}
<input
{...props}
id={name}
aria-invalid={!!error}
aria-errormessage={`${name}-error`}
/>
{error && <div id={`${name}-error`}>{error}</div>}
</div>
);
}
);