6.7 טפסים בריאקט הרצאה
טפסים בריאקט - Forms in React¶
טפסים הם חלק בסיסי בכל אפליקציה - רישום, התחברות, חיפוש, הגדרות, ועוד. בריאקט, הגישה המומלצת לטפסים היא controlled components - קומפוננטות שבהן ריאקט שולטת בערכי השדות.
קומפוננטות מבוקרות - controlled components¶
בקומפוננטה מבוקרת, ערך השדה נשלט על ידי הסטייט של ריאקט. כל שינוי עובר דרך ה-handler ומעדכן את הסטייט:
import { useState } from "react";
function ControlledInput() {
const [name, setName] = useState("");
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setName(e.target.value);
};
return (
<div>
<input type="text" value={name} onChange={handleChange} />
<p>Hello, {name}!</p>
</div>
);
}
הזרימה:
1. המשתמש מקליד תו
2. onChange נורה עם הערך החדש
3. setName מעדכן את הסטייט
4. ריאקט מרנדרת מחדש - ה-input מציג את הערך המעודכן
למה controlled ולא uncontrolled¶
ב-HTML רגיל, ה-DOM מנהל את ערך ה-input. בגישה controlled, ריאקט היא מקור האמת היחיד (single source of truth). זה מאפשר:
- ולידציה בזמן אמת
- שליטה מלאה בפורמט הקלט
- השבתה מותנית של כפתור השליחה
- סנכרון ערכים בין כמה שדות
טיפול בסוגי שדות שונים¶
שדה טקסט - input text¶
function TextInput() {
const [value, setValue] = useState("");
return (
<input
type="text"
value={value}
onChange={(e) => setValue(e.target.value)}
placeholder="Enter text"
/>
);
}
אזור טקסט - textarea¶
ב-HTML, textarea משתמש בתוכן פנימי. בריאקט, הוא עובד כמו input עם value:
function TextArea() {
const [text, setText] = useState("");
return (
<div>
<textarea
value={text}
onChange={(e) => setText(e.target.value)}
rows={4}
placeholder="Write something..."
/>
<p>Characters: {text.length}</p>
</div>
);
}
רשימה נפתחת - select¶
ב-HTML, האופציה הנבחרת מסומנת עם selected. בריאקט, משתמשים ב-value על ה-select:
function SelectInput() {
const [color, setColor] = useState("red");
return (
<select value={color} onChange={(e) => setColor(e.target.value)}>
<option value="red">Red</option>
<option value="green">Green</option>
<option value="blue">Blue</option>
</select>
);
}
תיבת סימון - checkbox¶
checkbox משתמש ב-checked במקום value:
function Checkbox() {
const [isChecked, setIsChecked] = useState(false);
return (
<label>
<input
type="checkbox"
checked={isChecked}
onChange={(e) => setIsChecked(e.target.checked)}
/>
I agree to the terms
</label>
);
}
שימו לב: קוראים מ-e.target.checked (בוליאני), לא מ-e.target.value.
כפתורי רדיו - radio buttons¶
קבוצת radio חולקת את אותו name, והערך הנבחר נשמר בסטייט אחד:
function RadioGroup() {
const [size, setSize] = useState("medium");
return (
<div>
<label>
<input
type="radio"
name="size"
value="small"
checked={size === "small"}
onChange={(e) => setSize(e.target.value)}
/>
Small
</label>
<label>
<input
type="radio"
name="size"
value="medium"
checked={size === "medium"}
onChange={(e) => setSize(e.target.value)}
/>
Medium
</label>
<label>
<input
type="radio"
name="size"
value="large"
checked={size === "large"}
onChange={(e) => setSize(e.target.value)}
/>
Large
</label>
<p>Selected: {size}</p>
</div>
);
}
שליחת טופס - form submission¶
משתמשים ב-onSubmit על אלמנט ה-form. חשוב לקרוא ל-preventDefault כדי למנוע רענון הדף:
function LoginForm() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
console.log("Login:", { email, password });
// send to server...
};
return (
<form onSubmit={handleSubmit}>
<div>
<label>Email:</label>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
</div>
<div>
<label>Password:</label>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
</div>
<button type="submit">Login</button>
</form>
);
}
כמה שדות עם handler אחד¶
כשיש הרבה שדות, במקום handler נפרד לכל שדה, אפשר לנהל את כולם עם אובייקט סטייט אחד ו-handler אחד:
interface FormData {
firstName: string;
lastName: string;
email: string;
age: string;
}
function RegistrationForm() {
const [form, setForm] = useState<FormData>({
firstName: "",
lastName: "",
email: "",
age: "",
});
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target;
setForm({ ...form, [name]: value });
};
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
console.log("Form data:", form);
};
return (
<form onSubmit={handleSubmit}>
<input name="firstName" value={form.firstName} onChange={handleChange} placeholder="First Name" />
<input name="lastName" value={form.lastName} onChange={handleChange} placeholder="Last Name" />
<input name="email" value={form.email} onChange={handleChange} placeholder="Email" type="email" />
<input name="age" value={form.age} onChange={handleChange} placeholder="Age" type="number" />
<button type="submit">Register</button>
</form>
);
}
המפתח: כל input צריך אטריביוט name שתואם לשם השדה באובייקט הסטייט. ה-handler משתמש ב-[name]: value (computed property) כדי לעדכן את השדה הנכון.
טיפול ב-checkbox וב-select עם handler אחד¶
כשהטופס כולל סוגי שדות שונים, ה-handler צריך לבדוק את סוג השדה:
interface FormData {
name: string;
email: string;
role: string;
newsletter: boolean;
}
function MixedForm() {
const [form, setForm] = useState<FormData>({
name: "",
email: "",
role: "user",
newsletter: false,
});
const handleChange = (
e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>
) => {
const target = e.target;
let value: string | boolean;
if (target instanceof HTMLInputElement && target.type === "checkbox") {
value = target.checked;
} else {
value = target.value;
}
setForm({ ...form, [target.name]: value });
};
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
console.log(form);
};
return (
<form onSubmit={handleSubmit}>
<input name="name" value={form.name} onChange={handleChange} placeholder="Name" />
<input name="email" value={form.email} onChange={handleChange} placeholder="Email" type="email" />
<select name="role" value={form.role} onChange={handleChange}>
<option value="user">User</option>
<option value="admin">Admin</option>
<option value="editor">Editor</option>
</select>
<label>
<input
name="newsletter"
type="checkbox"
checked={form.newsletter}
onChange={handleChange}
/>
Subscribe to newsletter
</label>
<button type="submit">Submit</button>
</form>
);
}
ולידציה בסיסית¶
אפשר להוסיף ולידציה שמופעלת בזמן אמת או בעת השליחה:
interface FormData {
email: string;
password: string;
}
interface FormErrors {
email?: string;
password?: string;
}
function ValidatedForm() {
const [form, setForm] = useState<FormData>({ email: "", password: "" });
const [errors, setErrors] = useState<FormErrors>({});
const validate = (): boolean => {
const newErrors: FormErrors = {};
if (!form.email.includes("@")) {
newErrors.email = "Invalid email address";
}
if (form.password.length < 8) {
newErrors.password = "Password must be at least 8 characters";
}
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setForm({ ...form, [e.target.name]: e.target.value });
};
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
if (validate()) {
console.log("Form is valid:", form);
}
};
return (
<form onSubmit={handleSubmit}>
<div>
<input
name="email"
type="email"
value={form.email}
onChange={handleChange}
placeholder="Email"
/>
{errors.email && <p style={{ color: "red" }}>{errors.email}</p>}
</div>
<div>
<input
name="password"
type="password"
value={form.password}
onChange={handleChange}
placeholder="Password"
/>
{errors.password && <p style={{ color: "red" }}>{errors.password}</p>}
</div>
<button type="submit">Submit</button>
</form>
);
}
איפוס טופס¶
לאחר שליחה מוצלחת, לרוב רוצים לאפס את הטופס:
const initialForm: FormData = {
firstName: "",
lastName: "",
email: "",
age: "",
};
function ResetForm() {
const [form, setForm] = useState<FormData>(initialForm);
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
console.log("Submitted:", form);
setForm(initialForm); // reset to initial values
};
// ...
}
שמירת הערכים ההתחלתיים במשתנה מחוץ לקומפוננטה (או ב-const מחוץ לפונקציה) מאפשרת איפוס קל.
קומפוננטות מבוקרות לעומת לא מבוקרות¶
בנוסף ל-controlled components, יש גם uncontrolled components שמשתמשים ב-ref כדי לגשת ישירות ל-DOM:
import { useRef } from "react";
function UncontrolledInput() {
const inputRef = useRef<HTMLInputElement>(null);
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
console.log("Value:", inputRef.current?.value);
};
return (
<form onSubmit={handleSubmit}>
<input ref={inputRef} type="text" defaultValue="" />
<button type="submit">Submit</button>
</form>
);
}
ההבדלים:
| controlled | uncontrolled |
|---|---|
| ערך נשלט על ידי סטייט | ערך נשלט על ידי ה-DOM |
value + onChange |
defaultValue + ref |
| ולידציה בזמן אמת | ולידציה בשליחה בלבד |
| יותר קוד | פחות קוד |
| יותר שליטה | פחות שליטה |
ברוב המקרים, controlled components הם הגישה המומלצת. השתמשו ב-uncontrolled רק כשאין צורך בשליטה בערך השדה (למשל, file input).
סיכום¶
- קומפוננטות מבוקרות (controlled) - ריאקט שולטת בערכי השדות דרך סטייט
- כל שדה input צריך
valueו-onChange(אוcheckedו-onChangeעבור checkbox) - textarea ו-select עובדים כמו input עם
valueבריאקט - שליחת טופס עם
onSubmit- חשוב לקרוא ל-preventDefault - handler אחד לכמה שדות - שימוש ב-
nameו-computed property[name]: value - ולידציה אפשרית בזמן אמת או בשליחה
- שמירת ערכים התחלתיים מאפשרת איפוס קל של הטופס
- controlled components הם הגישה המומלצת ברוב המקרים