#reactjs #redirect #redux #state #refresh
#reactjs #перенаправление #redux #состояние #обновить
Вопрос:
Я создаю серверное приложение администратора, и оно не будет оставаться на правильном маршруте / странице при обновлении браузера.
Когда пользователь входит в систему, он перенаправляет на /dashboard, обновляет состояние с помощью информации о пользователе и токена доступа, устанавливает токен обновления на сервере в файле cookie httponly.
Когда токен доступа истекает и маршрут получает значение 401, он вызывает токен проверки обновления и получает новый токен доступа и обновляет состояние. Все это отлично работает, за исключением обновления.
Я полагаю, что это может быть как-то связано с HashRouter, может быть? Не уверена, но я выдергиваю волосы, потому что уверена, что мне не хватает чего-то маленького.
При посещении страницы я создал приватный маршрут для всего трафика, отличного от /login .
App.js
import React, {Component} from 'react';
import {HashRouter, Route, Switch} from 'react-router-dom';
import PrivateRoute from "./components/PrivateRoute";
import './scss/style.scss';
const loading = (
<div className="pt-3 text-center">
<div className="sk-spinner sk-spinner-pulse"></div>
</div>
)
// Pages
const Login = React.lazy(() => import('./views/pages/login/Login'));
// Containers
const TheLayout = React.lazy(() => import('./containers/TheLayout'));
class App extends Component {
render() {
return (
<HashRouter>
<React.Suspense fallback={loading}>
<Switch>
<Route exact path="/login" name="Login" render={props => <Login {...props}/>}/>
<PrivateRoute path="/" name="Home" render={props => <TheLayout {...props}/>}/>
</Switch>
</React.Suspense>
</HashRouter>
);
}
}
export default App;
В PrivateRoute я помещаю начальную проверку, есть ли токен и состояние isLoggedIn, если нет, он отправляет мою функцию токена обновления для обновления состояния, которая также функционирует должным образом, но все равно перенаправляет на / login .
PrivateRoute.js
import React from 'react';
import {Route, Redirect} from 'react-router-dom';
import {useDispatch, useSelector} from "react-redux";
import axios from "axios";
import {eraseToken, getRefreshedToken} from "../store/auth";
import store from "../store";
axios.defaults.withCredentials = true;
axios.interceptors.request.use(req => {
console.log(req)
const token = store.getState().auth.user.token;
if (token) {
req.headers.Authorization = 'Bearer ' token;
} else {
//req.headers.Authorization = null;
}
return req;
});
axios.interceptors.response.use(res => {
return res;
}, async (error) => {
if (401 === error.response.status) {
if ('/refresh' !== error.response.config.url) {
await store.dispatch(getRefreshedToken());
} else {
store.dispatch(eraseToken());
}
} else {
return Promise.reject(error);
}
}
);
const PrivateRoute = ({render: Component, ...rest}) => {
const dispatch = useDispatch();
let token = useSelector(state => state.auth.user.token);
let isLoggedIn = useSelector(state => state.auth.isLoggedIn);
console.log(token, isLoggedIn);
if (!isLoggedIn || !token) {
dispatch(getRefreshedToken());
}
// check again after state update
token = useSelector(state => state.auth.user.token);
isLoggedIn = useSelector(state => state.auth.isLoggedIn);
return (
<Route {...rest} render={props => (
(token amp;amp; isLoggedIn)
? <Component {...props} />
: <Redirect to={{pathname: '/login', state: {from: props.location}}}/>
)}/>
)
}
export default PrivateRoute;
This is where I’m confused. Once I log in I get redirected fine to /dashboard because token etc exists in state, however if I refresh the page while at /dashboard it calls dispatch(getRefreshedToken()); which works fine, and updates the state with the correct values, which should then just render the component (I assumed) instead of redirecting back to /login.
If I manually change the url and replace /login with /dashboard it views the dashboard fine still as the state exists properly.
The PrivateRoute renders a component called TheLayout, which renders a component called TheContent which maps routes to views to display.
routes.js
const routes = [
{ path: '/', exact: true, name: 'Home' },
{ path: '/dashboard', name: 'Dashboard', component: Dashboard },
{ path: '/users', name: 'Users', component: Users }
]
export default routes;
TheContent.js
import React, {Suspense} from 'react';
import {Redirect, Route, Switch} from 'react-router-dom';
import {CContainer, CFade} from '@coreui/react';
// routes config
import routes from '../routes';
const loading = (
<div className="pt-3 text-center">
<div className="sk-spinner sk-spinner-pulse"></div>
</div>
)
const TheContent = () => {
return (
<main className="c-main">
<CContainer fluid>
<Suspense fallback={loading}>
<Switch>
{routes.map((route, idx) => {
return route.component amp;amp; (
<Route
key={idx}
path={route.path}
exact={route.exact}
name={route.name}
render={props => (
<CFade>
<route.component {...props} />
</CFade>
)}/>
)
})}
<Redirect from="/" to="/dashboard"/>
</Switch>
</Suspense>
</CContainer>
</main>
)
}
export default React.memo(TheContent);
Не уверен, что это может иметь какое-то отношение к этому?
Обновить
Я понял, что код не ожидал отправки для завершения обновления состояния, поэтому я изменил PrivateRoute, чтобы вместо этого использовать useEffect с зависимостями, и заставил его работать так, как мне нужно.
Однако, пытаясь лучше понять useEffect, он запускает мое обновление и вызывает сервер несколько раз после истечения срока действия моего файла cookie. Есть ли какой-нибудь способ предотвратить это? Вот мой обновленный частный маршрут.
const PrivateRoute = ({render: Component, ...rest}) => {
const dispatch = useDispatch();
const history = useHistory();
let token = useSelector(state => state.auth.user.token);
let isLoggedIn = useSelector(state => state.auth.isLoggedIn);
let isRefreshingToken = useSelector(state => state.auth.isRefreshingToken);
useEffect(() => {
if (!isLoggedIn amp;amp; !token amp;amp; !isRefreshingToken) {
(async () => {
await dispatch(getRefreshedToken())
.catch(err => {
history.push('/login');
});
})();
}
}, [token, isLoggedIn, isRefreshingToken, dispatch, history])
return (
<Route {...rest} render={props => (
<Component {...props} />
)}/>
)
}