Pablo Alejandro Berganza Campos
13 Dec 2021
•
6 min read
Arguably one of the most common problems front-end developers need to solve is form handling. Specially in modern web applications that require instant validation and other real-time interactions with the user. To provide the best user experience as possible, you’ll probably grab a third party form management library to help you.
In this post I am going to write about Felte, a form management library for Svelte I have been working on for the past year that aims to make the basics of form handling on the front-end as simple as possible, while still allowing for it to grow more complex as your requirements grow.
This is one of two blog posts I’m writing. This one is oriented towards Felte’s integration with Svelte. The other one is oriented towards Felte’s integration with Solid.
As mentioned above, Felte aims to make the basics of form reactivity as easy to handle as possible, while still allowing for more complex behaviours via configuration and extensibility. Its main features are:
name
attribute is necessary).reporter
packages.In its most basic form, Felte only requires a single function to be imported:
<script>
import { createForm } from 'felte'
const { form } = createForm({
onSubmit: async (values) => {
/* call to an api */
},
})
</script>
<form use:form>
<input type=text name=email>
<input type=password name=password>
<input type=submit value="Sign in">
</form>
We set up the form by calling createForm
with our submit
handler. This function returns, among other utilities, an action that can be used on your form element. Now Felte will track all inputs with a name
attribute. When submitting your form, the latest values in your inputs will be passed to your onSubmit
function as an object. For our previous example, the shape of values
will be:
{
email: '',
password: '',
}
As you type, Felte will keep track of your user’s input in a regular writable Svelte store. This store is returned by createForm
as data
, following the same shape as the values you’d receive on your onSubmit
function.
For example, this would log your user’s email to the console as they type it:
const { form, data } = createForm({ /* ... */ });
// We use a reactive statement to log everytime our data store changes.
// We access the value of our store by prefixing it with `$`.
$: console.log($data.email);
Of course, another common requirement of forms is validation. If we want our app to feel snappy to the user, we will want some client side validation. createForm
’s configuration object accepts a validate
function (which can be asynchronous). It will receive the current value of your data
store as it changes, and it expects you to return an object with the same shape as your data
store containing your validation messages if the form is not valid, or nothing if your form is valid. Felte will keep track of these validation messages on a writable store that is returned from createForm
as errors
:
const { form, errors } = createForm({
validate(values) {
const currentErrors = {};
if (!values.email) currentErrors.email = 'Must not be empty';
if (!values.password) currentErrors.password = 'Must not be empty';
return currentErrors;
},
});
$: console.log($errors);
More complex validation requirements might require third party validation libraries. Felte offers first party integrations with some popular validation libraries through its extensibility features. These integrations are offered as separate packages. I will write write more about this in the next section regarding extensibility, but you can read more about these packages in our official documentation.
Felte does not attempt to have the perfect solution on how to handle all scenarios regarding form management. This is why Felte offers an API to extend its functionality as your requirements grow more complex. You may have a preferred library you like to use, such as the really popular yup, or Vest (which was recently talked about during Svelte Summit). Or you might find it tedious to display your validation messages using if
statements. Modifying Felte’s behaviour to handle these scenarios can be done via the extend
option on createForm
’s configuration object. More about this can be read in the official documentation. To keep things simple for the purposes of this blog post, I am only going to write about the existing packages we maintain to handle some common use cases:
We are currently maintaining four packages to integrate Felte with some popular validation libraries: yup
, zod
, superstruct
and most recently vest
. Here I will use yup as an example, but you can read more about the rest here.
The package to use yup
is on npm under the name @felte/validator-yup
. You will need to install it alongside yup
:
npm install --save @felte/validator-yup yup
# Or, if you use yarn
yarn add @felte/validator-yup yup
This validator package exports a function called validator
which can be passed as-is to the extend
option of createForm
. Your validation schema can then be passed to the validateSchema
option:
import { validator } from '@felte/validator-yup';
import * as yup from 'yup';
const schema = yup.object({
email: yup.string().email().required(),
password: yup.string().required(),
});
const { form } = createForm({
// ...
extend: validator, // OR `extend: [validator],`
validateSchema: schema,
// ...
});
Displaying your validation messages can be done by directly accessing the errors
store returned by createForm
. Messages won’t be available on this store until the related field is interacted with.
<script>
const { form, errors } = createForm({ /* ... */ });
</script>
<form use:form>
<label for=email>Email:</label>
<input name=email id=email type=email>
# if $errors.email}
<span>{$errors.email}</span>
{/if}
<button>Submit</button>
</form>
But displaying the messages is not the end of the story in most cases. For example, you might want to add an aria-invalid
attribute to the related input. Or you simply might not like that specific syntax to handle your validation messages. Felte currently has four accompanying packages that offer different alternatives on how to display your validation messages:
For brevity, I am only going to write about the first package. But you can read more about the rest in the documentation.
Using a Svelte component to get your validation messages can be done with the package @felte/reporter-svelte
. You’ll need to add it to your project using your favourite package manager:
# npm
npm i -S @felte/reporter-svelte
# yarn
yarn add @felte/reporter-svelte
Then you’ll need to import both the svelteReporter
function to add to the extend
property, and the ValidationMessage
component which you will use to receive your validation messages:
<script>
import { svelteReporter, ValidationMessage } from '@felte/reporter-svelte';
import { createForm } from 'felte';
const { form } = createForm({
// ...
extend: svelteReporter,
// ...
},
})
</script>
<form use:form>
<input id="email" type="text" name="email">
<ValidationMessage for="email" let:messages={message}>
<!-- We assume a single string will be passed as a validation message -->
<!-- This can be an array of strings depending on your validation strategy -->
<span>{message}</span>
<!-- Shown when there's no validation messages -->
<span slot="placeholder">Please type a valid email.</span>
</ValidationMessage>
<input type="password" name="password">
<ValidationMessage for="password" let:messages={message}>
<!-- If not slot is used, you'll need to handle empty messages -->
<span>{message || ''}</span>
</ValidationMessage>
<input type="submit" value="Sign in">
</form>
You can check more about Felte in its official website with some functional examples. There’s also a more complex example showcasing its usage with Tippy.js and Yup available on CodeSandbox.
I hope this served as a good introduction to Felte, and that it is interesting enough for you to give it a try. Felte is currently in quite a useable state and I feel it’s flexible enough for most use cases. I am also open to help and suggestions so feel free to open an issue or make a pull request on GitHub.
Ground Floor, Verse Building, 18 Brunswick Place, London, N1 6DZ
108 E 16th Street, New York, NY 10003
Join over 111,000 others and get access to exclusive content, job opportunities and more!