Protected Route — faqat login qilganlar uchun

~20 daq · React · Routing

Muammodan boshlaylik

Siz bank ilovasini yozyapsiz. /dashboard, /hisoblar, /o'tkazmalar sahifalari faqat login qilgan foydalanuvchilar uchun.

Hozirgi holat: /dashboard manzilini brauzerga yozgan har kim — login qilmagan bo'lsa ham — sahifani ko'ra oladi.

Bu jiddiy muammo. Har bir sahifa komponentiga tekshiruv qo'yish ham yaxshi emas — kod takrorlanadi va biror sahifada unutib qolish mumkin.

Yechim: bitta ProtectedRoute komponenti — u login holatini tekshiradi. Login qilingan bo'lsa o'tkazib yuboradi, bo'lmasa /kirish sahifasiga yo'naltiradi. Barcha himoyalangan sahifalar shu komponent orqali o'tadi.


Protected Route qanday ishlaydi?

ProtectedRoute — oddiy komponent. U ikkita narsa qiladi:

  1. Foydalanuvchi login qilganmi — tekshiradi
  2. Ha — <Outlet /> (bola Route) ni ko'rsatadi
  3. Yo'q — /kirish sahifasiga yo'naltiradi

Hayotiy o'xshatma: ofis kiraverishdagi qo'riqchi. Xodimlar kartasini ko'rsatsangiz — o'tkazib yuboradi. Ko'rsatmasangiz — "Iltimos, ro'yxatdan o'ting" deydi. Har bir xonaga alohida qo'riqchi qo'yishning hojati yo'q — bitta kiraverishda tekshiruv.


Eng oddiy variant

// components/ProtectedRoute.jsx
import { Navigate, Outlet } from 'react-router-dom';

function ProtectedRoute({ foydalanuvchi }) {
  if (!foydalanuvchi) {
    return <Navigate to="/kirish" replace />;
  }

  return <Outlet />;
}

export default ProtectedRoute;

<Navigate to="/kirish" replace /> — foydalanuvchini /kirish sahifasiga yo'naltiradi. replace — tarixga qo'shmaslik uchun (login dan keyin "orqaga" tugmasi himoyalangan sahifaga qaytmasin).


App.jsx da ishlatish

// App.jsx
import { Routes, Route } from 'react-router-dom';
import ProtectedRoute from './components/ProtectedRoute';
import { useAuth } from './hooks/useAuth';

import Bosh from './pages/Bosh';
import Kirish from './pages/Kirish';
import Dashboard from './pages/Dashboard';
import Profil from './pages/Profil';
import Sozlamalar from './pages/Sozlamalar';
import TopilmadI from './pages/TopilmadI';

function App() {
  const { foydalanuvchi } = useAuth();

  return (
    <Routes>
      {/* Ochiq sahifalar — hamma ko'ra oladi */}
      <Route path="/" element={<Bosh />} />
      <Route path="/kirish" element={<Kirish />} />

      {/* Himoyalangan sahifalar — faqat login qilganlar */}
      <Route element={<ProtectedRoute foydalanuvchi={foydalanuvchi} />}>
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/profil" element={<Profil />} />
        <Route path="/sozlamalar" element={<Sozlamalar />} />
      </Route>

      <Route path="*" element={<TopilmadI />} />
    </Routes>
  );
}

ProtectedRoute — path siz Route. U faqat wrapper vazifasini o'taydi: tekshiradi va <Outlet /> orqali bola Route larni chiqaradi.


useAuth — autentifikatsiya hook i

Amaliy loyihalarda foydalanuvchi holati global state da saqlanadi. Oddiy useAuth hook misoli:

// hooks/useAuth.js
import { createContext, useContext, useState } from 'react';

const AuthContext = createContext(null);

export function AuthProvider({ children }) {
  const [foydalanuvchi, setFoydalanuvchi] = useState(
    () => JSON.parse(localStorage.getItem('foydalanuvchi')) || null
  );

  function kirish(malumot) {
    setFoydalanuvchi(malumot);
    localStorage.setItem('foydalanuvchi', JSON.stringify(malumot));
  }

  function chiqish() {
    setFoydalanuvchi(null);
    localStorage.removeItem('foydalanuvchi');
  }

  return (
    <AuthContext.Provider value={{ foydalanuvchi, kirish, chiqish }}>
      {children}
    </AuthContext.Provider>
  );
}

export function useAuth() {
  return useContext(AuthContext);
}
// main.jsx
import { AuthProvider } from './hooks/useAuth';

<BrowserRouter>
  <AuthProvider>
    <App />
  </AuthProvider>
</BrowserRouter>

Endi useAuth ni ilovaning istalgan joyida chaqirish mumkin.


ProtectedRoute — useAuth bilan

foydalanuvchi ni prop orqali emas, hook orqali olsa — App.jsx da prop uzatish shart emas:

// components/ProtectedRoute.jsx
import { Navigate, Outlet } from 'react-router-dom';
import { useAuth } from '../hooks/useAuth';

function ProtectedRoute() {
  const { foydalanuvchi } = useAuth(); // ← prop emas, hook

  if (!foydalanuvchi) {
    return <Navigate to="/kirish" replace />;
  }

  return <Outlet />;
}

export default ProtectedRoute;
// App.jsx — prop uzatish shart emas
<Route element={<ProtectedRoute />}>
  <Route path="/dashboard" element={<Dashboard />} />
  <Route path="/profil" element={<Profil />} />
</Route>

Qaytish manzilini saqlash

Foydalanuvchi /dashboard ga kirmoqchi bo'ldi — login sahifasiga yo'naltirildi. Login qilgandan keyin — /dashboard ga qaytishi kerak, /bosh-sahifa ga emas.

Buning uchun state orqali qaytish manzilini uzatamiz:

// components/ProtectedRoute.jsx
import { Navigate, Outlet, useLocation } from 'react-router-dom';
import { useAuth } from '../hooks/useAuth';

function ProtectedRoute() {
  const { foydalanuvchi } = useAuth();
  const location = useLocation(); // ← joriy manzil

  if (!foydalanuvchi) {
    return (
      <Navigate
        to="/kirish"
        state={{ from: location.pathname }} // ← qaytish manzili saqlanadi
        replace
      />
    );
  }

  return <Outlet />;
}

export default ProtectedRoute;
// pages/Kirish.jsx
import { useNavigate, useLocation } from 'react-router-dom';
import { useAuth } from '../hooks/useAuth';

function Kirish() {
  const { kirish } = useAuth();
  const navigate = useNavigate();
  const location = useLocation();

  const qaytishManzili = location.state?.from || '/dashboard'; // ← qaytish manzili

  async function yuborish(e) {
    e.preventDefault();
    // ... server bilan ishlash ...
    await kirish({ ism: 'Jasur', email: 'jasur@misol.uz' });

    navigate(qaytishManzili, { replace: true }); // ← o'sha sahifaga qaytish
  }

  return (
    <form onSubmit={yuborish}>
      <input name="email" type="email" placeholder="Email" />
      <input name="parol" type="password" placeholder="Parol" />
      <button type="submit">Kirish</button>
    </form>
  );
}

export default Kirish;

Rol asosida himoya — admin sahifalari

Ba'zi sahifalar faqat admin uchun. ProtectedRoute ga rol tekshiruvi qo'shish mumkin:

// components/ProtectedRoute.jsx
import { Navigate, Outlet, useLocation } from 'react-router-dom';
import { useAuth } from '../hooks/useAuth';

function ProtectedRoute({ kerakliRol }) {
  const { foydalanuvchi } = useAuth();
  const location = useLocation();

  // Login qilinmaganmi?
  if (!foydalanuvchi) {
    return <Navigate to="/kirish" state={{ from: location.pathname }} replace />;
  }

  // Kerakli rol bormi?
  if (kerakliRol && foydalanuvchi.rol !== kerakliRol) {
    return <Navigate to="/ruxsat-yoq" replace />;
  }

  return <Outlet />;
}

export default ProtectedRoute;
// App.jsx
<Routes>
  <Route path="/" element={<Bosh />} />
  <Route path="/kirish" element={<Kirish />} />
  <Route path="/ruxsat-yoq" element={<RuxsatYoq />} />

  {/* Barcha login qilganlar uchun */}
  <Route element={<ProtectedRoute />}>
    <Route path="/dashboard" element={<Dashboard />} />
    <Route path="/profil" element={<Profil />} />
  </Route>

  {/* Faqat adminlar uchun */}
  <Route element={<ProtectedRoute kerakliRol="admin" />}>
    <Route path="/admin" element={<AdminPanel />} />
    <Route path="/admin/foydalanuvchilar" element={<FoydalanuvchilarBoshqaruv />} />
  </Route>

  <Route path="*" element={<TopilmadI />} />
</Routes>

Yuklanish holati — loading

Sahifa birinchi yuklanayotganda foydalanuvchi holati aniqlanmagan bo'lishi mumkin (masalan, token tekshirilmoqda). Bu vaqtda <Navigate> chaqirilsa — noto'g'ri yo'naltirish bo'ladi.

// components/ProtectedRoute.jsx
import { Navigate, Outlet, useLocation } from 'react-router-dom';
import { useAuth } from '../hooks/useAuth';

function ProtectedRoute() {
  const { foydalanuvchi, yuklanmoqda } = useAuth();
  const location = useLocation();

  if (yuklanmoqda) {
    return <div>Yuklanmoqda...</div>; // ← token tekshirilayotganda kuting
  }

  if (!foydalanuvchi) {
    return <Navigate to="/kirish" state={{ from: location.pathname }} replace />;
  }

  return <Outlet />;
}
// hooks/useAuth.js — yuklanmoqda holati bilan
export function AuthProvider({ children }) {
  const [foydalanuvchi, setFoydalanuvchi] = useState(null);
  const [yuklanmoqda, setYuklanmoqda] = useState(true); // ← boshida true

  useEffect(() => {
    // Token tekshirish — server so'rovi
    tokenniTekshir()
      .then((mal) => setFoydalanuvchi(mal))
      .catch(() => setFoydalanuvchi(null))
      .finally(() => setYuklanmoqda(false)); // ← tekshiruv tugadi
  }, []);

  return (
    <AuthContext.Provider value={{ foydalanuvchi, yuklanmoqda }}>
      {children}
    </AuthContext.Provider>
  );
}

Tez-tez uchraydigan xatolar

1. replace ni unutish

// ❌ Xato — tarixga qo'shiladi
<Navigate to="/kirish" />

// ✅ To'g'ri — tarix almashtiriladi
<Navigate to="/kirish" replace />

Sababi: replace siz foydalanuvchi login qilgandan keyin "orqaga" bossa — himoyalangan sahifaga qaytib, yana /kirish ga yo'naltiriladi. Bu cheksiz aylanaga olib keladi. replace bilan login sahifasi tarixdan o'chirilib, bunday muammo bo'lmaydi.


2. Yuklanish holatini hisobga olmaslik

// ❌ Xato — yuklanmoqda vaqtida foydalanuvchi null
function ProtectedRoute() {
  const { foydalanuvchi } = useAuth();
  // yuklanmoqda paytida foydalanuvchi === null
  // Shuning uchun darhol /kirish ga yo'naltiriladi — noto'g'ri!

  if (!foydalanuvchi) {
    return <Navigate to="/kirish" replace />;
  }
  return <Outlet />;
}

// ✅ To'g'ri — yuklanmoqda tekshiriladi
function ProtectedRoute() {
  const { foydalanuvchi, yuklanmoqda } = useAuth();

  if (yuklanmoqda) return <div>Yuklanmoqda...</div>;
  if (!foydalanuvchi) return <Navigate to="/kirish" replace />;
  return <Outlet />;
}

Sababi: Token tekshiruvi asenkron. Sahifa birinchi yuklanganda foydalanuvchi hali null — tekshiruv tugamagan. Bu paytda <Navigate> chaqirilsa, login qilgan foydalanuvchi ham /kirish ga yo'naltiriladi.


3. Har sahifaga alohida tekshiruv yozish

// ❌ Xato — har sahifada takrorlanmoqda
function Dashboard() {
  const { foydalanuvchi } = useAuth();
  if (!foydalanuvchi) return <Navigate to="/kirish" replace />;
  return <div>Dashboard</div>;
}

function Profil() {
  const { foydalanuvchi } = useAuth();
  if (!foydalanuvchi) return <Navigate to="/kirish" replace />;
  return <div>Profil</div>;
}
// ✅ To'g'ri — bir joyda ProtectedRoute
<Route element={<ProtectedRoute />}>
  <Route path="/dashboard" element={<Dashboard />} />
  <Route path="/profil" element={<Profil />} />
</Route>

Sababi: Tekshiruvni har sahifada yozish — kod takrorlanadi va xavfli. Biror sahifada unutib qolish mumkin. Markaziy ProtectedRoute bilan — bir joyda o'zgartirsangiz hamma joyga ta'sir qiladi.


Xulosa

  • ProtectedRoute — login holatini tekshiruvchi wrapper komponent
  • Login qilinmagan → <Navigate to="/kirish" replace />
  • Login qilingan → <Outlet /> (bola Route ko'rsatiladi)
  • state={{ from: location.pathname }} — qaytish manzilini saqlash
  • yuklanmoqda holatini tekshirish — token asenkron tekshirilganda
  • Rol asosida himoya — kerakliRol prop bilan kengaytirish
  • Tekshiruvni har sahifada emas, markaziy ProtectedRoute da yozing

Keyingi dars: 47-dars: Lazy Loading — Route larni bo'laklash, Suspense bilan