use tincan::Scope;
fn main() {
println!("=== Form Validation Example ===\n");
let cx = Scope::new();
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());
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('.')
}
});
let password_valid = cx.memo({
let password = password.clone();
move || password.get().len() >= 8
});
let passwords_match = cx.memo({
let password = password.clone();
let confirm = password_confirm.clone();
move || !password.get().is_empty() && password.get() == confirm.get()
});
let age_valid = cx.memo({
let age = age.clone();
move || {
age.get()
.parse::<u32>()
.map(|a| a >= 18 && a <= 120)
.unwrap_or(false)
}
});
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()
}
});
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");
});
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!");
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"));
}