Nuno Alexandre

Svelte is Great, how i build forms on it

#svelte
#frontend

About a week ago I went out to the wild discovering another frontend framework to try and learn, and I end up founding Svelte, actually i already have tried but not that in dept. Have been using it to build a side project, made me learn a lot about it. This blog post I will share with you a bit about and how I am using svelte to create forms.

svelte-logo

What is svelte ?

Svelte is a framework/compiler with an abstraction logic that updates the DOM, making it fast and simple. Let me show you an example, of how simple it seams:

<script>
  let name = 'Nuno';
</script>

<p>My name is {name}</p>

(App.svelte)

What you see in this example seams like HTML right ? That is true the svelte syntax is very familiar to HTML but with some cool reactivity effects.

In this example, we can see a script tag that holds the name variable and on the <p> block w are referencing the name, so the result will be:

My name is Nuno

So as you can see svelte files holds javascript and html at the same time, like VUE, but with a different syntax.

Another cool thing is that you can even declare styling on the svelte file, like so:

<style>
  p { color: red; }
</style>

Basic stuff right ?

There is a lot of the syntax to learn but if you want to learn that is better to check out the svelte documentation, here. Although in the following sections, I will show you how to i use svelte with forms, in this way you will learn more in dept about svelte and at the same time how to create reusable form and components.

Lets create a Form component

Creation page

<script>
  import { saving, save, errors, success } from "../stores/product.js";
  import Form from './ProductForm.svelte';
  
  let form = {
    name: '',
    price: 0
  };

  const handleSubmit = () => {
    save(form);
  };
</script>
  
<Form bind:form={form} on:submit={handleSubmit} errors={$errors} loading={$saving} />

(create-product.svelte)

This code holds the product creation page, and as you can see we have a script tag importing some values and methods from the product store (i will get on that in a minute), as well a Product Form component (that can be reused on the creation of the product and when we will edit it).

In the HTML part, you can see that we are passing the form object into the Form component with bind:form={form}, this will bind the form object, meaning that when the form object updates inside the Form component, it will be reflected in the creation page as well.

The on:submit={handleSubmit} is an event listener, the will trigger whenever the Form fires submit event.

The errors={$errors} loading={$saving} props are values that we receive from the store and are passed to our Form component.

Form component

<script>
  import Field from "../components/Field.svelte";

  import { createEventDispatcher } from 'svelte';
  const dispatch = createEventDispatcher();
  
  export let form; // prop
  export let errors; // prop
  export let loading; // prop
</script>
<form on:submit|preventDefault={() => dispatch('submit')}>
  <Field
    name="name"
    label="Name"
    bind:value={form.name}
    errors={errors} />
  <Field 
    name="price" 
    label="Price" 
    bind:value={form.price} 
    errors={errors} />
    <div class="mt-4 flex justify-between">
      <button
        type="submit"
        class={loading ? 'button loading' : 'button success'}
        disabled={loading}>
        Save
      </button>
    </div>
</form>

(ProductForm.svelte)

The Product Form looks fairly simple, in the script tag, we are importing the Field component, to use it as our inputs but already abstracted with the markup and necessary logic to work. Then we are importing createEventDispatcher that will provide us with a dispatch letting us communicate with a parent component. To receive the props from a parent component, we are exporting some variables (form, errors, loading), using export is required, by using it we are tailing the parent and svelte that we want to receive that as props.

In the HTML part, we are defining a form with an on:submit event and passing a preventDefault modifier (allows us to prevent the default browser behavior when the event fires), as a value we are passing a function that will dispatch submit if you remember of the Product creation form we are using on:submit={handleSubmit} to listen to the submit event from the child.

We are also declaring some Field components that will receive the value and will bind it.

To finish the Form component we have a submit button, and the thing is more complicated to understand is the class props, so when the user [prop]={ ...expression }, the value of the prop will be always the result of the expression.

Field Component

<script>
  export let value;
  export let label;
  export let name;
  export let errors = {};

  $: hasErrors = errors && errors[name];
  $: classes = hasErrors ? "input with-error" : "input";
  const handleInput = e => {
    value = e.target.value;
  };
</script>

<div class="mb-4">
  <input
    type="text"
    placeholder={label}
    {name}
    class={classes}
    bind:value
    on:input={handleInput} />
    {#if hasErrors}
      <p class="text-red-500 text-xs italic mt-3">{errors[name].message}</p>
    {/if}
</div>

In the Field component we have some props as expected, two computed variables, when we have $: [varName] = {expression} will create a variable that will listen for changes (is like a computed method in vue) on its expression. Them we are creating a method to handle the input change, this means that on:input={handleInput} will listen to any change on the input and when it happens it will trigger the handleInput method, that therefore will update the value, since the value is being bound the parent value will be updated as well.

For the first time we can see a if statement, like it there are others (see here)

The Store

import { writable } from 'svelte/store';
import * as api from 'api.js';
import { goto } from '@sapper/app';

let errors = writable(null);
let saving = writable(false);
let success = writable(null);

const save = async (form) => {
  saving.set(true);
  errors.set(null);

  const response = await api.post('orders', form);
  
  if (response.status >= 400) {
    saving.set(false);
    errors.set(response.data.errors);
    return;
  }

  success.set(response.data.message);
  order.set(response.data.data);

  setTimeout(() => {
    saving.set(false);
    goto('/dashboard/orders/' + response.data.data._id);
  }, 2000);
};

export default { errors, saving, success, save}

(product.js)

To abstract logic from components we often need to create a store, this can also be useful when we need to share information across multiple components. Svelte have a couple of functions that helps us create a store like writable, readable, derived. When accessing store variables on a svelte component we normally use $ prefix, which will set the appropriate store subscription with that component.

The writable returns a object that has additional set and update methods, rede more here.

Our store has multiple writables (stores) like errors, success, saving that will be used later on our components with $ prefix, to be subscribed. We are also declaring a save method that is responsible to send our product to the API and set the errors or success accordingly.

Conclusion

There is a lot more about Svelte than what I have shown you, although I think this will be a good start. Of course, this could not the best way if you think that, feel free to reach out to me.

easy