1

In a Svelte app, I have a form with simple validation.

The form

<form id="surveyForm" class="mt-4">
    <div class="form-group">
      <input type="text" class="form-control" placeholder="First name">
    </div>
    <div class="form-group">
      <input type="text" class="form-control" placeholder="Last name">
    </div>
    <button class="btn btn-full" on:click={Continue}>Continue</button>
</form>

The validation:

import {fly, fade} from 'svelte/transition';
let hasError = false;
let errMessage = "";
let isSuccessVisible = false;

function validateInput() {
    var surveyForm = document.getElementById('surveyForm'),
        inputFields = surveyForm.querySelectorAll('input[type=text]');

    hasError = false;

    inputFields.forEach(function(field) {
        field.classList.remove("is-invalid");
        var inputFieldVal = field.value;
        if (inputFieldVal.length == 0) {
            field.classList.add("is-invalid");
            hasError = true;
        }
    });
    errMessage = "All the fileds are mandatory";
}


function Continue(e) {
    e.preventDefault();
    validateInput();
    if (hasError == false) {
        isSuccessVisible = true;

        setTimeout(function() {
            isSuccessVisible = false;
        }, 3000);
    }
}

In order to give invalid form fields a red border, I use border: 1px solid #c00 !important:

.error-alert {
    border: 1px solid #c00 !important;
    color: #c00;
}

The class is-invalid is correctly added (and removed), yet, the default global.css styles overwrite my component-level styles, as you can see in this REPL.

Why does this happen? What is a reliable fix?


NOTE: The case described is a simplified one, in reality I might need other validations, like making sure the value is numeric. The solution I have to implement is not a very specific one.

2 Answers 2

5

In the REPL, you see the following warning:

Unused CSS selector (87:1)

That's because the .is-invalid selector isn't used anywhere in the markup — if a classname is unused, Svelte tells you about it and removes it. For Svelte to be able to 'see' it, it has to be in the markup — you can't add it programmatically with classList. (This is a feature, not a bug: programmatically manipulating the DOM will inevitably lead to your view getting out of sync with your state.)

In this case though, you don't need to add a class to the input element, or do the checking inside a function. You can just use the input's required attribute and :invalid CSS selector:

<input type="text" class="form-control" placeholder="First name" required>
input:invalid {
  border: 1px solid #c00;
}

The :invalid state will be applied before the user submits any data though, so you may want to add a class that indicates whether the user has tried to submit already:

<form id="surveyForm" class="mt-4" class:submitted>
  <div class="form-group">
    <input type="text" class="form-control" placeholder="First name" required>
  </div>

  <!-- ... -->
</form>
.submitted input:invalid {
  border: 1px solid #c00;
}

The easiest way to add that class is in a click event handler on the button.

Here's a demo.

Using the stuff you get for free in HTML comes with other benefits. For example, the form won't submit as long as there are validation errors (and validation isn't limited to 'required' — you can mandate specific patterns, or use e.g. type="email" to require a valid email address, etc), which means that inside your submit event handler, you already know that the inputs are valid.

Sign up to request clarification or add additional context in comments.

3 Comments

Thank you for this reminder to leverage HTML whenever possible, something I'm guilty of overlooking too often. I've taken the liberty to modify your REPL ever so slightly in order to bring back the global error/success alert: svelte.dev/repl/70bb412acad94c3ab4bc54e5bb3da312?version=3.24.0
Nice, but what if I need other validations, like making sure the value is numeric, for instance?
@RazvanZamfir, then you would give the input a type of number, or use a css selector in combination with not() to add extra styles in that case.
2
+50

You can use the technique Rich described for other validations by using HTML5 client-side form validation. More information can be found on the MDN

<form id="surveyForm" class="mt-4" class:submitted>
    <div class="form-group">
    <input type="text" class="form-control" placeholder="First name" required>
  </div>
    <div class="form-group">
    <input type="text" class="form-control" placeholder="Last name" required>
  </div>
    <div class="form-group">
    <input type="number" class="form-control" placeholder="Enter a number" required>
  </div>
    <div class="form-group">
    <input type="email" class="form-control" placeholder="Email" required>
  </div>
    <div class="form-group">
    <input type="date" class="form-control" placeholder="Date" required>
  </div>
  <button class="btn btn-full" on:click|preventDefault={Continue}>Continue</button>
</form>

REPL

If you don't want to use this - or it doesn't cover all your use cases for some reason - you can manipulate the classes using Svelte instead of accessing the DOM directly:

<div class="form-group">
    <input bind:value={second} type="text" class="form-control" class:is-invalid="{ submitted && second.length == 0 }" placeholder="Last name">
</div>

REPL

The reason Svelte removes your .is-invalid style is because Svelte doesn't use it. I wouldn't recommend it in this case but you can force Svelte to keep the style using :global():

:global(.is-invalid) {
    border: 1px solid #c00;
}

REPL

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.