۶ React Router یک کتابخانه routing محبوب و قدرتمند برای برنامههای React است. کتابخانه React Router یک رویکرد declarative و مبتنی بر کامپوننت برای مسیریابی ارائه میدهد و وظایف رایج مربوط به رسیدگی به پارامترهای URL، redirectها و بارگذاری دادهها را انجام میدهد.
React Router یکی از بصریترین APIهای موجود را ارائه میدهد و lazy loading و رندر سمت سرور سازگار با SEO را امکانپذیر میکند.
در این قاله قصد داریم تا نحوه ایجاد مسیرهای محافظت شده و احراز هویت با نسخه ۶ React Router را باهم بررسی کنیم.
برای شروع، ترمینال را باز میکنیم، با اجرای دستور زیر و با استفاده از vite یک پروژه React جدید میسازیم:
npm create vite@latest ReactRouterAuthDemo -- --template react cd ReactRouterAuthDemo
سپس، React Router را به عنوان یک dependency در برنامه React نصب میکنیم:
npm install react-router-dom
هنگامی که React Router نصب شد، باید فایل src/main.js
را ویرایش کنیم.
BrowserRouter
را از react-router-dom
import میکنیم و سپس کامپوننت <App />
را داخل <BrowserRouter />
قرار میدهیم، به این صورت که:
import React from "react"; import ReactDOM from "react-dom/client"; import App from "./App.jsx"; import "./index.css"; import { BrowserRouter } from "react-router-dom"; ReactDOM.createRoot(document.getElementById("root")).render( <React.StrictMode> <BrowserRouter> <App /> </BrowserRouter> </React.StrictMode> );
اکنون، تنظیماتی که انجام دادیم به گونهای است که میتوانیم از کامپوننتهای React Router و Hooks در هر نقطه از برنامه خود استفاده کنیم.
حال قصد داریم تا boilerplate code از فایل App.js
را با برخی از مسیرها جایگزین نماییم.
React Router کامپوننتهای <Routes />
و <Route />
را فراهم میکند که ما را قادر میسازد تا کامپوننتها را بر اساس مکان فعلی آنها رندر کنیم:
// src/App.jsx import { Routes, Route } from "react-router-dom"; import { LoginPage } from "./pages/Login"; import { HomePage } from "./pages/Home"; import "./App.css"; function App() { return ( <Routes> <Route path="/" element={<HomePage />} /> <Route path="/login" element={<LoginPage />} /> </Routes> ); } export default App;
<Route >
<Route />
mapping بین pathهای موجود در برنامه و کامپوننتهای مختلف React را ارائه میدهد. به عنوان مثال، هنگامی که شخصی به مسیر /login
هدایت میشود، برای این که کامپوننت LoginPage
را رندر کنیم فقط باید <Route />
را ارائه دهیم، مانند:
<Route path="/login" element={<LoginPage />} />
کامپوننت <Route />
را میتوانیم مانند یک عبارت if در نظر بگیریم. به این ترتیب، فقط در صورتی که با path مشخص شده مطابقت داشته باشد، بر روی یک لوکیشین URL با element خود عمل میکند.
<Routes />
کامپوننت <Routes />
جایگزینی برای کامپوننت <Switch />
از نسخه ۵ React Router است. برای استفاده از <Routes />
، ابتدا فایلهای Login.jsx
و Home.jsx
را در دایرکتوری pages با محتوای زیر ایجاد میکنیم:
// Login.jsx export const LoginPage = () => ( <div> <h1>This is the Login Page</h1> </div> ); // Home.jsx export const HomePage = () => ( <div> <h1>This is the Home Page</h1> </div> );
در مرحله بعد، دستور زیر را برای شروع برنامه اجرا مینماییم:
npm run dev
در مرورگر، به طور پیشفرض کامپوننت HomePage
را میبینیم. اگر به مسیر /login
برویم، کامپوننت LoginPage
را در صفحه نمایش مشاهده خواهیم کرد. از طرف دیگر، میتوانیم از یک آبجکت جاوااسکریپت ساده برای نشان دادن مسیرها در برنامه خود با استفاده از هوک useRoutes
استفاده نماییم. این یک رویکرد فانکشنال برای تعریف مسیرها است و مانند ترکیب کامپوننتهای <Routes />
و <Route />
کار میکند:
// src/App.jsx import { useRoutes } from "react-router-dom"; // ... export default function App() { const routes = useRoutes([ { path: "/", element: <HomePage /> }, { path: "/login", element: <LoginPage /> } ]); return routes; }
اکنون که تنظیمات اولیه تکمیل شده است، قصد داریم به این موضوع بپردازیم که چگونه میتوانیم مسیرهای محافظت شده ایجاد کنیم تا کاربران احراز هویت نشده نتوانند به محتوای خاصی در برنامه دسترسی داشته باشند.
مسیرهای محافظت شده، که اغلب به عنوان مسیرهای private شناخته میشوند، یک مفهوم اساسی در توسعه وب است که برای محدود کردن دسترسی به صفحات یا منابع خاص فقط برای کاربران تأیید شده مورد استفاده قرار میگیرد.
برای پیادهسازی یک مسیر محافظتشده در پروژه، کار خود را با ایجاد یک هوک سفارشی با نام useAuth
برای مدیریت state کاربر احراز هویت شده با استفاده از Context API و هوک useContext
شروع میکنیم:
// src/hooks/useAuth.jsx import { createContext, useContext, useMemo } from "react"; import { useNavigate } from "react-router-dom"; import { useLocalStorage } from "./useLocalStorage"; const AuthContext = createContext(); export const AuthProvider = ({ children }) => { const [user, setUser] = useLocalStorage("user", null); const navigate = useNavigate(); // call this function when you want to authenticate the user const login = async (data) => { setUser(data); navigate("/profile"); }; // call this function to sign out logged in user const logout = () => { setUser(null); navigate("/", { replace: true }); }; const value = useMemo( () => ({ user, login, logout, }), [user] ); return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>; }; export const useAuth = () => { return useContext(AuthContext); };
این هوک useAuth
state و متدهای user را برای ورود و خروج کاربران نشان میدهد. هنگامی که کاربران با موفقیت وارد سیستم میشوند، متد login()
state آنها را تغییر میدهد تا وضعیت احراز هویت آنها را منعکس کند. علاوه بر این، هنگامی که کاربران از سیستم خارج میشوند، با استفاده از هوک useNavigate
در React Router، آنها را به صفحه اصلی هدایت میکنیم.
برای این که بتوانیم state کاربر را حتی پس از بهروزرسانی صفحه حفظ نماییم، هوک useLocalStorage
را ایجاد میکنیم که مقدار state را با local storage مرورگر همگامسازی مینماید:
// src/hooks/useLocalStorage.jsx import { useState } from "react"; export const useLocalStorage = (keyName, defaultValue) => { const [storedValue, setStoredValue] = useState(() => { try { const value = window.localStorage.getItem(keyName); if (value) { return JSON.parse(value); } else { window.localStorage.setItem(keyName, JSON.stringify(defaultValue)); return defaultValue; } } catch (err) { return defaultValue; } }); const setValue = (newValue) => { try { window.localStorage.setItem(keyName, JSON.stringify(newValue)); } catch (err) { console.log(err); } setStoredValue(newValue); }; return [storedValue, setValue]; };
در مرحله بعد، کامپوننت ProtectedRoute
را میسازیم، که state کاربر فعلی را از هوک useAuth
بررسی میکند و در صورت عدم احراز هویت، آنها را به صفحه اصلی هدایت میکند:
import { Navigate } from "react-router-dom"; import { useAuth } from "../hooks/useAuth"; export const ProtectedRoute = ({ children }) => { const { user } = useAuth(); if (!user) { // user is not authenticated return <Navigate to="/login" />; } return children; };
در کد بالا، ما از کامپوننت <Navigate />
React Router برای هدایت کاربران احراز هویت نشده به مسیر /login
استفاده مینماییم.
اکنون، گام بعدی اضافه کردن یک مسیر LoginPage
برای احراز هویت کاربر و یک مسیر Secret
است که فقط برای کاربران وارد شده قابل مشاهده میباشد.
برای این کار، یک فایل به نام Login.jsx
در دایرکتوری pages خود ایجاد میکنیم و کد زیر را در آن قرار میدهیم:
import { useState } from "react"; import { useAuth } from "../hooks/useAuth"; export const LoginPage = () => { const [username, setUsername] = useState(""); const [password, setPassword] = useState(""); const { login } = useAuth(); const handleLogin = async (e) => { e.preventDefault(); // Here you would usually send a request to your backend to authenticate the user // For the sake of this example, we're using a mock authentication if (username === "user" && password === "password") { // Replace with actual authentication logic await login({ username }); } else { alert("Invalid username or password"); } }; return ( <div> <form onSubmit={handleLogin}> <div> <label htmlFor="username">Username:</label> <input id="username" type="text" value={username} onChange={(e) => setUsername(e.target.value)} /> </div> <div> <label htmlFor="password">Password:</label> <input id="password" type="password" value={password} onChange={(e) => setPassword(e.target.value)} /> </div> <button type="submit">Login</button> </form> </div> ); };
این کامپوننت به عنوان interface ورود به سیستم عمل میکند و از هوک useAuth
برای کنترل احراز هویت کاربر استفاده مینماید. هنگامی که کاربران اطلاعات خود را وارد کرده و فرم را ارسال میکنند، تابع login()
از useAuth
برای احراز هویت و ورود آنها فراخوانی میشود.
به طور مشابه، یک فایل Secret.jsx
در دایرکتوری pages ایجاد میکنیم که یک صفحه ایمن را نشان میدهد و محتوا آن منحصراً برای کاربران تأیید شده نمایش داده میشود:
import { useAuth } from "../hooks/useAuth"; export const Secret = () => { const { logout } = useAuth(); const handleLogout = () => { logout(); }; return ( <div> <h1>This is a Secret page</h1> <button onClick={handleLogout}>Logout</button> </div> ); };
همچنین یک دکمه خروج در صفحه Secret
در بالا اضافه قرار دادهایم تا این امکان را برای کاربران فراهم کنیم که در صورت لزوم از سیستم خارج شوند. این عمل خروج توسط متد logout()
از هوک useAuth
انجام میشود.
در نهایت، در فایل App.jsx
، تمام مسیرهای موجود در AuthProvider
را از هوک useAuth
که قبلا ایجاد کرده بودیم کپسوله میکنیم تا یک context احراز هویت ثابت در سراسر برنامهای که داریم فراهم شود. مسیرهای خود را طبق روش همیشگی تنظیم میکنیم و برای مسیرهایی که نیاز به احراز هویت دارند، از کامپوننت <ProtectedRoute />
استفاده میکنیم تا دسترسی را فقط به کاربران تأیید شده محدود نماییم:
// src/App.jsx import { Routes, Route } from "react-router-dom"; import { LoginPage } from "./pages/Login"; import { HomePage } from "./pages/Home"; import { Secret } from "./pages/Secret"; import "./App.css"; import { ProtectedRoute } from "./components/ProtectedRoute"; import { AuthProvider } from "./hooks/useAuth"; function App() { return ( <AuthProvider> <Routes> <Route path="/" element={<HomePage />} /> <Route path="/login" element={<LoginPage />} /> <Route path="/secret" element={ <ProtectedRoute> <Secret /> </ProtectedRoute> } /> </Routes> </AuthProvider> ); } export default App;
با انجام این مراحل، یک جریان احراز هویت اولیه ایجاد کردهایم که فقط به کاربران احراز هویت شده اجازه دسترسی به مسیرهای محافظت شده را میدهد. اگر سعی کنیم بدون ورود به سیستم به مسیر /secret
دسترسی پیدا کنید، فوراً به صفحه ورود هدایت میشویم. با این حال، پس از وارد کردن نام کاربری و رمز عبور پیشفرض در فرم ورود، میتوانیم به صفحه secret دسترسی پیدا کنیم.
اگر تعداد محدودی از مسیرهای محافظت شده داشته باشیم، رویکرد فوق به خوبی کار میکند. با این حال، اگر چندین مسیر با این شرایط داشته باشیم، باید هر کدام را wrap کنیم که این موضوع میتواند کمی خستهکننده باشد.
برای رفع این مشکل، میتوانیم از ویژگی مسیر تودرتو نسخه ۶ React Router استفاده کنیم تا تمام مسیرهای محافظتشده را در یک layout واحد قرار دهیم.
در این بخش قصد داریم تا برنامه خود را با افزودن احراز هویت دو مرحلهای (۲FA) با React Router ارتقا دهیم. ۲FA با الزام کاربران به ارائه دو شکل مجزا از شناسایی قبل از دسترسی به ویژگیهای حساس، یک لایه امنیتی اضافی اضافه میکند.
برای ادامه، ابتدا لازم است تا تنظیمات احراز هویت موجود را تغییر دهیم تا شامل ۲FA شود. فایل useAuth.jsx
خود را با کد زیر بهروزرسانی مینماییم:
// src/hooks/useAuth.jsx import { createContext, useContext, useState } from "react"; import { useNavigate } from "react-router-dom"; import { useLocalStorage } from "./useLocalStorage"; const AuthContext = createContext(); export const AuthProvider = ({ children }) => { const [user, setUser] = useLocalStorage("user", null); const [is2FAVerified, setIs2FAVerified] = useState(false); const navigate = useNavigate(); const login = async (data) => { setUser(data); // Navigate to 2FA verification page navigate("/verify-2fa"); }; const logout = () => { setUser(null); setIs2FAVerified(false); navigate("/", { replace: true }); }; const verify2FACode = async (code) => { // Mock verification logic if (code === "0000") { setIs2FAVerified(true); navigate("/secret"); // Navigate to a protected route after successful 2FA return true; } return false; }; const value = { user, is2FAVerified, login, logout, verify2FACode, }; return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>; }; export const useAuth = () => { return useContext(AuthContext); };
در این فایل آپدیت شده، ما متد ورود به سیستم را برای ۲FA تغییر دادهایم و یک تابع verify2FACode()
اضافه کردهایم که کد ۰۰۰۰
را برای سادگی تایید میکند. اگر یک سناریوی واقعی را در نظر بگیریم، اینجا جایی است که ما باید تأیید واقعی ۲FA را اجرا نماییم، مانند ارسال یک کد از طریق پیامک یا ایمیل.
در مرحله بعد، یک کامپوننت صفحه جدید اضافه میکنیم که به کاربران این امکان را میدهد تا کد ۲FA ارسال شده به آنها را در آن وارد کنند:
import { useState } from "react"; import { useNavigate } from "react-router-dom"; import { useAuth } from "../hooks/useAuth"; export const Verify2FA = () => { const navigate = useNavigate(); const { verify2FACode } = useAuth(); const [code, setCode] = useState(""); const handleSubmit = async (e) => { e.preventDefault(); const isValid = await verify2FACode(code); if (isValid) { navigate("/secret"); } else { alert("Invalid code. Please try again."); } }; return ( <form onSubmit={handleSubmit}> <input type= "text" value={code} onChange={(e) => setCode(e.target.value)} placeholder= "Enter verification code" /> <button type="submit">Verify</button> </form> ); };
ما همچنین باید کامپوننت ProtectedRoute
خود را بهروزرسانی کنیم تا منطق تأیید ۲FA را یکپارچه نماییم:
import { Navigate } from "react-router-dom"; import { useAuth } from "../hooks/useAuth"; export const ProtectedRoute = ({ children }) => { const { user, is2FAVerified } = useAuth(); if (!user) { return <Navigate to="/login" />; } if (!is2FAVerified) { return <Navigate to="/verify-2fa" />; } return children; }; export default ProtectedRoute;
با راهاندازی کامپوننت Verify2FA
جدید و کامپوننت ProtectedRoute
، تنظیمات مسیر App.jsx
خود را تغییر میدهیم تا تعریف مسیر verify-2fa
را شامل شود، مانند کد زیر:
import { Routes, Route } from "react-router-dom"; import { LoginPage } from "./pages/Login"; import { HomePage } from "./pages/Home"; import { Secret } from "./pages/Secret"; import { Verify2FA } from "./pages/Verify2FA"; import "./App.css"; import { ProtectedRoute } from "./components/ProtectedRoute"; import { AuthProvider } from "./hooks/useAuth"; function App() { return ( <AuthProvider> <Routes> <Route path="/" element={<HomePage />} /> <Route path="/verify-2fa" element={<Verify2FA />} /> <Route path="/login" element={<LoginPage />} /> <Route path="/secret" element={ <ProtectedRoute> <Secret /> </ProtectedRoute> } /> </Routes> </AuthProvider> ); } export default App;
پس از راهاندازی مجدد برنامه، کاربران احراز هویت نشده هنگامی که سعی میکنند به مسیر محافظت شده /secret
دسترسی پیدا کنند به صفحه ورود هدایت میشوند. همچنین، اگر وارد سیستم شدهاند اما هنوز ۲FA را نگذراندهاند، در این صورت، به صفحه تأیید ۲FA هدایت میگردند. کاربران تنها زمانی میتوانند به مسیرهای محافظت شده دسترسی داشته باشند که هر دو مرحله احراز هویت را تکمیل کرده باشند.
یکی دیگر از الگوهای رایج احراز هویت، ادغام React Router با کتابخانههای احراز هویت third-party مانند Auth0 است. این فرآیند شامل ایجاد یک حساب کاربری Auth0، بازیابی اطلاعات کاربری و استفاده از کتابخانههایی مانند auth0-react
برای اجرای یکپارچه فرآیند احراز هویت میباشد.
از آن جایی که ادغام Auth0 با React Router خارج از محدوده این مقاله است، یادگیری انجام این کار با مطالعه مستندات رسمی Auth0 میتواند بسیار مفید باشد.
<Outlet />
یکی از قدرتمندترین ویژگیهای نسخه ۶ React Router، مسیرهای تودرتو است. این ویژگی به ما این امکان را میدهد تا مسیری داشته باشیم که شامل مسیرهای child دیگری باشد. اکثر layoutهای ما با بخشهایی در URL همراه هستند، و React Router به طور کامل از آن پشتیبانی میکند.
برای مثال، میتوانیم یک کامپوننت parent <Route />
را به مسیرهای <HomePage />
و <LoginPage />
اضافه نماییم، مانند:
import { ProtectedLayout } from "./components/ProtectedLayout"; import { HomeLayout } from "./components/HomeLayout"; // ... export default function App() { return ( <Routes> <Route element={<HomeLayout />}> <Route path="/" element={<HomePage />} /> <Route path="/login" element={<LoginPage />} /> </Route> <Route path="/dashboard" element={<ProtectedLayout />}> <Route path="profile" element={<ProfilePage />} /> <Route path="settings" element={<SettingsPage />} /> </Route> </Routes> ); }
کامپوننت parent <Route />
نیز میتواند یک path داشته باشد و مسئول رندر کردن کامپوننت child <Route />
در صفحه است.
هنگامی که کاربر به /dashboard/profile
میرود، روتر <ProfilePage />
را نمایش میدهد. برای اینکه این اتفاق بیفتد، المنت route مربوط به parent باید یک کامپوننت <Outlet />
برای رندر کردن المنتهای child داشته باشد. کامپوننت Outlet
به المنتهای تودرتو در رابط کاربری این امکان را میدهد تا هنگام نمایش مسیرهای child قابل مشاهده باشند.
المنت route مربوط به parent همچنین میتواند منطق business مشترک و رابط کاربری اضافی داشته باشد. به عنوان مثال، در کامپوننت <ProtectedLayout />
، منطق مسیر خصوصی را به همراه یک navigation bar مشترک قرار دادهایم که هنگام نمایش مسیرهای child قابل مشاهده خواهد بود:
import { Navigate, Outlet } from "react-router-dom"; import { useAuth } from "../hooks/useAuth"; export const ProtectedLayout = () => { const { user } = useAuth(); if (!user) { return <Navigate to="/" />; } return ( <div> <nav> <Link to="/settings">Settings</Link> <Link to="/profile">Profile</Link> </nav> <Outlet /> </div> ) };
به جای کامپوننت <Outlet />
، میتوانیم از هوک useOutlet
نیز استفاده کنیم که همان هدف را دنبال میکند:
import { Link, Navigate, useOutlet } from "react-router-dom"; // ... export const ProtectedLayout = () => { const { user } = useAuth(); const outlet = useOutlet(); if (!user) { return <Navigate to="/" />; } return ( <div> <nav> <Link to="/settings">Settings</Link> <Link to="/profile">Profile</Link> </nav> {outlet} </div> ); };
مشابه مسیرهای محافظت شده، ما نمیخواهیم کاربران احراز هویت شده به path /login
دسترسی داشته باشند. بنابراین، برای مدیریت آن در کامپوننت <HomeLayout />
به صورت زیر عمل میکنیم:
import { Navigate, Outlet } from "react-router-dom"; import { useAuth } from "../hooks/useAuth"; export const HomeLayout = () => { const { user } = useAuth(); if (user) { return <Navigate to="/dashboard/profile" />; } return ( <div> <nav> <Link to="/">Home</Link> <Link to="/login">Login</Link> </nav> <Outlet /> </div> ) };
دسترسی به کد و دمو برنامهای که نوشتیم از طریق این لینک امکانپذیر میباشد.
پکیج React Router در نسخه ۶٫۴، روترها و data APIهای جدیدی را معرفی کرد. همه برنامههای وب برای فعال کردن دسترسی به data API باید از تابع createBrowserRouter()
استفاده کنند. سریعترین راه برای بهروزرسانی یک برنامه موجود به API جدید React Router این است که کامپوننتهای Route
را درون تابع createRoutesFromElements()
قرار دهیم:
export const router = createBrowserRouter( createRoutesFromElements( <> <Route element={<HomeLayout />}> <Route path="/" element={<HomePage />} /> <Route path="/login" element={<LoginPage />} /> </Route> <Route path="/dashboard" element={<ProtectedLayout />}> <Route path="profile" element={<ProfilePage />} /> <Route path="settings" element={<SettingsPage />} /> </Route> </> ) );
در فایل index.js
، به جای کامپوننت <BrowserRouter />
، از کامپوننت <RouterProvider />
استفاده میکنیم و آبجکت router
export شده را از فایل App.js
ارسال مینماییم. همچنین باید به این نکته توجه داشته باشیم که AuthProvider
بدون BrowserRouter
کار نخواهد کرد زیرا از تابع useNavigate()
استفاده میکند:
import { router } from "./App"; ... root.render( <StrictMode> <ThemeProvider theme={theme}> <RouterProvider router={router} /> </ThemeProvider> </StrictMode> );
برای استفاده از AuthProvider
در context روتر، باید یک کامپوننت <AuthLayout />
ایجاد کنیم که المنت outlet
را درون AuthProvider
قرار دهد. این کار تمام مسیرهای child را قادر میسازد تا به AuthContext
دسترسی داشته باشند:
import { useLoaderData, useOutlet } from "react-router-dom"; import { AuthProvider } from "../hooks/useAuth"; export const AuthLayout = () => { const outlet = useOutlet(); return ( <AuthProvider>{outlet}</AuthProvider> ); };
اکنون میتوانیم از کامپوننت AuthLayout
به عنوان یک مسیر در سطح root استفاده کنیم، به عنوان مثال:
export const router = createBrowserRouter( createRoutesFromElements( <Route element={<AuthLayout />} > <Route element={<HomeLayout />}> ... </Route> <Route path="/dashboard" element={<ProtectedLayout />}> ... </Route> </Route> ) );
در این مرحله، برنامه برای دسترسی به data APIها آماده است.
با data APIهای React Router، میتوانیم نحوه دریافت دادهها را انتزاعی کنیم. معمولاً، ما دادهها را در داخل کامپوننت خود با استفاده از هوک useEffect
لود میکنیم. در عوض، میتوانیم از تابع loader()
Router برای دریافت دادهها قبل از رندر المنت route استفاده نماییم.
مثالی را در نظر میگیریم که در آن باید هنگام لود برنامه، اطلاعات کاربری که وارد شده است را دریافت کنیم. بسته به اینکه کاربر، احراز هویت شده است یا نه میتوانیم آن را به صفحه اصلی یا داشبورد هدایت نماییم.
برای شبیه سازی دریافت دادهها، میتوانیم از Promise
با متد setTimeout()
استفاده کنیم و user
را از localStorage
دریافت نماییم:
const getUserData = () => new Promise((resolve) => setTimeout(() => { const user = window.localStorage.getItem("user"); resolve(user); }, ۳۰۰۰) );
با استفاده از prop loader
در کامپوننت Route، میتوانیم Promise
– getUserData()
– را به کامپوننت AuthLayout
با کمک تابع utility defer()
ارسال کنیم. تابع defer()
به ما این امکان را میدهد که قبل از رندر شدن کامپوننت Route
به جای مقادیر resolve شده، promiseها را ارسال کنیم:
import { Route, createBrowserRouter, createRoutesFromElements, defer } from "react-router-dom"; import { AuthLayout } from "./components/AuthLayout"; ... // ideally this would be an API call to server to get logged in user data const getUserData = () => new Promise((resolve) => setTimeout(() => { const user = window.localStorage.getItem("user"); resolve(user); }, ۳۰۰۰) ); export const router = createBrowserRouter( createRoutesFromElements( <Route element={<AuthLayout />} loader={() => defer({ userPromise: getUserData() })} > <Route element={<HomeLayout />}> ... </Route> <Route path="/dashboard" element={<ProtectedLayout />}> ... </Route> </Route> ) );
در کامپوننت AuthLayout
، میتوانیم با استفاده از هوک useLoaderData
به userPromise
دسترسی داشته باشیم.
کامپوننت Await
میتواند مقادیر معوق را با مکانیزم مدیریت خطای داخلی رندر کند. کامپوننت Await
باید داخل React Suspense قرار بگیرد تا یک رابط کاربری fallback فعال شود. در این مورد، ما یک progress bar خطی را رندر میکنیم تا زمانی که userPromise
که داریم resolve شود.
ما میتوانیم یک کامپوننت را به prop errorElement
ارسال کنیم تا در صورت rejecte شدن Promise
، بتوانیم state رابط کاربری خطا را رندر نماییم.
در نهایت، میتوانیم دادههای کاربر را به عنوان مقدار اولیه به AuthProvider
ارسال کنیم:
import { Suspense } from "react"; import { useLoaderData, useOutlet, Await } from "react-router-dom"; import LinearProgress from "@mui/material/LinearProgress"; import Alert from "@mui/material/Alert"; import { AuthProvider } from "../hooks/useAuth"; export const AuthLayout = () => { const outlet = useOutlet(); const { userPromise } = useLoaderData(); return ( <Suspense fallback={<LinearProgress />}> <Await resolve={userPromise} errorElement={<Alert severity="error">Something went wrong!</Alert>} children={(user) => ( <AuthProvider userData={user}>{outlet}</AuthProvider> )} /> </Suspense> ); };
برای تأیید شرایط خطا، میتوانیم Promise
را مطابق شکل زیر reject کنیم:
// for error const getUserData = () => new Promise((resolve, reject) => setTimeout(() => { reject("Error"); }, ۳۰۰۰) );
دسترسی به کد کامل نمونه احراز هویت ۲FA در لینک GitHub و نمونه کد ادغام data library در این لینک امکانپذیر میباشد.
در این مقاله سعی کردیم نحوه مدیریت احراز هویت کاربران با استفاده از نسخه ۶ React Router را بررسی کنیم. نسخه ۶ React Router نسبت به نسخههای قبلی پیشرفت زیادی داشته است و سریع، پایدار و قابل اعتماد میباشد. علاوه بر این که کار کردن با آن بسیار آسان است، دارای بسیاری از ویژگیهای جدید مانند <Outlet />
و یک کامپوننت بهبود یافته <Route />
است که مسیریابی را در برنامههای React بسیار سادهتر کرده است.
با routerها و data APIهای جدید موجود در نسخه ۶٫۴، میتوانیم به راحتی stateهای optimistic UI، pending و error را مدیریت کنیم. همچنین، میتوانیم در حین نمایش یک رابط کاربری fallback تا زمانی که دادهها آماده شوند، دادههای خارج از کامپوننت را abstract کرده و لود نماییم.