Warten Sie auf API-Aufrufe, bevor Sie eine private Route rendern

Sep 05 2020

Ich habe Routen geschützt, bei denen die Übergabe der Authentifizierung true oder false aktiviert hat, damit Benutzer auf die Authentifizierungsseite zugreifen können. Bei jedem Laden der Seite wird die Auth-Token-API aufgerufen und auf Gültigkeit überprüft. Wenn das Token ungültig ist, leiten Sie zur Anmeldeseite weiter.

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 && res.data && 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>
  )
}

Problem

Wenn Benutzer authentifizierte Seiten (Dashboard) durchsuchen, werden sie zur Anmeldeseite weitergeleitet. Der Grund dafür ist, dass die Überprüfungstoken-API zurückgegeben wird, nachdem ProtectedRoute als isAuth gerendert wurde, was false entspricht.

Antworten

1 PrateekThapa Sep 05 2020 at 14:44

Sie können den Anfangszustand verwenden null, um die verschiedenen Zustände Ihrer Komponente zu unterscheiden.

  • null -> API-Token noch nicht aufgerufen.
  • true -> Token verifiziert
  • false -> Token-Überprüfung fehlgeschlagen.
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 && res.data && 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>
  )
}
2 3limin4t0r Sep 05 2020 at 21:05

Anstatt isAuthein Boolescher Wert zu sein, verwenden Sie eine Zeichenfolge oder Zahl, um die Anzahl der Zustände zu erhöhen. Zum Beispiel umbenennen, isAuthin loginStatedie sein kann "pending", "authenticated"oder "unauthenticated". Verwenden Sie dann "pending"als Ausgangszustand und fügen Sie ein zusätzliches Szenario hinzu. Sie können beispielsweise zurückkehren null, um nichts zu rendern, einen sich drehenden Kreis zu rendern usw.

Hier ist ein Beispiel für das Rendern null(nichts), während das Anmeldetoken authentifiziert wird:

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 && res.data && 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>
  )
}

Als Haftungsausschluss habe ich keine Erfahrung mit React Router, daher habe ich das Beispiel so nah wie möglich am Original gehalten.

1 deepakgupta191199 Sep 05 2020 at 21:38

Sie können eine andere Statusvariable verwenden, um auf die API-Ausführung zu warten, bevor Sie Routen initialisieren.

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

Dann können Sie in Routen Folgendes tun:

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

Normalerweise trenne ich Sitzungsrouten und keine Sitzungsrouten in separaten Hooks und es funktioniert gut.