简体   繁体   中英

React Router Redirect does not render the component

Here is my App.js:

function App() {
  return (
    <BrowserRouter>
      <AuthProvider>
        <Switch>
          <PrivateRoute path="/" component={MainPage} />
          <PrivateRoute path="/admin" component={MainPage} />
          <Container
            className="d-flex align-items-center justify-content-center"
            style={{ minHeight: "100vh" }}
          >
            <div className="w-100" style={{ maxWidth: "400px" }}>
              <Route path="/signup" component={Signup} />
              <Route path="/login" component={Login} /> //The component part
              <Route path="/forgot-password" component={ForgotPassword} />
            </div>
          </Container>
        </Switch>
      </AuthProvider>
    </BrowserRouter>
  );
}

Auth Context for PriveRoute Component from firebase:

export default function PrivateRoute({ component: Component, ...rest }) {
  const { currentUser } = useAuth();

  return (
    <Route
      render={(props) => {
        return currentUser ? <Admin {...props} /> : <Redirect to="/login" />; // it redirects when the user does not log in.
      }}
    ></Route>
  );

Login Component:

export default function Login() {
  const emailRef = useRef();
  const passwordRef = useRef();
  const { login, currentUser } = useAuth();
  const [error, setError] = useState("");
  const [loading, setLoading] = useState(false);
  const history = useHistory();

  if (currentUser) {
    history.push("/admin/mainpage");
  }
  async function handleSubmit(e) {
    e.preventDefault();

    try {
      setError("");
      setLoading(true);
      await login(
        emailRef.current.value + "@myosmail.com",
        passwordRef.current.value
      );
      history.push("/admin/mainpage");
    } catch {
      setError("Failed to log in");
    }

    setLoading(false);
  }

  return (
    <>
      <Card>
        <Card.Body>
          <h2 className="text-center mb-4">Log In</h2>
          {error && <Alert variant="danger">{error}</Alert>}
          <Form onSubmit={handleSubmit}>
            <Form.Group id="email">
              <Form.Label>Username</Form.Label>
              <Form.Control ref={emailRef} required />
            </Form.Group>
            <Form.Group id="password">
              <Form.Label>Password</Form.Label>
              <Form.Control type="password" ref={passwordRef} required />
            </Form.Group>
            <Button disabled={loading} className="w-100" type="submit">
              Log In
            </Button>
          </Form>
          <div className="w-100 text-center mt-3">
            <Link to="/forgot-password">Forgot Password?</Link>
          </div>
        </Card.Body>
      </Card>
      <div className="w-100 text-center mt-2">
        Need an account? <Link to="/signup">Sign Up</Link>
      </div>
    </>
  );
}

I want to build a component if the user does not logged in, it can not access to content of my web site so I build the component above. But the problem is that when I opened my page and does not logged in the page redirect me to "/login" but the Login component does not be rendered I do not understand what is the problem. I debugged my component and I saw that my code firstly goes to PrivateRoute component after that it redirected to "/login" but nothing rendered when the page redirected to Login. When I removed PrivateRoutes from App.js my code worked correctly.

Issue

My guess is that you've placed a "/" path first within the Switch component:

<PrivateRoute path="/" component={MainPage} />

The redirect to "/login" works but then the Switch matches the "/" portion and tries rendering this private route again.

Your private route is also malformed, it doesn't pass on all the Route props received.

Solution

Fix the private route component to pass on all props.

export default function PrivateRoute({ component: Component, ...rest }) {
  const { currentUser } = useAuth();

  return (
    <Route
      {...rest}
      render={(props) => {
        return currentUser ? <Admin {...props} /> : <Redirect to="/login" />;
      }}
    />
  );
}

Within the Switch component path order and specificity matter, you want to order more specific paths before less specific paths. "/" is a path prefix for all paths, so you want that after other paths. Nested routes also need to be rendered within a Switch so only a single match is returned and rendered.

<BrowserRouter>
  <AuthProvider>
    <Switch>
      <Container
        className="d-flex align-items-center justify-content-center"
        style={{ minHeight: "100vh" }}
      >
        <div className="w-100" style={{ maxWidth: "400px" }}>
          <Switch>
            <Route path="/signup" component={Signup} />
            <Route path="/login" component={Login} /> //The component part
            <Route path="/forgot-password" component={ForgotPassword} />
          </Switch>
        </div>
      </Container>
      <PrivateRoute path={["/admin", "/"]} component={MainPage} />
    </Switch>
  </AuthProvider>
</BrowserRouter>

Update

I'm a bit confused by your private route though, you specify the MainPage component on the component prop, but then render an Admin component within. Typically an more agnostic PrivateRoute component may look something more like:

const PrivateRoute = props => {
  const { currentUser } = useAuth();

  return currentUser ? <Route {...props} /> : <Redirect to="/login" />;
}

This allows you to use all the normal Route component props and doesn't limit to using just the component prop.

Usages:

  •  <PrivateRoute path="/admin" component={Admin} />
  •  <PrivateRoute path="/admin" render={props => <Admin {...props} />} />
  •  <PrivateRoute path="/admin"> <Admin /> </PrivateRoute>

I also had same problem once. This solved me. Wrap your three routes with a Switch.

<Switch>
    <Route path="/signup" component={Signup} />
    <Route path="/login" component={Login} />
    <Route path="/forgot-password" component={ForgotPassword} />
</Switch>

As the first private route has the root path it will always go to the route. You can use exact for the first private route. But the best way should be placing the first private route

<PrivateRoute path={["/admin", "/"]} component={MainPage} />

at the bottom. So that when there is no match it goes there only.

You are not passing the path to the Route in the ProtectedRoute .

<Route
      {...rest} // spread the test here
      render={(props) => {
        return currentUser ? <Admin {...props} /> : <Redirect to="/login" />; // it redirects when the user does not log in.
      }}
    ></Route>

Also switch the Route of the path as @Drew Reese mentioned.

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