DEV Community

tengxgfyrz67s
tengxgfyrz67s

Posted on

Form-Validation-Patterns

Form Validation Patterns in euv

Project Code:https://github.com/euv-dev/euv

Form validation is a critical aspect of any web application. It ensures that user input meets the expected criteria before being processed or submitted to a server. In euv, form validation is built on top of the framework's reactive signal system and event handling, giving you a powerful and flexible way to validate user input in real time.

This article explores various form validation patterns you can use in your euv applications, from simple field validation to more advanced reactive validation strategies.

Basic Form Setup in euv

Before diving into validation, let's establish the foundation of a form in euv. Forms typically consist of input fields, labels, and a submit button. The euv framework uses the html! macro to declaratively define form elements, and the Signal type to manage their state reactively.

Here is a basic form with a username field:

let username: Signal<String> = use_signal(|| "".to_string());
html! {
    input {
        r#type: "text"
        placeholder: "Enter username"
        value: username
        oninput: move |event: Event| {
            if let Some(target) = event.target()
                && let Ok(input) = target.clone().dyn_into::<HtmlInputElement>() {
                    username.updater.set(input.value());
                }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

In this example, the username signal holds the current value of the input field. The oninput event handler captures user input and updates the signal accordingly. This pattern — binding a signal to an input's value and updating it via events — is the fundamental building block for all form validation in euv.

Simple Field Validation

The simplest form of validation checks whether individual fields meet specific criteria. You can implement this by adding validation logic to your submit button's onclick handler. When the user clicks the submit button, the validation function runs and collects any errors.

Consider a form with username and email fields. We want to ensure both fields are not empty before submission:

let errors: Signal<String> = use_signal(|| "".to_string());
button {
    onclick: move |_event: Event| {
        let mut validation_errors: Vec<String> = Vec::new();
        if username.get().trim().is_empty() {
            validation_errors.push("Username is required".to_string());
        }
        if email.get().trim().is_empty() {
            validation_errors.push("Email is required".to_string());
        }
        if validation_errors.is_empty() {
            errors.set("".to_string());
            submitted.set(format!("Submitted: {}", username.get()));
        } else {
            errors.set(validation_errors.join("; "));
        }
    }
    "Submit"
}
Enter fullscreen mode Exit fullscreen mode

In this pattern, we maintain an errors signal that stores a concatenated string of validation error messages. The onclick handler on the submit button performs the following steps:

  1. Creates a Vec<String> to accumulate validation errors.
  2. Checks each field against its validation rule. If a field fails, a descriptive error message is added to the vector.
  3. If no errors were found, the errors signal is cleared and the form data is submitted.
  4. If errors exist, they are joined into a single string and displayed to the user.

This approach is straightforward and works well for small forms with a few fields. However, as your form grows, you may want a more structured approach.

Using Computed Signals for Reactive Validation

One of the most powerful features of euv is its reactive signal system. You can leverage computed! and watch! macros to create validation logic that runs automatically whenever the underlying state changes. This enables real-time validation feedback — the user sees errors as they type, not just when they click submit.

For example, you can create a computed signal that tracks whether the form is valid:

let is_valid: Signal<bool> = computed!(username.get().trim().is_empty() == false
    && email.get().trim().is_empty() == false);
Enter fullscreen mode Exit fullscreen mode

You can then use this computed signal to conditionally render error messages or disable the submit button:

html! {
    button {
        disabled: !is_valid
        onclick: move |_event: Event| {
            submitted.set(format!("Submitted: {}", username.get()));
        }
        "Submit"
    }
}
Enter fullscreen mode Exit fullscreen mode

This reactive approach provides a much better user experience because validation feedback is immediate. The user doesn't have to submit the form to discover there are errors.

The watch! Macro for Side-Effect Validation

The watch! macro is another tool in euv's reactive arsenal. It watches a signal for changes and runs a callback whenever the signal's value changes. This is useful for validation scenarios where you need to perform side effects, such as logging validation results or triggering additional checks.

For instance, you might want to validate a field whenever its value changes:

watch!(username, |val: String| {
    if val.trim().is_empty() {
        error.set("Username is required".to_string());
    } else {
        error.set("".to_string());
    }
});
Enter fullscreen mode Exit fullscreen mode

This watcher will fire every time the username signal changes, providing real-time validation feedback.

Displaying Validation Errors in the UI

Once you have validation logic in place, you need a way to display errors to the user. In euv, you can use conditional rendering with the html! macro to show or hide error messages based on the validation state:

html! {
    div {
        if errors.get().is_empty() {
            span { class: "success-message", "All fields are valid!" }
        } else {
            span { class: "error-message", errors }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

You can also use CSS classes to style error states. The class! macro in euv makes it easy to apply conditional styling:

html! {
    input {
        r#type: "text"
        placeholder: "Enter username"
        class: if username.get().trim().is_empty() { "input-error" } else { "input-valid" }
        value: username
        oninput: move |event: Event| {
            if let Some(target) = event.target()
                && let Ok(input) = target.clone().dyn_into::<HtmlInputElement>() {
                    username.updater.set(input.value());
                }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

This applies different CSS classes depending on whether the field is empty or has content, giving the user visual feedback about the validation state.

Multi-Field Cross Validation

Sometimes validation rules involve multiple fields. For example, a "confirm password" field must match the "password" field. In euv, you can handle this by using computed signals that depend on multiple values:

let passwords_match: Signal<bool> = computed!(
    password.get() == confirm_password.get()
);
Enter fullscreen mode Exit fullscreen mode

You can then use this signal in your submit handler to check cross-field validation:

button {
    onclick: move |_event: Event| {
        let mut validation_errors: Vec<String> = Vec::new();
        if username.get().trim().is_empty() {
            validation_errors.push("Username is required".to_string());
        }
        if email.get().trim().is_empty() {
            validation_errors.push("Email is required".to_string());
        }
        if password.get() != confirm_password.get() {
            validation_errors.push("Passwords do not match".to_string());
        }
        if validation_errors.is_empty() {
            errors.set("".to_string());
            submitted.set(format!("Submitted: {}", username.get()));
        } else {
            errors.set(validation_errors.join("; "));
        }
    }
    "Submit"
}
Enter fullscreen mode Exit fullscreen mode

Disabling the Submit Button

A common UX pattern is to disable the submit button until the form is valid. In euv, you can bind the disabled attribute of a button to a computed signal:

let is_valid: Signal<bool> = computed!(username.get().trim().is_empty() == false
    && email.get().trim().is_empty() == false);

html! {
    button {
        disabled: !is_valid
        onclick: move |_event: Event| {
            submitted.set(format!("Submitted: {}", username.get()));
        }
        "Submit"
    }
}
Enter fullscreen mode Exit fullscreen mode

This prevents the user from submitting an invalid form, reducing frustration and unnecessary server requests.

Validation with Component Props

When building reusable form components, you can pass validation rules as component props. The #[component] macro and try_get_props() method make this straightforward:

#[derive(Clone, Default)]
struct MyCardProps {
    title: &'static str,
    onclick: Option<Rc<dyn Fn(Event)>>,
    disabled: Signal<bool>
}

#[component]
pub fn my_card(node: VirtualNode<MyCardProps>) -> VirtualNode {
    let MyCardProps { title, .. } = node.try_get_props().unwrap_or_default();
    let children: VirtualNode = node.try_get_child_node();
    html! {
        div {
            h3 { title }
            children
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

While MyCardProps is a general-purpose component example, you can create specialized form components with validation-specific props, such as validation functions or error messages, following the same pattern.

Form Submission Patterns

When the form passes validation, you typically need to submit the data to a server. In euv, you can handle form submission in the onclick handler of the submit button:

button {
    onclick: move |_event: Event| {
        let mut validation_errors: Vec<String> = Vec::new();
        if username.get().trim().is_empty() {
            validation_errors.push("Username is required".to_string());
        }
        if email.get().trim().is_empty() {
            validation_errors.push("Email is required".to_string());
        }
        if validation_errors.is_empty() {
            errors.set("".to_string());
            submitted.set(format!("Submitted: {}", username.get()));
        } else {
            errors.set(validation_errors.join("; "));
        }
    }
    "Submit"
}
Enter fullscreen mode Exit fullscreen mode

After validation passes, you can use the submitted signal to store the result of a successful submission, which can then be displayed to the user or sent to an API endpoint.

Summary

Form validation in euv leverages the framework's reactive signal system and event handling to provide a flexible and powerful validation mechanism. Key patterns include:

  • Basic field validation: Check individual fields on submit using onclick handlers.
  • Reactive validation: Use computed! and watch! macros for real-time validation feedback.
  • Error display: Use conditional rendering in the html! macro to show or hide error messages.
  • Cross-field validation: Use computed signals that depend on multiple values.
  • Submit button state: Bind the disabled attribute to a computed validity signal.
  • Component-based validation: Pass validation rules through component props.

By combining these patterns, you can build robust, user-friendly forms in your euv applications. The reactive nature of euv's signal system makes it easy to provide immediate feedback and maintain a clean separation between validation logic and UI rendering.


Project Code:https://github.com/euv-dev/euv

Top comments (0)