پیاده سازی یک فرم لاگین با استفاده از چندین useState Hook
در ابتدا، یک مثال کاربردیتر را به کمک useState Hookها پیاده سازی میکنیم. در اینجا هر المان فرم را به یک useState Hook مجزا، متصل کردهایم. کدهای کامل این کامپوننت را در ادامه مشاهده میکنید:
import React, { useState } from "react"; export default function Login() { const [username, setUsername] = useState(""); const [password, setPassword] = useState(""); const [user, setUser] = useState(null); const handleSubmit = event => { event.preventDefault(); const userData = { username, password }; setUser(userData); setUsername(""); setPassword(""); }; return ( <> <h2 className="mt-3">Login</h2> <form onSubmit={handleSubmit}> <div className="form-group"> <label htmlFor="username">Username</label> <input type="text" name="username" id="username" onChange={event => setUsername(event.target.value)} value={username} className="form-control" /> </div> <div className="form-group"> <label htmlFor="password">Password</label> <input type="password" name="password" id="password" onChange={event => setPassword(event.target.value)} value={password} className="form-control" /> </div> <button type="submit">Submit</button> </form> {user && JSON.stringify(user, null, 2)} </> ); }
- اگر دقت کرده باشید، اینبار این کامپوننت تابعی را به صورت متداول ()function Login تعریف کردهایم. مزیت یک چنین تعریفی، امکان export در محل آن میباشد:
export default function Login() {
به علاوه وجود واژهی default در اینجا سبب میشود که برای import آن، بتوان از هر نام دلخواهی استفاده کرد و در اینجا اجباری به استفادهی از نام Login وجود ندارد که نمونهی استفادهی از آن در فایل index.js، میتواند به صورت زیر باشد:
import App from "./components/part02/Login";
- فرم لاگین تعریف شده، از یک فیلد نام کاربری و یک فیلد کلمهی عبور تشکیل شدهاست.
- اکنون میخواهیم اطلاعات دریافت شدهی از کاربر را در state کامپوننت جاری منعکس کنیم. به همین جهت، کار با import متد useState شروع میشود. سپس به ازای هر فیلد در فرم، یک state مجزا را تعریف میکنیم:
const [username, setUsername] = useState(""); const [password, setPassword] = useState("");
<input type="text" name="username" id="username" onChange={event => setUsername(event.target.value)} value={username} className="form-control" />
- پس از به روز رسانی state، میخواهیم در حین submit فرم، این اطلاعات را برای مثال به صورت یک شیء، به سمت سرور ارسال کنیم. به همین جهت نیاز است رویداد onSubmit فرم را مدیریت کرد. در این متد ابتدا از post back معمول آن به سمت سرور جلوگیری میشود و سپس بر اساس متغیرهای تعریف شدهی در state، یک شیء را ایجاد کردهایم:
const handleSubmit = event => { event.preventDefault(); const userData = { username, password }; setUser(userData); setUsername(""); setPassword(""); };
{user && JSON.stringify(user, null, 2)}
const [user, setUser] = useState(null);
- دو سطر بعدی را که در انتهای handleSubmit مشاهده میکنید، روشی است برای خالی کردن المانهای فرم، پس از ارسال اطلاعات فرم، برای مثال به backend server. البته این حالت فقط برای حالتی نیاز است که فرم قرار نباشد به آدرس دیگری Redirect شود. برای خالی کردن المانهای فرم، المانهای آنرا باید تبدیل به controlled elements کرد که اینکار با مقدار دهی value آنها توسط value={username} صورت گرفتهاست. به این ترتیب محتوای این المانها با اطلاعاتی که در state داریم، قابل کنترل میشوند.
مثال دوم این مطلب نیز در مورد مدیریت المانهای یک فرم توسط useState Hook است؛ با این تفاوت که در اینجا تنها یک شیء، کل state را تشکیل میدهد. کدهای کامل این مثال را در ادامه مشاهده میکنید:
import React, { useState } from "react"; const initialFormState = { username: "", email: "", password: "" }; export default function Register() { const [form, setForm] = useState(initialFormState); const [user, setUser] = useState(null); const handleChange = event => { setForm({ ...form, [event.target.name]: event.target.value }); }; const handleSubmit = event => { event.preventDefault(); setUser(form); setForm(initialFormState); }; return ( <> <h2 className="mt-3">Register</h2> <form onSubmit={handleSubmit}> <div className="form-group"> <label htmlFor="username">Username</label> <input type="text" name="username" id="username" onChange={handleChange} value={form.username} className="form-control" /> </div> <div className="form-group"> <label htmlFor="email">Email</label> <input type="email" name="email" id="email" onChange={handleChange} value={form.email} className="form-control" /> </div> <div className="form-group"> <label htmlFor="password">Password</label> <input type="password" name="password" id="password" onChange={handleChange} value={form.password} className="form-control" /> </div> <button type="submit" className="btn btn-primary"> Submit </button> </form> {user && JSON.stringify(user, null, 2)} </> ); }
- فرم ثبت نام فوق از سه فیلد نام کاربری، ایمیل و کلمهی عبور تشکیل شدهاست.
- اینبار نحوهی تشکیل state مرتبط با این سه فیلد را بسیار شبیه به حالت مدیریت state در کامپوننتهای کلاسی، تعریف کردهایم؛ که تنها با یک تک شیء، انجام میشود و نام آنرا form در نظر گرفتهایم:
const [form, setForm] = useState({ username: "", email: "", password: ""});
<input type="text" name="username" id="username" onChange={handleChange} value={form.username} className="form-control" />
const handleChange = event => { setForm({ ...form, [event.target.name]: event.target.value }); };
- علت وجود spread operator تعریف شدهی در اینجا یعنی form...، این است که در حالت استفادهی از useState، برخلاف حالت کار با کامپوننتهای کلاسی، خواص اضافه شدهی به state، به شیء نهایی به صورت خودکار اضافه نمیشوند و باید کار یکی سازی را توسط spread operator انجام داد. برای مثال فرض کنید که کاربر، فیلد نام کاربری را ابتدا ثبت میکند. بنابراین در این لحظه، شیء ارسالی به setForm، فقط دارای خاصیت username خواهد شد. اکنون اگر در ادامه، کاربر فیلد ایمیل را تکمیل کند، اینبار فقط خاصیت ایمیل در این شیء قرار خواهد گرفت (یا مقدار قبلی را به روز رسانی میکند) و از سایر خواص صرفنظر میشود؛ مگر اینکه توسط spread operator، سایر خواص پیشین موجود در شیء form را نیز در اینجا لحاظ کنیم، تا اطلاعاتی را از دست نداده باشیم.
بنابراین به صورت خلاصه در روش سنتی کار با کامپوننتهای کلاسی، فراخوانی متد this.setState کار merge خواص را انجام میدهد؛ اما در اینجا فقط کار replace صورت میگیرد و باید کار merge خواص یک شیء را به صورت دستی و توسط یک spread operator انجام دهیم. البته در قسمت قبل چون تمام خواص شیء تعریف شدهی در state را با هم به روز رسانی میکردیم:
setMousePosition({ x: event.pageX, y: event.pageY });
- سایر فیلدهای فرم نیز به همین روش onChange={handleChange}، به متد رویدادگردان فوق متصل میشوند.
- در پایان برای مدیریت رخداد ارسال فرم، handleSubmit را به صورت زیر تعریف کردهایم:
const handleSubmit = event => { event.preventDefault(); setUser(form); setForm(initialFormState); };
- همچنین چون در پایین فرم نیز میخواهیم این اطلاعات را به صورت JSON نمایش دهیم:
{user && JSON.stringify(user, null, 2)}
const [user, setUser] = useState(null);
- برای پاک کردن المانهای فرم، پس از submit آن، ابتدا نیاز است این المانها را تبدیل به controlled elements کرد که اینکار با مقدار دهی value آنها توسط برای مثال value={form.username} صورت گرفتهاست. به این ترتیب محتوای این المانها با اطلاعاتی که در state داریم، قابل کنترل میشوند. اکنون اگر setForm را با یک شیء خالی مقدار دهی کنیم، به صورت خودکار المانهای فرم را پاک میکند. برای اینکار بجای تعریف شیء موجود در state به صورت inline:
const [form, setForm] = useState({ username: "", email: "", password: ""});
const initialFormState = { username: "", email: "", password: "" }; export default function Register() { const [form, setForm] = useState(initialFormState);
مقایسهی روشهای مختلف مدیریت state توسط useState Hook