Handle submission
Now your first form is almost ready. There is only one little thing missing and that is the data processing when the form is submitted.
Form actions
To make your form work even if JavaScript is disabled in your user's browser, we recommend using actions. Optionally, you can also perform the data processing on the client with onSubmit$
or use both in parallel.
You can find more about progressively enhanced forms in this guide.
Create an action
To create an action, you don't use Qwik's routeAction$
or globalAction$
, but instead our formAction$
which builds on globalAction$
and adds additional functionality. The code of an action is only executed server-side. This has the advantage that the validation cannot be manipulated and you can communicate directly with backend services like a database.
You can then pass your action to the useForm
hook, similar to how it works with the loader. More about this in the following code example.
Code example
import { $, component$, QRL } from '@builder.io/qwik';
import { routeLoader$ } from '@builder.io/qwik-city';
import {
type InitialValues,
formAction$,
valiForm$,
} from '@modular-forms/qwik';
import * as v from 'valibot';
const LoginSchema = v.object({
email: v.pipe(
v.string(),
v.nonEmpty('Please enter your email.'),
v.email('The email address is badly formatted.')
),
password: v.pipe(
v.string(),
v.nonEmpty('Please enter your password.'),
v.minLength(8, 'You password must have 8 characters or more.')
),
});
type LoginForm = v.InferInput<typeof LoginSchema>;
export const useFormLoader = routeLoader$<InitialValues<LoginForm>>(() => ({
email: '',
password: '',
}));
export const useFormAction = formAction$<LoginForm>((values) => {
// Runs on server
}, valiForm$(LoginSchema));
export default component$(() => {
const [loginForm, { Form, Field }] = useForm<LoginForm>({
loader: useFormLoader(),
action: useFormAction(),
validate: valiForm$(LoginSchema),
});
const handleSubmit: QRL<SubmitHandler<LoginForm>> = $((values, event) => {
// Runs on client
});
return <Form onSubmit$={handleSubmit}>…</Form>;
});
Prevent default
When the form is submitted, event.preventDefault()
is executed by default to prevent the window from reloading so that the values can also be processed directly in the browser and the state of the form is preserved.
Loading animation
While the form is being submitted, you can use loginForm.submitting
to display a loading animation and disable the submit button.
Only dirty values
Using the shouldDirty
property of the Form
component, you have the option to have the form return only modified values. This reduces network traffic, for example if you only want to update values that have changed in your database.
Return custom data
If you process form values with actions, you may wonder how to return individual data to your component. For example, the ID of a user after login.
To do this, first add a second generic to formAction$
and useForm
that defines your individual data and makes it type safe. Then you can return this data in formAction$
via the data
key to access it afterwards with loginForm.response.data
in your component.
type ResponseData = {
userId: string;
};
export const useFormAction = formAction$<LoginForm, ResponseData>(
async (values) => {
const userId = await loginUser(values);
return {
status: 'success',
message: 'You are now logged in.',
data: { userId },
};
},
valiForm$(LoginSchema)
);