loading...

Form Validation with Svelte, Rails and Inertia

buhrmi profile image Stefan Buhrmester ・2 min read

Why won't users just input data that is VALID? We will never know. Handling invalid data is always annoying. However, if you build your web app with Rails, Svelte and Inertia, you can turn form validation into a real delight.

Rails always had its own way of dealing with invalid forms: Inside of the create action, a standard Rails app would simply re-render the form (with render :action => "new") and let the user submit it again.

However, it's 2020, we have things like Svelte, and we don't want to re-render the entire page. Instead, we just want to somehow send the errors to the frontend and let Svelte do the work of updating our page. So we are now going to change the way how Rails deals with validation errors. Instead of re-rendering the new action, we are going to save all our errors into our flash object. Then we redirect the user back to where he came from to try again:

# controllers/application_controller.rb
class ApplicationController < ActionController::Base

  # When we encounter an invalid record, save the errors in the flash and send the user back 
  rescue_from ActiveRecord::RecordInvalid do |e|
    flash[:errors] = e.record.errors
    redirect_back fallback_location: root_url
  end
end

But how can we make the flash object available to Svelte? For this, we can use the amazing inertia_share method made available by the inertia_svelte gem. This will make the flash available to page components as a svelte prop called flash.

# controllers/application_controller.rb
class ApplicationController < ActionController::Base

  ...

  # Share the entire flash with our frontend
  inertia_share do 
    {
      flash: flash.to_hash
    }
  end
end

Now we can call the create! method inside of our controller, knowing that all validation errors are being caught and shared as props with our page component.

# controllers/post_controller.rb
class PostController < ApplicationController
  def create
    post = Post.create! post_params
    redirect_to post
  end

  def show
    post = Post.find(params[:id])
    render inertia: 'posts/show', props: {
      post: post
    }
  end

  def new
    render inertia: 'posts/new'
  end
end

To actually display the errors, we make our flash object available as a component prop in our page component:

<script>
// pages/posts/new.svelte
import { Inertia } from '@inertiajs/inertia'
let post = {}
export let flash
</script>

<form on:submit|preventDefault={() => Inertia.post('/posts', {post})}>
  {#if flash.errors && flash.errors.title}
    <div class="error">{flash.errors.title}</div>
  {/if}
  <input bind:value={post.title}>
  <textarea bind:value={post.body}>
  <button>Create post</button>
</form>

And that's already it. Now, whever a validation on the Post object fails, the errors are being forwarded all the way to Svelte, which handles the DOM updates for us.

Really good stuff.

Posted on Jul 3 '19 by:

buhrmi profile

Stefan Buhrmester

@buhrmi

Searching for the dots to connect.

Discussion

markdown guide