در این مقاله قصد داریم تا نحوه استفاده از localStorage برای تداوم ورودی فرم کاربر در حافظه مرورگر با استفاده از هوکهای React را پوشش دهیم. همچنین نحوه ساخت یک هوک سفارشی React برای به اشتراک گذاشتن منطق مشابه بین چندین کامپوننت را بررسی خواهیم کرد.
localStorage یکی از دو مکانیسم ذخیرهسازی وب مرورگر است. این کار به کاربران اجازه میدهد تا دادهها را به عنوان جفتهای key-value برای استفاده بعدی در مرورگر ذخیره کنند.
برخلاف مکانیسم sessionStorage
که تا زمانی که تب مرورگر فعلی در حال اجرا است دادهها را در حافظه مرورگر نگه میدارد، localStorage وقتی مرورگر بسته میشود، دادهها را پاک نمیکند. این موضوع آن را برای دادههای ماندگار که به تب مرورگر فعلی محدود نمیشوند ایدهآل میکند.
توسعهدهندگان معمولاً هنگام افزودن یک ویژگی مانند افزودن حالت دارک مود به یک برنامه، تداوم یک آیتم to-do یا حفظ مقادیر ورودی فرم کاربر یا موارد دیگر، از localStorage استفاده میکنند.
برای این که پروژه localStorage خود را با React راهاندازی کنیم به ترمینال میرویم و با استفاده از دستور زیر این کار را انجام میدهیم:
npx create-react-app localstorage-react-hook
همچنین میتوانیم برای راحتی بیشتر، راهاندازی پروژه را با استفاده از vite نیز انجام دهیم.
پس از ایجاد پوشه پروژه، آن را در IDE باز میکنیم و با اجرای دستور npm start
سرور توسعه را راهاندازی مینماییم. پروژه در آدرس مرورگر http://localhost:3000/
راهاندازی میشود.
همانطور که قبلا به آن اشاره کردیم، ما از localStorage استفاده میکنیم تا مقادیری که کاربر در فرم برنامه React وارد میکند را در حافظه مرورگر حفظ نماییم.
مانند هر برنامه Reactای، تمرکز ما بر روی پوشه src
است. بنابراین، تمام فایلهای داخل src
را حذف میکنیم و یک فایل index.js
در داخل پوشه src
میسازیم. سپس کد زیر را به index.js
اضافه میکنیم:
import React from "react"; import ReactDOM from "react-dom"; import App from "./components/App"; // styles import "./app.css"; ReactDOM.render( <React.StrictMode> <App /> </React.StrictMode>, document.getElementById("root") );
همینطور برای این که برنامه ظاهری زیبایی داشته باشد میتوانیم یک فایل app.css
در پوشه src
ایجاد کرده و کدهای css موجود در این لینک را در آن قرار دهیم.
سپس یک پوشه components
در پوشه src
ایجاد میکنیم تا فایلهای کامپوننت را در آن قرار دهیم. پس از آن، یک فایل App.js
و یک فایل Form1.js
میسازیم. فایل App.js
کامپوننت root و parent برنامه است و فایل Form1.js
ورودیهای فرم را نگه میدارد.
کد زیر را به فایل components/App.js
اضافه میکنیم:
import Form1 from "./Form1"; const App = () => { return ( <div className="container"> <h1>localStorage with React hooks</h1> <Form1 /> </div> ); }; export default App;
و در نهایت کدی که در ادامه داریم را در فایل components/Form1.js
قرار میدهیم:
import { useState } from "react"; const Form1 = () => { const [name, setName] = useState(""); return ( <form> <input type="text" value={name} onChange={(e) => setName(e.target.value)} placeholder="Full name" aria-label="fullname" /> <input type="submit" value="Submit"></input> </form> ); }; export default Form1;
کد بالا سادهترین پیادهسازی inputهای فرم در React است. با استفاده از هوک useState
کامپوننتی که داریم را کنترل میکنیم و state ورودی را بعد از هر تغییری که اتفاق میافتد، بهروز نگه میداریم.
اما، هنگامی که ما صفحه را رفرش میکنیم، همانطور که انتظار داریم دادههای ورودی پاک میشوند. برای تداوم دادههای ورودی به طوری که در بارگذاری مجدد صفحه یا در بازدیدهای بعدی در دسترس باشد، باید آنها را در localStorage ذخیره کنیم.
localStorage به ما امکان دسترسی به آبجکت Storage
مرورگر را میدهد. آبجکت Storage
دارای متدهایی برای ذخیرهسازی، خواندن و حذف دادهها و بسیاری از اکشنهای دیگر است.
برای مشاهده لیستی از متدهای Storage
، کنسول مرورگر را باز کرده و localStorage را تایپ میکنیم. پس از فشار دادن enter، متدها در prototype
آبجکت Storage
در دسترس قرار میگیرند.
برای ذخیره دادههای ورودی فرم در حافظه مرورگر، باید متد setItem()
مربوط به storage
را با استفاده از سینتکس زیر فراخوانی کنیم:
localStorage.setItem("key", "value")
storage
مرورگر فقط data-typeهای از نوع رشته را میپذیرد. بنابراین، برای مقادیر data-typeهای مختلف مانند آبجکت یا آرایه، باید آن را با استفاده از JSON.stringify()
به یک رشته JSON تبدیل نماییم.
ما میتوانیم از هوک useEffect
برای انجام side effectهایی مانند ذخیرهسازی دادهها در حافظه مرورگر استفاده کنیم. این اتفاق باعث میشود که هوک useEffect
مکان مناسبی برای فراخوانی متد setItem
باشد.
فایل components/Form1.js
را باز میکنیم و کد زیر را بالای عبارت return
اضافه مینماییم:
useEffect(() => { // storing input name localStorage.setItem("name", JSON.stringify(name)); }, [name]);
باید مطمئن شویم که useEffect
را از React به صورت زیر import کرده باشیم:
import { useState, useEffect } from "react";
در اینجا ما یک key "name"
و یک value داینامیک از متغیر state، که name
است را اختصاص دادهایم. مقدار اولیه متغیر state name
به صورت پیشفرض یک رشته خالی است:
const [name, setName] = useState("");
استفاده از JSON.stringify
در setItem
هنگام ذخیرهسازی دادههای string در حافظه اختیاری است:
localStorage.setItem("name", JSON.stringify(name));
با این حال، اگر value از نوع دادهای متفاوتی مانند یک آبجکت یا آرایه باشد، JSON.stringify
لازم است. اکنون اگر پروژه را تست کنیم میبینیم که با هر تغییری که در input صورت میگیرد، value آن در local storage ذخیره میشود. زیرا، هوک useEffect
که متد ذخیرهسازی setItem
را در خود دارد، در اولین رندر کامپوننت و پس از هر تغییر state اجرا میشود.
با این حال، در هر بار بارگذاری مجدد صفحه، value موجود در حافظه پاک میشود. این اتفاق میافتد زیرا ما یک رشته خالی پیشفرض را به متغیر state name
اختصاص دادهایم. بنابراین، React از آن string خالی در رندر اولیه استفاده میکند.
اکنون باید به جای اختصاص دادن یک رشته خالی، مقدار state بهروزرسانی شده در هر نقطه از ذخیرهسازی را دریافت کنیم و آن را به عنوان مقدار پیشفرض state اختصاص دهیم.
در بارگذاری اولیه صفحه، به جای اختصاص دادن یک رشته خالی به متغیر state name
، باید تابعی را اختصاص دهیم که به حافظه محلی دسترسی داشته باشد، مقدار ذخیره شده را بازیابی کند و از آن به عنوان مقدار پیشفرض استفاده نماید.
هوک useState
را در فایل components/Form1.js
بهروزرسانی میکنیم:
const [name, setName] = useState(() => { // getting stored value const saved = localStorage.getItem("name"); const initialValue = JSON.parse(saved); return initialValue || ""; });
در این کد، ما از متد getItem()
storage برای بازیابی دادهها از حافظه محلی استفاده میکنیم. JSON.parse()
مورد استفاده در کد، رشته JSON بازگشتی را از فضای ذخیرهسازی جدا میکند. هر دو JSON.stringify
و JSON.parse
هنگام کار با مقادیر رشته اختیاری هستند اما، انواع دادههای دیگر، مانند آبجکتها و آرایهها، به آنها نیاز دارند.
فایل را ذخیره کرده و پروژه را تست میکنیم. این بار دادههای ورودی input باید در فیلد فرم در بارگذاری مجدد صفحه یا پس بازدید از صفحه دیگر همچنان موجود باشند.
گاهی اوقات ممکن است بخواهیم ورودیهای فرم بیشتری، مانند ورودی متن و ورودی checkbox را در یک کامپوننت دیگری رندر کرده و آنها را داشته باشیم.
با این که میتوانیم به راحتی از منطقی که قبلا آن را پیادهسازی کردهایم در کامپپوننت جدید استفاده کنیم، اما این کار همیشه عملی نیست. به خصوص اگر تصمیم بگیریم که تعداد بیشتری از این ورودیها را داشته باشیم.
در عوض، React به ما این امکان را میدهد تا با استفاده از هوکهای سفارشی، منطق مشابهی را extract کرده و بین کامپوننتها به اشتراک بگذاریم. هدف ما در این بخش این است که نحوه ایجاد یک هوک سفارشی برای ماندگاری مقادیر ورودی فرمها در چندین کامپوننت را با هم بررسی کنیم.
در پوشه src/components
یک فایل جدید به نام Form2.js
ایجاد کرده و کد زیر را به اضافه میکنیم:
import { useState } from "react"; const Form2 = () => { const [name, setName] = useState(""); const [checked, setChecked] = useState(false); return ( <form> <input type="text" value={name} onChange={(e) => setName(e.target.value)} placeholder="Full name" aria-label="fullname" /> <label> <input type="checkbox" checked={checked} onChange={(e) => setChecked(e.target.checked)} />{" "} Not a robot? </label> <input type="submit" value="Submit"></input> </form> ); }; export default Form2;
سپس، کامپوننت را در فایل components/App.js
وارد کرده و از آن استفاده مینماییم:
// ... import Form2 from "./Form2"; const App = () => { return ( <div className="container"> {/* ... */} <Form2 /> </div> ); }; export default App;
اگر با فرم جدیدی که ساختیم تعامل داشته باشیم، مقدار state را در localStorage ذخیره نمیکند، زیرا هنوز منطق آن را نداریم. بنابراین، یک منطق واحد برای مدیریت تمام ورودیهای فرم خود تعریف میکنیم.
برای شروع extract منطق localStorage، یک فایل به نام useLocalStorage.js
در پوشه src
ایجاد کرده و کد زیر را به آن میافزاییم:
import { useState, useEffect } from "react"; function getStorageValue(key, defaultValue) { // getting stored value const saved = localStorage.getItem(key); const initial = JSON.parse(saved); return initial || defaultValue; } export const useLocalStorage = (key, defaultValue) => { const [value, setValue] = useState(() => { return getStorageValue(key, defaultValue); }); useEffect(() => { // storing input name localStorage.setItem(key, JSON.stringify(value)); }, [key, value]); return [value, setValue]; };
اگر در کد بالا دقت کنیم میبینیم که ما فقط منطق ذخیرهسازی را از فایل components/Form1.js
extract کردهایم.
با ایجاد یک هوک سفارشی به نام useLocalStorage
، تمام منطق ذخیرهسازی که در کامپوننت Form1
داریم را حفظ میکنیم. باید به این نکته توجه داشته باشیم که میتوانیم برای نامگذاری هوک سفارشی خود از هر نامی استفاده کنیم اما حتما باید در ابتدای نام از use
استفاده کرده باشیم.
هوک useLocalStorage
دو آرگومان دارد: key
و defaultValue
. این بدان معنی است که ما باید هنگام فراخوانی این هوک در کامپوننتهای مختلف، این مقادیر را حتما به آن پاس بدهیم.
در فایل components/Form1.js
، منطق بالای عبارت return
را با هوک سفارشی جایگزین میکنیم تا موارد زیر را داشته باشیم:
import { useLocalStorage } from "../useLocalStorage"; const Form1 = () => { const [name, setName] = useLocalStorage("name", ""); return ( <form> {/* ... */} </form> ); }; export default Form1;
پس از import کردن هوک سفارشی، میتوانیم از آن استفاده کرده و key منحصربهفرد و value پیشفرض را که در این مثال یک رشته خالی است، ارسال کنیم.
اگر همین کار را برای کامپوننت Form2
در فایل components/Form2js
انجام دهیم، باید کدی که داریم به صورت کد زیر باشد:
import { useLocalStorage } from "../useLocalStorage"; const Form2 = () => { const [name, setName] = useLocalStorage("name2", ""); const [checked, setChecked] = useLocalStorage("checked", false); return ( <form> {/* ... */} </form> ); }; export default Form2;
اکنون اگر پروژه را تست کنیم میتوانیم در قسمت localStorage مرورگر مشاهده کنیم که تمام ورودیهای فرم را ذخیره کردهایم.
هنگام کار با فریمورکهایی مانند Next.js که کد را در سمت سرور اجرا میکند، استفاده از localStorage خطایی ایجاد میکند تحت عنوان window is not defined.
.
localStorage، همانطور که در کد برنامه React خود از آن استفاده کردیم، یک ویژگی built-in از آبجکت window
یعنی window.localStorage
است. ما در کد خود، هنگام دسترسی به localStorage window
را نادیده گرفتیم زیرا یک آبجکت سراسری است. یعنی میتوانیم انتخاب کنیم که آبجکت window
به کد اضافه شود یا خیر، زیرا اختیاری میباشد.
اکنون، این آبجکت window
در سمت سرور در دسترس نیست، و فقط در سمت کلاینت یا مرورگر موجود است و همین دلیل باعث بروز خطا میشود. برای رفع خطا در سمت سرور، بررسی میکنیم که آیا آبجکت window
تعریف شده است یا خیر. به این ترتیب کد ما فقط در محیطی که window
در دسترس است اجرا میشود.
فایل src/useLocalStorage.js
را باز کرده و تابع getStorageValue()
را بهروزرسانی میکنیم تا موارد زیر را داشته باشد:
function getStorageValue(key, defaultValue) { // getting stored value if (typeof window !== "undefined") { const saved = localStorage.getItem(key); const initial = saved !== null ? JSON.parse(saved) : defaultValue; return initial; } }
نباید فراموش کنیم که ما از localStorage در داخل هوک useEffect
در فایل useLocalStorage.js
نیز استفاده کردهایم. اما در این مورد، localStorage دچار خطا نمیشود زیرا، هوک useEffect
فقط در سمت کلاینت اجرا میشود و در آن جا دسترسی به آبجکت window
وجود دارد.
خطای localStorage is not defined
در React به این دلیل رخ میدهد که localStorage یک API ذخیرهسازی وب مخصوص مرورگر است. این خطا عمدتاً زمانی رخ میدهد که از یک فانکشنالیتی مخصوص مرورگر در محیطی که تعریف نشده است استفاده میکنیم. به عنوان مثال، اگر کد ما در سمت سرور اجرا شود که localStorage در آن تعریف نشده است در این صورت با خطا مواجه میشویم. برای رفع این خطا چندین راه حل داریم که عبارتند از:
if...else
) یا با بررسی اینکه آیا window.localStorage
تعریف شده است یا خیر، انجام دهیم.پاک کردن localStorage جفتهای key-value ذخیره شده توسط برنامه ما را حذف میکند، بنابراین مهم است که این کار را فقط زمانی انجام دهیم که از آن مطمئن هستیم. برای پاک کردن localStorage در React، دو راه اصلی وجود دارد:
localStorage.clear()
: استفاده از این متد سادهترین و سریعترین راه برای پاک کردن localStorage است، زیرا هر چیزی که در آن دامنه ذخیره شده باشد را پاک میکند. سینتکس آن localStorage.clear();
است..removeItem()
: این متد یک key مشخص شده را از فضای localStorage دامنه حذف میکند. در نتیجه میتوانیم کنترل بیشتری بر روی موارد حذف شده داشته باشیم. استفاده از این متد نیز بسیار ساده است و ما باید نام key را برای حذف ارسال کنیم و سینتکس آن localStorage.removeItem(key)
میباشد.استفاده از هوکهای سفارشی React برای localStorage بهترین گزینه نیستند. اما با این حال، موارد استفاده و مزایای خود را دارند.
در این مثال، نحوه ایجاد یک هوک سفارشی برای localStorage با استفاده از هوکهای useEffect
و useState
را بررسی میکنیم:
import { useState, useEffect } from 'react'; export default function useLS(key, defaultValue) { const [value, setValue] = useState(() => { const storedValue = localStorage.getItem(key); if (storedValue) { return JSON.parse(storedValue); } return defaultValue; }); useEffect(() => { if (value === undefined) return; localStorage.setItem(key, JSON.stringify(value)); }, [value, key]); return [value, setValue]; } export default useLS;
کد بالا یک هوک سفارشی به نام useLS
است که امکان ذخیرهسازی و بازیابی دادهها از حافظه محلی مرورگر با استفاده از هوکهای useState
و useEffect
را فراهم میکند. این هوک سفارشی دو آرگومان میگیرد که عبارتند از: key
و defaultValue
. هوک useState
دادهها را از localStorage بازیابی میکند، آنها را parse کرده و درصورت null
بودن مقدار، defaultValue
را return میکند.
useLS
مقدار فعلی ذخیره شده در localStorage برای یک key
معین و یک تابع برای بهروزرسانی value را return میکند.
میتوانیم دریافت داده از localStorage را با استفاده از متد .getItem(key)
در React انجام دهیم. این متد value مربوط به key که آن را به متد ارسال میکنیم، بازیابی میکند و سینتکس آن به این صورت است: localStorage.getItem(your key)
ما در این مقاله سعی کردیم تا نحوه استفاده از localStorage برای ماندگاری دادهها در مرورگر با استفاده از هوکهای React را بررسی کنیم. همچنین با این مفهوم که چگونه میتوانیم یک هوک سفارشی برای extract کردن منطق کامپوننت در توابع با قابلیت استفاده مجدد آشنا شدیم.
در نهایت، دسترسی به کدهایی که در این مقاله آنها را بررسی کردیم از طریق این لینک امکانپذیر میباشد.