tincan 0.3.0

A lightweight reactive state management library for Rust.
Documentation
//! Form validation example
//!
//! Demonstrates complex derived state for form validation logic.
//!
//! Run with: cargo run --example form_validation

use tincan::Scope;

fn main() {
    println!("=== Form Validation Example ===\n");

    let cx = Scope::new();

    // Form fields
    let email = cx.signal(String::new());
    let password = cx.signal(String::new());
    let password_confirm = cx.signal(String::new());
    let age = cx.signal(String::new());

    // Validation: email
    let email_valid = cx.memo({
        let email = email.clone();
        move || {
            let email_str = email.get();
            !email_str.is_empty() && email_str.contains('@') && email_str.contains('.')
        }
    });

    // Validation: password
    let password_valid = cx.memo({
        let password = password.clone();
        move || password.get().len() >= 8
    });

    // Validation: passwords match
    let passwords_match = cx.memo({
        let password = password.clone();
        let confirm = password_confirm.clone();
        move || !password.get().is_empty() && password.get() == confirm.get()
    });

    // Validation: age
    let age_valid = cx.memo({
        let age = age.clone();
        move || {
            age.get()
                .parse::<u32>()
                .map(|a| a >= 18 && a <= 120)
                .unwrap_or(false)
        }
    });

    // Overall form validation
    let form_valid = cx.memo({
        let email_valid = email_valid.clone();
        let password_valid = password_valid.clone();
        let passwords_match = passwords_match.clone();
        let age_valid = age_valid.clone();
        move || {
            email_valid.get()
                && password_valid.get()
                && passwords_match.get()
                && age_valid.get()
        }
    });

    // Effect to show validation status
    let email_valid_clone = email_valid.clone();
    let password_valid_clone = password_valid.clone();
    let passwords_match_clone = passwords_match.clone();
    let age_valid_clone = age_valid.clone();
    let form_valid_clone = form_valid.clone();
    
    let _validation_effect = cx.effect(move || {
        println!("─────────────────────────────");
        println!("Validation Status:");
        println!("  Email: {}", if email_valid_clone.get() { "" } else { "" });
        println!("  Password: {}", if password_valid_clone.get() { "" } else { "" });
        println!("  Passwords Match: {}", if passwords_match_clone.get() { "" } else { "" });
        println!("  Age: {}", if age_valid_clone.get() { "" } else { "" });
        println!("  Form Valid: {}", if form_valid_clone.get() { "✓ CAN SUBMIT" } else { "✗ CANNOT SUBMIT" });
        println!("─────────────────────────────\n");
    });

    // Simulate user input
    println!("Step 1: Enter email");
    email.set(String::from("user@example.com"));

    println!("Step 2: Enter password");
    password.set(String::from("short"));

    println!("Step 3: Enter valid password");
    password.set(String::from("mySecurePassword123"));

    println!("Step 4: Confirm password (typo)");
    password_confirm.set(String::from("mySecurePassword124"));

    println!("Step 5: Correct password confirmation");
    password_confirm.set(String::from("mySecurePassword123"));

    println!("Step 6: Enter age");
    age.set(String::from("17"));

    println!("Step 7: Enter valid age");
    age.set(String::from("25"));

    println!("✅ Form is now valid and ready to submit!");

    // Demonstrate error message generation
    println!("\n=== Error Messages ===\n");

    let cx2 = Scope::new();
    let username = cx2.signal(String::new());
    
    let username_error = cx2.memo({
        let username = username.clone();
        move || {
            let name = username.get();
            if name.is_empty() {
                Some("Username is required".to_string())
            } else if name.len() < 3 {
                Some("Username must be at least 3 characters".to_string())
            } else if name.len() > 20 {
                Some("Username must be at most 20 characters".to_string())
            } else if !name.chars().all(|c| c.is_alphanumeric() || c == '_') {
                Some("Username can only contain letters, numbers, and underscores".to_string())
            } else {
                None
            }
        }
    });

    let username_error_clone = username_error.clone();
    let _error_effect = cx2.effect(move || {
        if let Some(error) = username_error_clone.get() {
            println!("❌ Error: {}", error);
        } else {
            println!("✓ Username is valid");
        }
    });

    username.set(String::from("ab"));
    username.set(String::from("user@name"));
    username.set(String::from("valid_user_123"));
}