Вопрос или проблема
Как создать защищенный маршрут с помощью react-router-dom
и сохранить ответ в localStorage, чтобы при следующем открытии пользователь мог снова увидеть свои данные. После входа в систему их следует перенаправить на страницу панели управления.
Вся функциональность добавлена в ContextApi.
Ссылка на Codesandbox: Код
Я пытался, но не смог этого добиться
Страница маршрута
import React, { useContext } from "react";
import { globalC } from "./context";
import { Route, Switch, BrowserRouter } from "react-router-dom";
import About from "./About";
import Dashboard from "./Dashboard";
import Login from "./Login";
import PageNotFound from "./PageNotFound";
function Routes() {
const { authLogin } = useContext(globalC);
console.log("authLogin", authLogin);
return (
<BrowserRouter>
<Switch>
{authLogin ? (
<>
<Route path="/dashboard" component={Dashboard} exact />
<Route exact path="/About" component={About} />
</>
) : (
<Route path="/" component={Login} exact />
)}
<Route component={PageNotFound} />
</Switch>
</BrowserRouter>
);
}
export default Routes;
Страница контекста
import React, { Component, createContext } from "react";
import axios from "axios";
export const globalC = createContext();
export class Gprov extends Component {
state = {
authLogin: null,
authLoginerror: null
};
componentDidMount() {
var localData = JSON.parse(localStorage.getItem("loginDetail"));
if (localData) {
this.setState({
authLogin: localData
});
}
}
loginData = async () => {
let payload = {
token: "ctz43XoULrgv_0p1pvq7tA",
data: {
name: "nameFirst",
email: "internetEmail",
phone: "phoneHome",
_repeat: 300
}
};
await axios
.post(`https://app.fakejson.com/q`, payload)
.then((res) => {
if (res.status === 200) {
this.setState({
authLogin: res.data
});
localStorage.setItem("loginDetail", JSON.stringify(res.data));
}
})
.catch((err) =>
this.setState({
authLoginerror: err
})
);
};
render() {
// console.log(localStorage.getItem("loginDetail"));
return (
<globalC.Provider
value={{
...this.state,
loginData: this.loginData
}}
>
{this.props.children}
</globalC.Provider>
);
}
}
Проблема
<BrowserRouter>
<Switch>
{authLogin ? (
<>
<Route path="/dashboard" component={Dashboard} exact />
<Route exact path="/About" component={About} />
</>
) : (
<Route path="/" component={Login} exact />
)}
<Route component={PageNotFound} />
</Switch>
</BrowserRouter>
Компонент Switch
не обрабатывает отрисовку ничего, кроме компонентов Route
и Redirect
. Если вы хотите “вложить” так, то вам нужно обернуть каждый в общие маршруты, но это совершенно ненужно.
Ваш компонент входа также не обрабатывает перенаправление обратно на любую “домашнюю” страницу или частные маршруты, к которым первоначально был доступ.
Решение
react-router-dom
v6
В версии 6 пользовательские компоненты маршрутов вышли из моды, предпочтительный метод – использовать компонент макета аутентификации.
import { Navigate, Outlet } from 'react-router-dom';
const PrivateRoutes = () => {
const location = useLocation();
const { authLogin } = useContext(globalC);
if (authLogin === undefined) {
return null; // или индикатор загрузки/спиннер/и т.д.
}
return authLogin
? <Outlet />
: <Navigate to="/login" replace state={{ from: location }} />;
}
…
<BrowserRouter>
<Routes>
<Route path="/" element={<PrivateRoutes />} >
<Route path="dashboard" element={<Dashboard />} />
<Route path="about" element={<About />} />
</Route>
<Route path="/login" element={<Login />} />
<Route path="*" element={<PageNotFound />} />
</Routes>
</BrowserRouter>
или
const routes = [
{
path: "/",
element: <PrivateRoutes />,
children: [
{
path: "dashboard",
element: <Dashboard />,
},
{
path: "about",
element: <About />
},
],
},
{
path: "/login",
element: <Login />,
},
{
path: "*",
element: <PageNotFound />
},
];
…
export default function Login() {
const location = useLocation();
const navigate = useNavigate();
const { authLogin, loginData } = useContext(globalC);
useEffect(() => {
if (authLogin) {
const { from } = location.state || { from: { pathname: "/" } };
navigate(from, { replace: true });
}
}, [authLogin, location, navigate]);
return (
<div
style={{ height: "100vh" }}
className="d-flex justify-content-center align-items-center"
>
<button type="button" onClick={loginData} className="btn btn-primary">
Вход
</button>
</div>
);
}
react-router-dom
v5
Создайте компонент PrivateRoute
, который использует ваш контекст аутентификации.
const PrivateRoute = (props) => {
const location = useLocation();
const { authLogin } = useContext(globalC);
if (authLogin === undefined) {
return null; // или индикатор загрузки/спиннер/и т.д.
}
return authLogin ? (
<Route {...props} />
) : (
<Redirect
to={{
pathname: "/login",
state: { from: location }
}}
/>
);
};
Обновите ваш компонент Login
, чтобы обработать перенаправление обратно к исходному маршруту.
export default function Login() {
const location = useLocation();
const history = useHistory();
const { authLogin, loginData } = useContext(globalC);
useEffect(() => {
if (authLogin) {
const { from } = location.state || { from: { pathname: "/" } };
history.replace(from);
}
}, [authLogin, history, location]);
return (
<div
style={{ height: "100vh" }}
className="d-flex justify-content-center align-items-center"
>
<button type="button" onClick={loginData} className="btn btn-primary">
Вход
</button>
</div>
);
}
Отрисуйте все ваши маршруты в “плоском списке”
function Routes() {
return (
<BrowserRouter>
<Switch>
<PrivateRoute path="/dashboard" component={Dashboard} />
<PrivateRoute path="/About" component={About} />
<Route path="/login" component={Login} />
<Route component={PageNotFound} />
</Switch>
</BrowserRouter>
);
}
Для v6:
import { Routes, Route, Navigate } from "react-router-dom";
function App() {
return (
<Routes>
<Route path="/public" element={<PublicPage />} />
<Route
path="/protected"
element={
<RequireAuth redirectTo="/login">
<ProtectedPage />
</RequireAuth>
}
/>
</Routes>
);
}
function RequireAuth({ children, redirectTo }) {
let isAuthenticated = getAuth();
return isAuthenticated ? children : <Navigate to={redirectTo} />;
}
Ссылка на документацию:
https://gist.github.com/mjackson/d54b40a094277b7afdd6b81f51a0393f
Этот ответ для новых пользователей Reactjs
React Router Dom: ^ v6
//App.js
<Route path="/"
element={<HomePage /> } />
<Route path="/dashboard"
element={<AuthMiddleware> <DashboardPage /> </AuthMiddleware>}
/>
// AuthMiddelware.js
import { Navigate } from "react-router-dom"
export const AuthMiddleware = (props) => {
const token = true; // получить статус входа здесь
const { auth=token, children, redirect="/login" } = props;
if (!auth) {
return <Navigate to={redirect} replace />;
}
return children;
};
Если вы хотите простое решение, используйте компонент входа в App.js, если пользователь вошел в систему, установите переменную пользователя. Если переменная пользователя установлена, то начинайте эти маршруты, иначе он останется на странице входа. Я реализовал это в своем проекте.
return (
<div>
<Notification notification={notification} type={notificationType} />
{
user === null &&
<LoginForm startLogin={handleLogin} />
}
{
user !== null &&
<NavBar user={user} setUser={setUser} />
}
{
user !== null &&
<Router>
<Routes>
<Route exact path="/" element={<Home />} />
<Route exact path="/adduser" element={<AddUser />} /> />
<Route exact path="/viewuser/:id" element={<ViewUser />} />
</Routes>
</Router>
}
</div>
)
import { BrowserRouter as Router } from "react-router-dom";
import { Routes, Route } from "react-router-dom";
import Home from "./component/home/Home.js";
import Products from "./component/Products/Products.js";
import Profile from "./component/user/Profile.js";
import ProtectedRoute from "./component/Route/ProtectedRoute";
function App() {
return (
<Router>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/products" element={<Products />} />
<Route
path="/account"
element={
<ProtectedRoute >
<Profile />
</ProtectedRoute>
}
/>
</Routes>
</Router>
);
}
//ProtectedRoute
export default App;
import React from "react";
import { useSelector } from "react-redux";
import { Navigate } from "react-router-dom";
function ProtectedRoute({ children }) {
const { isAuthecated, loading } = useSelector((state) => state.user);
if (!isAuthecated) {
return <Navigate to="/login" replace />;
}
return children;
}
export default ProtectedRoute;
import { v4 as uuidv4 } from "uuid";
const routes = [
{
id: uuidv4(),
isProtected: false,
exact: true,
path: "/home",
component: param => <Overview {...param} />,
},
{
id: uuidv4(),
isProtected: true,
exact: true,
path: "/protected",
component: param => <Overview {...param} />,
allowed: [...advanceProducts], // подписка
},
{
// если вы используете условный рендеринг для того же пути
id: uuidv4(),
isProtected: true,
exact: true,
path: "https://stackoverflow.com/",
component: null,
conditionalComponent: true,
allowed: {
[subscription1]: param => <Overview {...param} />,
[subscription2]: param => <Customers {...param} />,
},
},
]
// Компонент навигации
import React, { useEffect, useState } from "react";
import { useSelector } from "react-redux";
import { Switch, Route, useLocation } from "react-router-dom";
// ...логика компонента
<Switch>
{routes.map(params => {
return (
<ProtectedRoutes
exact
routeParams={params}
key={params.path}
path={params.path}
/>
);
})}
<Route
render={() => {
props.setHideNav(true);
setHideHeader(true);
return <ErrorPage type={404} />;
}}
/>
</Switch>
// Компонент ProtectedRoute
import React from "react";
import { Route } from "react-router-dom";
import { useSelector } from "react-redux";
const ProtectedRoutes = props => {
const { routeParams } = props;
const currentSubscription = 'xyz'; // ваша текущая подписка;
if (routeParams.conditionalComponent) {
return (
<Route
key={routeParams.path}
path={routeParams.path}
render={routeParams.allowed[currentSubscription]}
/>
);
}
if (routeParams.isProtected && routeParams.allowed.includes(currentSubscription)) {
return (
<Route key={routeParams.path} path={routeParams.path} render={routeParams?.component} />
);
}
if (!routeParams.isProtected) {
return (
<Route key={routeParams.path} path={routeParams.path} render={routeParams?.component} />
);
}
return null;
};
export default ProtectedRoutes;
Хотелось бы отметить, что никогда не забывайте передавать путь как пропс в ProtectedRoute, иначе он не будет работать.
</div><div class="s-prose js-post-body" itemprop="text">
Вот простой защищенный маршрут react-router v6. Я поместил все маршруты, которые хочу защитить, в routes.js:-
const routes = [{ path: "/dasboard", name:"Dashboard", element: <Dashboard/> }]
Чтобы отрендерить маршруты, просто отображайте их следующим образом: –
<Routes>
{routes.map((routes, id) => {
return(
<Route
key={id}
path={route.path}
exact={route.exact}
name={route.name}
element={
localStorage.getItem("token") ? (
route.element
) : (
<Navigate to="/login" />
)
}
)
})
}
</Routes>
</div>
Ответ или решение
Создание защищенного маршрута в приложении React с использованием react-router-dom
является важной частью разработки для обеспечения безопасности пользовательских данных. В данном ответе мы рассмотрим, как это реализовать, сохраняя информацию о пользователе в localStorage
, чтобы они могли видеть свои данные при повторном входе, а также автоматическую переадресацию на страницу панели управления после успешной аутентификации. Все функции будут реализованы в контексте Context API.
Шаг 1: Настройка контекста
Сначала создадим контекст для аутентификации. Этот контекст будет управлять состоянием аутентификации и хранить данные пользователя в localStorage
.
import React, { Component, createContext } from "react";
import axios from "axios";
export const AuthContext = createContext();
export class AuthProvider extends Component {
state = {
authLogin: null,
authLoginError: null
};
componentDidMount() {
const localData = JSON.parse(localStorage.getItem("loginDetail"));
if (localData) {
this.setState({ authLogin: localData });
}
}
loginData = async () => {
// Пример загрузки данных для аутентификации
const payload = {
token: "ctz43XoULrgv_0p1pvq7tA",
data: {
name: "nameFirst",
email: "internetEmail",
phone: "phoneHome"
}
};
try {
const res = await axios.post(`https://app.fakejson.com/q`, payload);
if (res.status === 200) {
this.setState({ authLogin: res.data });
localStorage.setItem("loginDetail", JSON.stringify(res.data));
}
} catch (error) {
this.setState({ authLoginError: error });
}
};
render() {
return (
<AuthContext.Provider value={{
...this.state,
loginData: this.loginData
}}>
{this.props.children}
</AuthContext.Provider>
);
}
}
Шаг 2: Создание защищенного маршрута
Теперь мы создадим компонент защищенного маршрута, который будет проверять, авторизован ли пользователь, и перенаправлять его на страницу входа, если нет.
import React, { useContext } from "react";
import { Navigate } from "react-router-dom";
import { AuthContext } from "./context";
const ProtectedRoute = ({ children }) => {
const { authLogin } = useContext(AuthContext);
if (authLogin === null) {
return null; // или индикатор загрузки
}
return authLogin ? children : <Navigate to="/login" replace />;
};
export default ProtectedRoute;
Шаг 3: Настройка маршрутов
Теперь мы настроим маршруты в нашем приложении. Мы будем использовать Switch
или Routes
(в зависимости от версии react-router-dom
, которую вы используете) и добавим защищенные маршруты.
import React, { useContext } from "react";
import { BrowserRouter as Router, Route, Routes } from "react-router-dom";
import { AuthContext } from "./context";
import About from "./About";
import Dashboard from "./Dashboard";
import Login from "./Login";
function AppRoutes() {
const { authLogin } = useContext(AuthContext);
return (
<Router>
<Routes>
<Route path="/login" element={<Login />} />
<Route path="/" element={<ProtectedRoute><Dashboard /></ProtectedRoute>} />
<Route path="/about" element={<ProtectedRoute><About /></ProtectedRoute>} />
{/* Добавьте дополнительные защищенные маршруты здесь */}
</Routes>
</Router>
);
}
export default AppRoutes;
Шаг 4: Компонент входа
В компоненте Login
мы будем управлять процессом входа пользователя и перенаправлением его на нужную страницу после успешной аутентификации.
import React, { useEffect, useContext } from "react";
import { useLocation, useNavigate } from "react-router-dom";
import { AuthContext } from "./context";
const Login = () => {
const navigate = useNavigate();
const location = useLocation();
const { authLogin, loginData } = useContext(AuthContext);
useEffect(() => {
if (authLogin) {
const { from } = location.state || { from: { pathname: "/" } };
navigate(from, { replace: true });
}
}, [authLogin, location, navigate]);
return (
<div style={{ height: "100vh" }} className="d-flex justify-content-center align-items-center">
<button type="button" onClick={loginData} className="btn btn-primary">
Войти
</button>
</div>
);
};
export default Login;
Заключение
Теперь ваше приложение имеет защищенные маршруты, и пользователи могут войти в систему и видеть свои данные на соответствующих страницах. Убедитесь, что вы импортировали все необходимые компоненты и добавили их в ваше главное приложение.
Этот подход позволяет эффективно управлять аутентификацией пользователей и защищать маршруты, используя Context API и react-router-dom
. Не забывайте также тестировать функциональность, чтобы убедиться, что все работает как задумано.