19. Формы и валидация
📝 Формы и валидация в Solid.js
Заголовок раздела «📝 Формы и валидация в Solid.js»Яша, работа с формами — одна из самых частых задач. В Solid есть несколько подходов: управляемые и неуправляемые формы, createStore для сложного состояния, библиотека @modular-forms/solid и серверные action. Разберём всё от простого к сложному! 🎯
🔄 Управляемые формы (Controlled)
Заголовок раздела «🔄 Управляемые формы (Controlled)»import { createSignal } from 'solid-js';
function ControlledForm() { const [name, setName] = createSignal(''); const [email, setEmail] = createSignal('');
const handleSubmit = (e: SubmitEvent) => { e.preventDefault(); console.log({ name: name(), email: email() }); };
return ( <form onSubmit={handleSubmit}> <input value={name()} onInput={(e) => setName(e.target.value)} placeholder="Имя" /> <input type="email" value={email()} onInput={(e) => setEmail(e.target.value)} placeholder="Email" /> <button type="submit">Отправить</button> </form> );}⚠️ Важно: В Solid используй
onInput(неonChange!) для управляемых инпутов.onChangeсрабатывает только при потере фокуса.
📦 Неуправляемые формы (Uncontrolled)
Заголовок раздела «📦 Неуправляемые формы (Uncontrolled)»function UncontrolledForm() { let formRef: HTMLFormElement;
const handleSubmit = (e: SubmitEvent) => { e.preventDefault(); const data = new FormData(formRef); console.log({ name: data.get('name'), email: data.get('email'), role: data.get('role'), }); };
return ( <form ref={formRef!} onSubmit={handleSubmit}> <input name="name" placeholder="Имя" /> <input name="email" type="email" placeholder="Email" /> <select name="role"> <option value="user">Пользователь</option> <option value="admin">Администратор</option> </select> <button type="submit">Отправить</button> </form> );}💡 Неуправляемые формы проще и быстрее — нет лишних сигналов. Отлично для простых форм без валидации в реальном времени.
🏗️ createStore для сложных форм
Заголовок раздела «🏗️ createStore для сложных форм»import { createStore } from 'solid-js/store';
interface FormState { values: { username: string; email: string; password: string; confirmPassword: string; }; errors: { username?: string; email?: string; password?: string; confirmPassword?: string; }; touched: { username?: boolean; email?: boolean; password?: boolean; confirmPassword?: boolean; }; isSubmitting: boolean;}
function RegistrationForm() { const [form, setForm] = createStore<FormState>({ values: { username: '', email: '', password: '', confirmPassword: '' }, errors: {}, touched: {}, isSubmitting: false, });
const validate = (field: string, value: string) => { if (field === 'username') { if (!value) return 'Имя обязательно'; if (value.length < 3) return 'Минимум 3 символа'; if (!/^[a-z0-9_]+$/.test(value)) return 'Только a-z, 0-9, _'; } if (field === 'email') { if (!value) return 'Email обязателен'; if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) return 'Неверный формат'; } if (field === 'password') { if (!value) return 'Пароль обязателен'; if (value.length < 8) return 'Минимум 8 символов'; } return undefined; };
const handleInput = (field: keyof FormState['values']) => (e: InputEvent) => { const value = (e.target as HTMLInputElement).value; setForm('values', field, value); // Валидация при вводе (если поле уже тронуто) if (form.touched[field]) { setForm('errors', field, validate(field, value)); } };
const handleBlur = (field: keyof FormState['values']) => () => { setForm('touched', field, true); setForm('errors', field, validate(field, form.values[field])); };
const handleSubmit = async (e: SubmitEvent) => { e.preventDefault(); // Валидируем все поля const errors = {}; for (const [field, value] of Object.entries(form.values)) { const error = validate(field, value); if (error) errors[field] = error; } if (Object.keys(errors).length > 0) { setForm('errors', errors); return; } setForm('isSubmitting', true); await submitForm(form.values); setForm('isSubmitting', false); };
return ( <form onSubmit={handleSubmit}> {/* Инпуты с валидацией */} <For each={['username', 'email', 'password'] as const}> {(field) => ( <div> <input value={form.values[field]} onInput={handleInput(field)} onBlur={handleBlur(field)} /> <Show when={form.errors[field]}> <span class="error">{form.errors[field]}</span> </Show> </div> )} </For> </form> );}📚 @modular-forms/solid
Заголовок раздела «📚 @modular-forms/solid»import { createForm, required, email, minLength } from '@modular-forms/solid';
type LoginForm = { email: string; password: string; remember: boolean;};
function LoginForm() { const [loginForm, { Form, Field }] = createForm<LoginForm>();
const handleSubmit = (values: LoginForm) => { console.log(values); };
return ( <Form onSubmit={handleSubmit}> <Field name="email" validate={[required('Email обязателен'), email('Неверный email')]} > {(field, props) => ( <div> <input {...props} type="email" /> {field.error && <span>{field.error}</span>} </div> )} </Field>
<Field name="password" validate={[required('Пароль обязателен'), minLength(8, 'Минимум 8 символов')]} > {(field, props) => ( <div> <input {...props} type="password" /> {field.error && <span>{field.error}</span>} </div> )} </Field>
<button type="submit" disabled={loginForm.submitting}> {loginForm.submitting ? 'Вход...' : 'Войти'} </button> </Form> );}⚡ Server Actions с формами (SolidStart)
Заголовок раздела «⚡ Server Actions с формами (SolidStart)»// SolidStart: форма, работающая без JavaScript!import { action, redirect } from '@solidjs/router';
const signUp = action(async (formData: FormData) => { 'use server'; const email = formData.get('email') as string; const password = formData.get('password') as string;
// Валидация на сервере if (!email || !email.includes('@')) { return { error: 'Неверный email' }; }
// Создаём пользователя await db.createUser({ email, password: await hash(password) });
// Редирект после успеха throw redirect('/dashboard');});
function SignUpForm() { const [result, submit] = useAction(signUp);
return ( <form action={signUp} method="post"> <input name="email" type="email" required /> <input name="password" type="password" minlength="8" /> <Show when={result()?.error}> <p class="error">{result()?.error}</p> </Show> <button type="submit">Зарегистрироваться</button> </form> );}🎮 Интерактивный пример
Заголовок раздела «🎮 Интерактивный пример»🚨 Частые ошибки
Заголовок раздела «🚨 Частые ошибки»// ❌ ОШИБКА: onChange вместо onInput<input onChange={(e) => setName(e.target.value)} />// Срабатывает только при потере фокуса!
// ✅ ПРАВИЛЬНО<input onInput={(e) => setName(e.target.value)} />
// ❌ ОШИБКА: мутация store напрямуюconst [form, setForm] = createStore({ name: '' });form.name = 'Яша'; // Не реактивно!
// ✅ ПРАВИЛЬНОsetForm('name', 'Яша'); // или setForm({ name: 'Яша' })
// ❌ ОШИБКА: валидация при каждом нажатии (плохой UX)<input onInput={(e) => { setValue(e.target.value); setError(validate(e.target.value)); // Ошибка мелькает при вводе}}/>