Core Concepts
Creating Fields
When creating a field with @zluvo/forms, start by determining what it will record—whether it's a password, email, number, or another type. The library supports various fields, including:
- Text
- Telephone
- Checkbox
- Password
- URL
- Number
- Textarea
To use a specific field, simply import field. If you're using TypeScript, your IDE should guide you on available fields via field. Note that every field created using field requires the label and placeholder attributes. This opinionated approach aligns with good practices for meeting accessibility requirements:
import { form, field } from '@zluvo/forms';
export const helloForm = form.create({
name: field.text({
label: 'Name',
placeholder: 'Your Name'
})
});
Embeding in UI
@zluvo/forms remains flexible regarding the UI library or framework you choose, whether it's Next.js, React, Svelte, Angular, or any other JavaScript framework. This adaptability ensures @zluvo/forms stays future-proof against changes in current or new UI frameworks. Use the fields property to embed all the fields your form contains. Here's an example using Next.js:
import { registerForm } from '../../forms/registerForm';
export default function RegisterPage() {
async function handleSubmit(formData: FormData) {
// Handle form submission...
}
return (
<>
<h1>{registerForm.name}</h1>
<form action={handleSubmit}>
{registerForm.fields.map((field) => (
<>
<label>{field.label}</label>
<input {...field} />
</>
))}
<button type="submit">Submit</button>
</form>
</>
);
}
Validation
Each field created via field automatically validates against its expected format. For instance, the email field automatically checks if the input is a valid email. You can also apply custom validation using Zod through the validation attribute. In this example, the username field is limited to values between 1 and 10 characters:
import { form, field } from '@zluvo/forms';
import { z } from "zod"
export const helloForm = form.create({
username: field.text({
label: "Username"
placeholder: "Your Username",
validation: z.string().min(1).max(10)
}),
email: field.email({
lable: "Email",
placeholder: "Your Email"
})
})
Now, inputs outside the 1-10 character range will be prevented. To check if a form submission was valid, use the .validate() method, which executes validations in parallel:
import helloForm from '../../forms/helloForm';
export default function HelloPage() {
async function handleSubmit(formData: FormData) {
'use server'
const result = helloForm.validate(formData);
if (result.success) {
// result.data is appropriately typed
console.log(`Your username is ${result.data.username}`);
} else {
console.log("Submission is not valid");
}
}
return <form action={handleSubmit}>...</form>
}
Pipelines
After submitting and validating a form, the next step is often acting upon that data. With @zluvo/forms, you can use the pipeline API to add backend logic that acts on submission data. Using the registerForm example with drizzle and resend:
import { registerForm } from '@zluvo/forms';
import db from './dizzle';
import { Resend } from 'resend';
// add user to database using drizzle
registerForm.pipeline.add((data) => {
await db.insert(users).values(data);
});
// send email using resend
registerForm.pipeline.add((data) => {
const resend = new Resend('re_123456789');
await resend.emails.send({
from: 'hello@example.com',
to: data.email,
subject: 'Welcome!',
html: `Welcome ${data.username}`
});
});
After checking a submission is valid, run the pipeline. For additional performance, it executes in parallel:
export default function RegisterPage() {
async function handleSubmit(formData: FormData) {
const result = registerForm.validate();
if (result.success) {
// run all the logic in the pipeline!
await registerForm.pipeline.run()
}
}
...
}