简体   繁体   中英

Protected route not working correctly with React and Firebase

I'm building a small app with firebase and react and currently working on implementing the authentication. I've set the onAuthStateChanged in my app component as a side effect and whenever user is logged in it should be redirected to a desired component from ProtectedRoute.

This works correctly but unfortunately when refreshing the page the ProtectedRoute is not rendering correct component and is just firing redirection.

I get what is happening: on refresh user is empty and only after then it change so I would expect to see a screen flicker and a proper redirection.

Could you please look at below code and maybe tell me how to fix this behavior?

App component:

const App = () => {
  const [authUser, setAuthUser] = useState<firebase.User | null>(null);
  const Firebase = useContext(FirebaseContext);

  useEffect(() => {
    const authListener = Firebase!.auth.onAuthStateChanged((authUser) => {
      authUser ? setAuthUser(authUser) : setAuthUser(null);
    });

    return () => authListener();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  return (
    <AuthUserContext.Provider value={authUser}>
      <Router>
        <div>
          <Navigation />

          <hr />

          <Route exact path={ROUTES.LANDING} component={Landing} />
          <Route exact path={ROUTES.SIGN_UP} component={SignUpPage} />
          <Route exact path={ROUTES.SIGN_IN} component={SignIn} />
          <Route
            exact
            path={ROUTES.PASSWORD_FORGET}
            component={PasswordForget}
          />
          <ProtectedRoute exact path={ROUTES.HOME} component={Home} />
          <ProtectedRoute exact path={ROUTES.ACCOUNT} component={Account} />
          <Route exact path={ROUTES.ACCOUNT} component={Account} />
          <Route exact path={ROUTES.ADMIN} component={Admin} />
        </div>
      </Router>
    </AuthUserContext.Provider>
  );
};

Protected Route:

interface Props extends RouteProps {
  component?: any;
  children?: any;
}

const ProtectedRoute: React.FC<Props> = ({
  component: Component,
  children,
  ...rest
}) => {
  const authUser = useContext(AuthUserContext);

  return (
    <Route
      {...rest}
      render={(routeProps) =>
        !!authUser ? (
          Component ? (
            <Component {...routeProps} />
          ) : (
            children
          )
        ) : (
          <Redirect
            to={{
              pathname: ROUTES.SIGN_IN,
              state: { from: routeProps.location },
            }}
          />
        )
      }
    />
  );
};

Found the fix. Had to add the flag checking for user authentication status (default value of that flag is set to true). Flag needs to be passed to ProtectedRoute as prop and if is True then render some loading component:

App component:

const App = () => {
  const [authUser, setAuthUser] = useState(false);
  const [authPending, setAuthPending] = useState(true);
  const Firebase = useContext(FirebaseContext);

  useEffect(() => {
    const authListener = Firebase!.auth.onAuthStateChanged((authUser) => {
      setAuthUser(!!authUser);
      setAuthPending(false);
    });

    return () => authListener();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  return (
    <AuthUserContext.Provider value={authUser}>
      <Router>
        <div>
          <Navigation />

          <hr />
          <Switch>
            <Route exact path={ROUTES.LANDING} component={Landing} />
            <Route exact path={ROUTES.SIGN_UP} component={SignUpPage} />
            <Route exact path={ROUTES.SIGN_IN} component={SignIn} />
            <Route
              exact
              path={ROUTES.PASSWORD_FORGET}
              component={PasswordForget}
            />
            <ProtectedRoute
              pendingAuth={authPending}
              exact
              path={ROUTES.HOME}
              component={Home}
            />
            <ProtectedRoute
              pendingAuth={authPending}
              exact
              path={ROUTES.ACCOUNT}
              component={Account}
            />
            <Route exact path={ROUTES.ACCOUNT} component={Account} />
            <Route exact path={ROUTES.ADMIN} component={Admin} />
          </Switch>
        </div>
      </Router>
    </AuthUserContext.Provider>
  );
};

ProtectedRoute:

interface Props extends RouteProps {
  component?: any;
  children?: any;
  pendingAuth: boolean;
}

const ProtectedRoute: React.FC<Props> = ({
  component: Component,
  children,
  pendingAuth,
  ...rest
}) => {
  const authUser = useContext(AuthUserContext);

  if (pendingAuth) {
    return <div>Authenticating</div>;
  }

  return (
    <Route
      {...rest}
      render={(routeProps) =>
        !!authUser ? (
          Component ? (
            <Component {...routeProps} />
          ) : (
            children
          )
        ) : (
          <Redirect
            to={{
              pathname: ROUTES.SIGN_IN,
              state: { from: routeProps.location },
            }}
          />
        )
      }
    />
  );
};

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM