در این مقاله قصد داریم تا نحوه استفاده از 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 کردن منطق کامپوننت در توابع با قابلیت استفاده مجدد آشنا شدیم.
در نهایت، دسترسی به کدهایی که در این مقاله آنها را بررسی کردیم از طریق این لینک امکانپذیر میباشد.