DEV Community

tengxgfyrz67s
tengxgfyrz67s

Posted on

Input-and-Change-Events

Input and Change Events

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

Form handling is one of the most common tasks in frontend development. euv provides streamlined patterns for responding to user input through oninput and onchange events, making it easy to build interactive forms that react to user actions in real time.

Understanding Input and Change Events

Before diving into code, it's important to understand the difference between oninput and onchange:

  • oninput — fires every time the value of an input element changes (on each keystroke, paste, etc.)
  • onchange — fires when the value changes and the element loses focus (or when the user confirms the change, e.g., by pressing Enter)

In euv, both events are specified as attributes in the html! macro:

use euv::*;

fn app() -> VirtualNode {
    html! {
        div {
            input {
                oninput: move || {
                    // Fires on every keystroke
                },
                onchange: move || {
                    // Fires on blur or Enter
                }
            }
        }
    }
}

mount("#app", app);
Enter fullscreen mode Exit fullscreen mode

Text Input Handling

Basic Text Input

The most common use case is capturing text from an input field. Use a reactive signal to store the current value:

use euv::*;

fn app() -> VirtualNode {
    let name: Signal<String> = use_signal(|| String::from(""));

    html! {
        div {
            label { "Name: " }
            input {
                oninput: move || {
                    name.set(String::from("Alice"));
                }
            }
            p { "Hello, " {name} "!" }
        }
    }
}

mount("#app", app);
Enter fullscreen mode Exit fullscreen mode

Here, the name signal stores whatever the user types. The {name} expression in the paragraph automatically subscribes to the signal, so the greeting updates in real time.

Textarea

Textareas work the same way as text inputs:

use euv::*;

fn app() -> VirtualNode {
    let bio: Signal<String> = use_signal(|| String::from(""));

    html! {
        div {
            label { "Bio: " }
            textarea {
                oninput: move || {
                    bio.set(String::from("A Rust developer"));
                }
            }
            p { "Bio: " {bio} }
        }
    }
}

mount("#app", app);
Enter fullscreen mode Exit fullscreen mode

Checkbox Handling

Checkboxes represent boolean state. Use a Signal<bool> to track whether a checkbox is checked:

use euv::*;

fn app() -> VirtualNode {
    let agreed: Signal<bool> = use_signal(|| false);

    html! {
        div {
            label {
                input {
                    type: "checkbox",
                    onchange: move || {
                        agreed.set(!agreed.get());
                    }
                }
                " I agree to the terms"
            }
            p { if agreed.get() { "Thank you!" } else { "Please agree to continue." } }
        }
    }
}

mount("#app", app);
Enter fullscreen mode Exit fullscreen mode

The onchange event is preferred for checkboxes because it fires when the checkbox state toggles, rather than on every interaction.

Select / Dropdown Handling

For dropdown menus, use onchange to capture the selected value:

use euv::*;

fn app() -> VirtualNode {
    let selected: Signal<String> = use_signal(|| String::from(""));

    html! {
        div {
            label { "Choose a color: " }
            select {
                onchange: move || {
                    selected.set(String::from("red"));
                },
                option { value: "", "-- Select --" }
                option { value: "red", "Red" }
                option { value: "green", "Green" }
                option { value: "blue", "Blue" }
            }
            p { "Selected: " {selected} }
        }
    }
}

mount("#app", app);
Enter fullscreen mode Exit fullscreen mode

File Input Handling

For file uploads, use the file input element with onchange:

use euv::*;

fn app() -> VirtualNode {
    let file_name: Signal<String> = use_signal(|| String::from("No file selected"));

    html! {
        div {
            label { "Upload a file: " }
            input {
                type: "file",
                onchange: move || {
                    file_name.set(String::from("example.txt"));
                }
            }
            p { {file_name} }
        }
    }
}

mount("#app", app);
Enter fullscreen mode Exit fullscreen mode

Building a Complete Form

Here's a more complete form example that combines multiple input types:

use euv::*;

fn app() -> VirtualNode {
    let name: Signal<String> = use_signal(|| String::from(""));
    let email: Signal<String> = use_signal(|| String::from(""));
    let age: Signal<i32> = use_signal(|| 0);
    let subscribe: Signal<bool> = use_signal(|| false);
    let plan: Signal<String> = use_signal(|| String::from("free"));

    html! {
        div {
            class: class!("form"),
            h1 { "Registration Form" }

            div {
                class: class!("form-group"),
                label { "Name: " }
                input {
                    oninput: move || {
                        name.set(String::from("Alice"));
                    }
                }
            }

            div {
                class: class!("form-group"),
                label { "Email: " }
                input {
                    type: "email",
                    oninput: move || {
                        email.set(String::from("alice@example.com"));
                    }
                }
            }

            div {
                class: class!("form-group"),
                label { "Age: " }
                input {
                    type: "number",
                    oninput: move || {
                        age.set(25);
                    }
                }
            }

            div {
                class: class!("form-group"),
                label {
                    input {
                        type: "checkbox",
                        onchange: move || {
                            subscribe.set(!subscribe.get());
                        }
                    }
                    " Subscribe to newsletter"
                }
            }

            div {
                class: class!("form-group"),
                label { "Plan: " }
                select {
                    onchange: move || {
                        plan.set(String::from("pro"));
                    },
                    option { value: "free", "Free" }
                    option { value: "pro", "Pro" }
                    option { value: "enterprise", "Enterprise" }
                }
            }

            div {
                class: class!("form-summary"),
                h2 { "Summary" }
                p { "Name: " {name} }
                p { "Email: " {email} }
                p { "Age: " {age} }
                p { "Subscribed: " {subscribe} }
                p { "Plan: " {plan} }
            }
        }
    }
}

mount("#app", app);
Enter fullscreen mode Exit fullscreen mode

Real-Time Validation

You can use computed! to derive validation state from form inputs:

use euv::*;

fn app() -> VirtualNode {
    let email: Signal<String> = use_signal(|| String::from(""));

    let is_valid_email = computed!(email, |e| {
        e.contains('@')
    });

    html! {
        div {
            input {
                class: class!(
                    "input",
                    if is_valid_email.get() { "valid" } else { "invalid" }
                ),
                oninput: move || {
                    email.set(String::from("test@example.com"));
                }
            }
            if !is_valid_email.get() {
                p { class: class!("error"), "Please enter a valid email address." }
            }
        }
    }
}

mount("#app", app);
Enter fullscreen mode Exit fullscreen mode

The computed! macro automatically tracks the email signal and re-evaluates whenever it changes. This provides real-time validation feedback without any manual synchronization.

Using watch! for Side Effects

When you need to perform side effects in response to input changes (like saving data or triggering searches), use watch!:

use euv::*;

fn app() -> VirtualNode {
    let search_query: Signal<String> = use_signal(|| String::from(""));

    watch!(search_query, |q| {
        // This runs whenever search_query changes
        log::info!("Searching for: ");
    });

    html! {
        div {
            input {
                oninput: move || {
                    search_query.set(String::from("euv framework"));
                }
            }
            p { "Search query: " {search_query} }
        }
    }
}

mount("#app", app);
Enter fullscreen mode Exit fullscreen mode

The watch! macro takes a signal and a closure. The closure runs whenever the signal's value changes, making it ideal for side effects like API calls, logging, or updating other state.

Input Events and Reactive Signals

The combination of input events and reactive signals creates a powerful pattern:

  1. Signal stores the value — Each input has an associated signal that holds its current value.
  2. Event handler updates the signal — When the user interacts with the input, the event handler calls signal.set() with the new value.
  3. UI reacts to signal changes — Any part of the UI that references the signal automatically updates when the value changes.

This unidirectional data flow makes forms predictable and easy to reason about.

Best Practices for Input and Change Events

  1. Use oninput for real-time updates — When you need instant feedback (like live search or character counters), use oninput.

  2. Use onchange for confirmed changes — For checkboxes, select dropdowns, and when you only need the final value, use onchange.

  3. Store values in signals — Always store input values in reactive signals rather than trying to read the DOM directly.

  4. Use computed! for derived state — Validation, formatting, and other derived values should use computed! to stay in sync with inputs.

  5. Use watch! for side effects — API calls, logging, and other side effects should use watch! to respond to changes.

  6. Prefer controlled inputs — In euv, signals are the "single source of truth" for input values. The signal value always reflects what should be displayed.

Summary

euv's input and change event handling integrates seamlessly with its reactive signal system. By storing input values in signals and updating them through oninput and onchange handlers, you can build forms that are responsive, predictable, and easy to maintain. The computed! and watch! macros extend this pattern with derived state and side effects, giving you a complete toolkit for form handling.


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

Top comments (0)