Ожидает вызовов API перед отображением частного маршрута

#javascript #reactjs #react-hooks

#javascript #reactjs #реагирующие крючки

Вопрос:

У меня есть защищенные маршруты, для которых при передаче аутентификации включено значение true или false, чтобы разрешить пользователям доступ к странице аутентификации. При каждой загрузке страницы он вызывает API токена аутентификации и проверяет правильность. Если токен недействителен, перенаправьте на страницу входа.

ProtectedRoute.js

 const ProtectedRoute = ({isEnabled, ...props}) => {
    return (isEnabled) ? <Route {...props} /> : <Redirect to="/login"/>;
};

export default ProtectedRoute;
  

Routes.js

 import {withRouter, Switch, Route } from "react-router-dom";

export default withRouter(({ location }) => {
  const [isAuth, setIsAuth] = useState(false)
  useLayoutEffect(() => {
    (async() => {
      if(accessToken){
        let res = await ValidateLoginToken(accessToken)
        if (res amp;amp; res.data amp;amp; res.data.status === 200){
          setIsAuth(res.data.valid)
        } else setIsAuth(false);
      } else setIsAuth(false)
    })()
   },[isAuth])

  return (
    <Switch>
        <ProtectedRoute path="/dashboard" component={Dashboard} isEnabled={isAuth} />
        <Route path="/" component={Login} />
    </Switch>
  )
}
  

App.js

 const history = createBrowserHistory();
function App() {
  return (
    <Router history={history}>
      <Routes/>
    </Router>
  )
}

export default App;
  

Dashboard.js

 export const Dashboard = () => {
  return (
   <div class="ui form-user center raised padded segment">
      <a href="/section1">
        <div id="section1" class="ui floated right basic red button">
           Directory #1
        </div>
      </a>
   </div>
  )
}
  

Проблема

Когда авторизованные пользователи просматривают страницу аутентификации (панель мониторинга), перенаправляется на страницу входа. Причина в том, что verify token API возвращается после того, как ProtectedRoute отображается как isAuth, равный false.

Ответ №1:

Вы можете использовать другую переменную состояния для ожидания выполнения api перед инициализацией маршрутов.

 const [isAuth, setIsAuth] = useState(false)
const [checked, setChecked] = useState(false)
  useLayoutEffect(() => {
    (async() => {
      if(accessToken){
        let res = await ValidateLoginToken(accessToken)
        if (res amp;amp; res.data amp;amp; res.data.status === 200){
          setIsAuth(res.data.valid)
        } else setIsAuth(false);
      } else setIsAuth(false)
    setChecked(true)
})()
   },[isAuth])
  

Затем в Routes вы можете сделать что-то вроде:

  <Switch>
        {
        !checked?(<React.Fragment/>):!isAuth?(<Route path="/" component={Login} />):(<ProtectedRoute path="/dashboard" component={Dashboard} loginState={loginState} />)
        }
 </Switch> 
  

Обычно я разделяю сеансовые маршруты и маршруты без сеансов в отдельных перехватах, и это работает нормально.

Ответ №2:

Вместо isAuth логического значения вы используете строку или число для увеличения числа состояний. Например, переименовать isAuth в loginState , который может быть "pending" , "authenticated" или "unauthenticated" . Затем используйте "pending" в качестве начального состояния и добавьте дополнительный сценарий. Вы могли бы, например, вернуться null , чтобы ничего не отображать, отображать вращающийся круг и т.д.

Вот пример рендеринга null (ничего) во время аутентификации токена входа:

ProtectedRoute.js

 const loginRoutes = {
  pending:         (        ) => null,
  authenticated:   (...props) => <Route {...props} />,
  unauthenticated: (        ) => <Redirect to="/login"/>,
};

const ProtectedRoute = ({loginState = "pending", ...props}) => {
  const LoginRoute = loginRoutes[loginState];
  return <LoginRoute {...props} />;
};

export default ProtectedRoute;
  

Route.js

 import { withRouter, Switch, Route } from "react-router-dom";

export default withRouter(({ location }) => {
  const [loginState, setLoginState] = useState("pending")
  useLayoutEffect(() => {
    (async() => {
      if(accessToken){
        let res = await ValidateLoginToken(accessToken)
        if (res amp;amp; res.data amp;amp; res.data.status === 200) {
          setLoginState(res.data.valid ? "authenticate" : "unauthenticated")
        } else setLoginState("unauthenticated");
      } else setLoginState("unauthenticated")
    })()
   }, [loginState])

  return (
    <Switch>
      <ProtectedRoute path="/dashboard" component={Dashboard} loginState={loginState} />
      <Route path="/" component={Login} />
    </Switch>
  )
}
  

В качестве оговорки, у меня нет опыта работы с React Router, поэтому я сохранил пример как можно ближе к оригиналу.

Комментарии:

1. Реакция возвращает ошибку, когда ProtectedRoute возвращает null. return (LoginRoute) ? <LoginRoute {...props} /> : <></>; будет выполняться.

2. @Abel В чем ошибка? Я подозреваю, что вы вызываете ProtectedRoute без loginState где-либо в своем коде. В этом случае используйте prop по умолчанию для компонента. Я обновил ответ.

Ответ №3:

Вы могли бы использовать начальное состояние как null для различения различных состояний вашего компонента.

  • null -> токен API еще не вызван.
  • true -> токен проверен
  • ошибка false -> ошибка проверки токена.
 export default function ProtectedRoute(props) {
  const [isAuth, setIsAuth] = React.useState(false)
  const history = useHistory();

  React.useEffect(() => {
    async function validateToken() {
      if(accessToken){
        let res = await ValidateLoginToken(accessToken)
        if (res amp;amp; res.data amp;amp; res.data.status === 200){
          setIsAuth(true)
          return;
        }
      } 

      history.push('/login')
    } 
    
    validateToken();
   }, [isAuth]);

   if (isAuth === null) return null;

   return props.children;
}

export default function App() {
  return (
    <Router>
      <Switch>
        <Route path="/" exact component={HomePage} />
        <Route exact path="/login" component={Login} />
        <ProtectedRoute> 
          <Route path="/dashboard" component={Dashboard} />
        </ProtectedRoute>
      </Switch>
    </Router>
  )
}
  

Комментарии:

1. Он не будет перенаправлять на вход для неавторизованного пользователя, потому что возвращает false. Если я разместил маршрут входа в isAuth === null , пользователи будут перенаправлять страницу пользовательского интерфейса входа, а затем перенаправлять на панель мониторинга, чего я не хотел. Should be redirect to dashboard without showing login

2. Как выглядит ваш компонент панели мониторинга?

3. Я обновил приведенный выше код. Проверьте еще раз. Это должно работать.