Forms remain an integral part of how users interact with the web. When dealing with forms, we have to handle tracking user input, validating and displaying errors, and handling the form submission.
In this article, we will learn how input bindings work in Svelte, how to validate forms with Yup, and how svelte-forms-lib
makes managing forms easier. We will also build a sample form and validate it with these tools to demonstrate the many options you have when building and validating forms in Svelte.
We need a way to track and store the value of input fields as the user type. Svelte provides two directives to achieve that: on:input
and bind
.
on:input
This event listener is called whenever an input event takes place:
<script> let email = ""; const handleInput = (e) => { email = e.target.value; }; </script> <input type="email" name="email" on:input={handleInput} /> <p>{email}</p>
In the code above, we define a handleInput
and pass it to the email input field. Whenever the user types, the email
variable gets updated with the value of the field.
bind:value
The bind
directive is a cleaner way to work with tracking form values in Svelte:
<input type="email" name="email" bind:value={email} />
Instead of creating a handleInput
event and having to set event.target.value
for each input field in a given form, bind
handles that for us, and whenever we fill the input, the email
variable gets updated.
In this article, we will use the bind
directive to track and store form values, as it is an easier way to work.
Yup is a JavaScript object schema validator. Yup ensures that the data in an object is in the form and shape we want it to be:
import * as yup from 'yup'; let values = { email: "", password: "", confirmPassword: "", hobby: "", checkbox: false, }; const schema = yup.object().shape({ email: yup.string().required("Email is required") .email("Email is invalid"), password: yup.string().required("Password is required"), confirmPassword: yup.string().required("Please confirm your password") .oneOf([yup.ref("password"), null], "Passwords do not match"), hobby: yup.string().required("Hobby is required"), checkbox: yup.bool().required("Checkbox must be accepted") .oneOf([true], "Checkbox must be accepted"), }); const validationResult = schema .validate(values, { abortEarly: false }) .then(() => { alert(JSON.stringify(values, null, 2)); }) .catch((err) => { console.log(err.errors); }); //console.log message //[ "Email is invalid", "Passwords do not match", "Hobby is required", "Che//ckbox must be accepted" ]
In schema
, we define how we want the data of our form values to look. This ensures that the data that gets sent to the server is valid.
We validate objects in Yup using its validate
method. We can call this method on any schema we define.
Now that we understand how form binding works in Svelte and how Yup validates object values, let us set up a sample profile form and validate it:
<script> import schema from './schema'; let values = { //store form data that will then be validated }; const handleSubmit = () => { //validate form and submit data }; </script> <div class="container"> <h1>Profile Form</h1> <form on:submit|preventDefault={handleSubmit}> <div> <input type="text" name="email" bind:value={values.email} placeholder="Email" /> </div> <div> <input type="password" name="password" bind:value={values.password} placeholder="Password" /> </div> <div> <input type="password" name="confirmPassword" bind:value={values.confirmPassword} placeholder="Confirm password" /> </div> <div> <select name="hobby" bind:value={values.hobby}> <option value="">Select a hobby</option> <option value="Eating">Eating</option> <option value="Reading">Reading</option> <option value="Sleeping">Sleeping</option> </select> </div> <div> <label for="checkbox">Check this box</label> <input name="checkbox" type="checkbox" bind:checked={values.checkbox} /> </div> </form> </div>
We start by setting up a simple profile form to capture a user’s data. We bind the form fields to a values
object. This object is where we will store the data from the form.
Now that we have created the profile form, we need to validate it.
Unlike what we did when we logged the errors to the console, we want to display them for the user to see:
<script> let errors = {}; const handleSubmit = async () => { try { await schema.validate(values, { abortEarly: false }); alert(JSON.stringify(values, null, 2)); errors = {}; } catch (err) { errors = err.inner.reduce((acc, err) => { return { ...acc, [err.path]: err.message }; }, {}); } }; </script>
In this code block, we create an errors
object where we will store the errors we get back from the validate
call. Then, we create an async function, handleSubmit
. Here, we will handle the form validation and submission.
We pass the data we want to validate to this method. In this case, we will be validating the values
received from a form.
Validate
can take a second parameter, an options object. Validation returns on the first error by default. To get all the errors returned, we must set abortEarly
to false
.
If there are no errors, we display the form values. If there are, we display the errors. However, before we can display the errors, we must access them:
errors = err.inner.reduce((acc, err) => { return { ...acc, [err.path]: err.message }; }, {});
To access the errors, we loop over Yup’s validation error.inner
array and return a new object consisting of fields and their error messages. Then we update the errors
object with the errors for each corresponding input field.
Now that we have the errors
object that holds the error for each input field, we need to display them:
<div> <input type="email" /> {#if errors.email} <span class="error">{errors.email}</span> {/if} </div> <div> <input type="password" /> {#if errors.password} <span class="error">{errors.password}</span> {/if} </div> <div> <input type="password" /> {#if errors.confirmPassword} <span class="error">{errors.confirmPassword}</span> {/if} </div> <div> <select name="hobby" bind:value={values.hobby}> <option value="">Select a hobby</option> <option value="Eating">Eating</option> <option value="Reading">Reading</option> <option value="Sleeping">Sleeping</option> </select> {#if errors.hobby} <span class="error">{errors.hobby}</span> {/if} </div> <div> <input name="checkbox" type="checkbox" bind:checked={values.checkbox} /> {#if errors.checkbox} <span class="error">{errors.checkbox}</span> {/if} </div>
We set up an if
block to handle displaying the error. If an error exists for a particular field, we display the error of that field. This CodeSandbox link holds the code for this section.
svelte-forms-lib
Svelte forms lib is a Formik-inspired library for building forms easily in a Svelte project.
You can install svelte-forms-lib
with the following:
npm i svelte-forms-lib
First, we import the createForm
function from svelte-forms-lib
:
import { createForm } from "svelte-forms-lib";
This function is the core part of integrating svelte-forms-lib
into a form.
CreateForm
gives us access to useful form helps like handleChange
and handleSubmit
, among others. We will need these helper functions to set up the form:
<script> import { createForm } from "svelte-forms-lib"; const { form, handleChange, handleSubmit } = createForm({ initialValues: { email: "", password: "", confirmPassword: "", hobby: "", checkbox: "", }, onSubmit: (values) => { alert(JSON.stringify(values)); }, }); </script> <div class="container"> <h1>Registration Form</h1> <form on:submit|preventDefault={handleSubmit}> <div> <input type="text" name="email" bind:value={$form.email} placeholder="Email" on:change={handleChange} /> </div> <div> <input type="password" name="password" bind:value={$form.password} placeholder="Password" on:change={handleChange} /> </div> <div> <input type="password" name="confirmPassword" bind:value={$form.confirmPassword} placeholder="Confirm password" on:change={handleChange} /> </div> <div> <select name="hobby" bind:value={$form.hobby} on:blur={handleChange}> <option value="">Select a hobby</option> <option value="Eating">Eating</option> <option value="Reading">Reading</option> <option value="Sleeping">Sleeping</option> </select> </div> <div> <label for="checkbox">Check this box</label> <input name="checkbox" type="checkbox" bind:checked={$form.checkbox} on:change={handleChange} /> </div> <div> <button type="submit">Register</button> </div> </form> </div>
Aside from helper functions, svelte-forms-lib
exposes observable values that give us information on the current state of the form. In this article, we will focus on working with the form
and errors
observables. However, you can check out the full list of available observables here.
We pass a config object as an argument to createForm
. Here, we define the initialValues
of the form and an onSubmit
handler that will handle the form submission.
Having configured createForm
, we need to hook the profile form up to svelte-forms-lib
, so it can track the form values and handle the submission.
To do that, we pass the handleSubmit
helper to the form
element. We also pass handleChange
to the input fields and bind
their values to the form
observable.
svelte-forms-lib
Now that we know how to integrate svelte-forms-lib
into a form, we need to handle the form validation:
<script> import { createForm } from "svelte-forms-lib"; const { form, errors, handleChange, handleSubmit } = createForm({ initialValues: {}, validate: (values) => { let errors = {}; if (!values.email) { errors.email = "Email is Required"; } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(values.email)) { errors.email = "Invalid emaill address"; } if (!values.password) { errors["password"] = "password is required"; } if (!values.confirmPassword) { errors["confirmPassword"] = "confirm password is required"; } else if (values.confirmPassword !== values.password) { errors["confirmPassword"] = "password does not match"; } if (!values.hobby) { errors["hobby"] = "hobby is required"; } if (!values.checkbox) { errors.checkbox = "You must accept our terms"; } return errors; }, onSubmit: (values) => { alert(JSON.stringify(values)); }, }); </script> <div class="container"> <h1>Registration Form</h1> <form on:submit|preventDefault={handleSubmit}> <div> <input type="text" name="email" bind:value={$form.email} placeholder="Email" on:change={handleChange} /> {#if $errors.email} <span class="error">{$errors.email}</span> {/if} </div> <div> <input type="password" name="password" bind:value={$form.password} placeholder="Password" on:change={handleChange} /> {#if $errors.password} <span class="error">{$errors.password}</span> {/if} </div> <div> <input type="password" name="confirmPassword" bind:value={$form.confirmPassword} placeholder="Confirm password" on:change={handleChange} /> {#if $errors.confirmPassword} <span class="error">{$errors.confirmPassword}</span> {/if} </div> <div> <select name="hobby" bind:value={$form.hobby} on:blur={handleChange}> <option value="">Select a hobby</option> <option value="Eating">Eating</option> <option value="Reading">Reading</option> <option value="Sleeping">Sleeping</option> </select> {#if $errors.hobby} <span class="error">{$errors.hobby}</span> {/if} </div> <div> <label for="checkbox">Check this box</label> <input name="checkbox" type="checkbox" bind:checked={$form.checkbox} on:change={handleChange} /> {#if $errors.checkbox} <span class="error">{$errors.checkbox}</span> {/if} </div> <div> <button type="submit">Register</button> </div> </form> </div>
Aside from configuring createForm
with an initialValues
object and an onSubmit
function, we can also add a validate
callback to handle the form validation.
Here, we check the state of each input field and based on that state, update the errors
object. Whenever there are errors in any input field, we display them in an if
block.
svelte-forms-lib
While we can create a custom validation for our form, we also have the option of passing that responsibility to Yup.
We will work with the same schema
validation object we just created:
<script> import schema from "./schema"; import { createForm } from "svelte-forms-lib"; const { form, errors, handleChange, handleSubmit } = createForm({ initialValues: { //initial values here }, validationSchema: schema, onSubmit: (values) => { alert(JSON.stringify(values)); }, }); </script> //profile form below
Svelte-forms-lib
provides support with Yup validation through a validationSchema
prop which takes in a schema object. We pass in the schema we defined. You can find the CodeSandbox link to this section here.
svelte-forms-lib
So far, we have had to pass in handleSubmit
to the form, bind each field to their respective value, and pass in handleChange
to each field.
While this gets the job done, svelte-forms-lib
provides a better and less repetitive way to work with forms: custom components.
These components will reduce the boilerplate and make the form code very concise:
<script> import { Form, Field, ErrorMessage, Select } from "svelte-forms-lib"; import schema from "./schema"; const formProps = { initialValues: {}, validationSchema: schema, onSubmit: (values) => { alert(JSON.stringify(values)); }, }; </script> <div class="container"> <h1>Registration Form</h1> <Form {...formProps}> <div> <Field type="email" name="email" placeholder="Email" /> <ErrorMessage name="email" /> </div> <div> <Field type="password" name="password" placeholder="Password" /> <ErrorMessage name="password" /> </div> <div> <Field type="password" name="confirmPassword" placeholder="Password" /> <ErrorMessage name="confirmPassword" /> </div> <div> <Select name="hobby"> <option value="">Select a hobby</option> <option value="Eating">Eating</option> <option value="Reading">Reading</option> <option value="Sleeping">Sleeping</option> </Select> <ErrorMessage name="hobby" /> </div> <div> <label for="checkbox">Check this box</label> <Field type="checkbox" name="checkbox" /> <ErrorMessage name="hobby" /> </div> <div> <button type="submit">Register</button> </div> </Form> </div> //profile form below
Here, we make use of the <Form/>
, <Field/>
, <Select/>
, and <ErrorMessage/>
components.
We pass in initialValues
, onSubmit
, and validationSchema
to <Form/>
through the formProps
variable we define. The name
and type
are needed for <Field/>
to work properly and render the appropriate input type.
For <ErrorMessage/>
, we pass the in the name of the input field we want to track, and if there is an error for that input, <ErrorMessage/>
will display the error. We no longer need to conditionally render the error ourselves.
You can find the CodeSandbox link to this section here.
Creating forms in Svelte can be both simple and very complicated. In this article, we have learned how to track and store input values in Svelte, handle validation with Yup, how svelte-forms-lib
works, and the different ways we can integrate this awesome library into our forms.
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowBuild scalable admin dashboards with Filament and Laravel using Form Builder, Notifications, and Actions for clean, interactive panels.
Break down the parts of a URL and explore APIs for working with them in JavaScript, parsing them, building query strings, checking their validity, etc.
In this guide, explore lazy loading and error loading as two techniques for fetching data in React apps.
Deno is a popular JavaScript runtime, and it recently launched version 2.0 with several new features, bug fixes, and improvements […]