Redirect page upon login using react-router-v5 and

2020-08-03 04:56发布

问题:

I'm using react-router-dom v5.2.

Upon login, I want my page to redirect to /home from /. The login form is at /.

When I try to perform authentication without any async function (ie. comparing username and password to hardcoded values in react), everything works perfectly.

But when I do perform authentication using express and mongo, the redirection upon login stops working. If I login again, then the redirection happens. Protected routes still work (redirect to login page if the user is not logged in).

Here's a small demo of the problem where I use do auth with express + mongo ie. async redux. This is not working as intended. https://youtu.be/Zxm5GOYymZQ

Here's the link of the app where I use hardcoded username and password (both "test") to do auth. No async here. This works as intended. Username and password are both "test". https://poke-zoo.herokuapp.com/


Here's App.js:

const ProtectedRoute = ({ component: Component, ...rest }) => {
  const authState = useSelector(selectorAuth)
  // const location = useLocation()
  return (
    <Route
      {...rest}
      render={props => {
        if (authState.isUserLoggedIn) {
          return <Component {...props} />
        } else {
          return (
            <Redirect
              to={{
                pathname: "/",
                state: {
                  from: props.location,
                },
              }}
            />
          )
        }
      }}
    />
  )
}

const App = () => {
  return (
    <Router>
      <div tw="flex flex-col bg-green-100 min-h-screen">
        <Navbar />
        <Switch>
          <Route exact path="/" component={Landing} />
          <ProtectedRoute path="/home" component={Home} />
          <ProtectedRoute path="/explore" component={Explore} />
          <Route path="*" component={() => "404 Not found."} />
        </Switch>
      </div>
    </Router>
  )
}

Here's ModalLogin.js.

const ModalLogin = props => {
  const { loginModalBool, setLoginModalBool } = props
  const [username, setUsername] = useState("")
  const [password, setPassword] = useState("")

  const dispatch = useDispatch()
  const history = useHistory()

  const attemptLogin = e => {
    e.preventDefault()
    dispatch(tryLogin(username, password))
    history.push("/home")
  }

  return (
    <div tw="flex flex-col text-center h-full w-64 bg-gray-200 text-gray-900 rounded-lg shadow-lg p-2 md:p-4 lg:p-6">
      <div tw="flex flex-row justify-between">
        <p tw="text-lg">Login</p>
        <button tw="text-sm" onClick={() => setLoginModalBool(!loginModalBool)}>
          close
        </button>
      </div>
      <div tw="flex flex-col justify-around my-1">
        <form onSubmit={attemptLogin} tw="">
          <input
            tw="my-1"
            value={username}
            onChange={e => setUsername(e.target.value)}
            placeholder="username"
          />
          <input
            tw="my-1"
            value={password}
            onChange={e => setPassword(e.target.value)}
            type="password"
            placeholder="password"
          />
          <button
            type="submit"
            tw="my-1 p-1 rounded bg-gray-800 text-gray-100 hover:bg-gray-900"
          >
            log in
          </button>
        </form>
      </div>
    </div>
  )
}

Here's the authSlice.js.

import { createSlice } from "@reduxjs/toolkit"
import axios from "axios"

const initialState = {
  isUserLoggedIn: false,
  username: "",
}

export const authSlice = createSlice({
  name: "auth",
  initialState: initialState,
  reducers: {
    login: (state, action) => {
      const user = action.payload

      if (!user) return alert("Login failed. Incorrect username or password.")

      state.username = user.username
      state.isUserLoggedIn = true
    },
    logout: (state, action) => {
      // window.localStorage.removeItem("loggedInUser")
      state.username = ""
      state.isUserLoggedIn = false
    },
    signup: (state, action) => {
      const user = action.payload
      state.username = user.data.username
      state.isUserLoggedIn = true
    },
  },
})

export const tryLogin = (username, password) => {
  return async dispatch => {
    try {
      const response = await axios.post("/api/auth/login", {
        username: username,
        password: password,
      })

      const user = {
        token: response.headers["auth-token"],
        username: response.data.username,
      }

      // window.localStorage.setItem("token", response.headers["auth-token"])

      dispatch(login(user))
    } catch (e) {
      alert("Incorrect Username/Password.")
    }
  }
}

export const selectorAuth = state => state.auth
export const { login, logout } = authSlice.actions
export default authSlice.reducer


Am I using react-router with redux-toolkit incorrectly?

Here's the Github repo

回答1:

Your code does not define redirection logic after login. You can do it in two way.

1st : You can define another redirection wrapper for authentication if you want your routes to redirect in case of authentication.

const AuthRoute = ({ component: Component, ...rest }) => {
  const authState = useSelector(selectorAuth)
  const location = useLocation()
  return (
    <Route
      {...rest}
      render={props => {
        if (!authState.isUserLoggedIn) {
          return <Component {...props} />
        } else {
          return (
            <Redirect
              to={{
                pathname: "/home",
                state: {
                  from: location,
                },
              }}
            />
          )
        }
      }}
    />
  )
}

const App = () => {
  return (
    <Router>
      <div tw="flex flex-col bg-green-100 min-h-screen">
        <Navbar />
        <Switch>
          // It is for login users to redirect to home page
          <AuthRoute exact path="/" component={Landing} />
          <ProtectedRoute path="/home" component={Home} />
          <ProtectedRoute path="/explore" component={Explore} />
          <Route path="*" component={() => "404 Not found."} />
        </Switch>
      </div>
    </Router>
  )
}

2nd : Another approach can be imperatively handling with history.push() or history.replace() :

const Layout = () => {
  const authState = useSelector(selectorAuth);
  const history = useHistory();

  useEffect(() => {
    // if isUserLoggedIn turned to true redirect to /home
    if (authState.isUserLoggedIn) { 
      history.push("/home");
    }
  }, [authState.isUserLoggedIn]); // triggers when isUserLoggedIn changes

  return (
    <Switch>
      <Route exact path="/" component={Landing} />
      <ProtectedRoute path="/home" component={Home} />
      <ProtectedRoute path="/explore" component={Explore} />
      <Route path="*" component={() => "404 Not found."} />
    </Switch>
  );
};

const App = () => {
  return (
    <Router>
      <div tw="flex flex-col bg-green-100 min-h-screen">
        <Navbar />
        <Layout />
      </div>
    </Router>
  );
};

Why didn't your code work? Take a look at the code below :

      <Route exact path="/" component={Landing} />
      <ProtectedRoute path="/home" component={Home} />
      <ProtectedRoute path="/explore" component={Explore} />
      <Route path="*" component={() => "404 Not found."} />

What it does? It checks your browser path and check if it matches with given Route rule from top to down. If Route path matches then it renders the component if not it continues through down visiting each one of Route until it matches your 404.

So back to your case; when you login, you were not leaving "/" path. Because there is no logic implemented to leave the "/" path. So it again match with landing page even though it is authenticated. It matches with route path (Landing page) and stays there. It does not continue and try your logic on ProtectedRoute.



回答2:

to be honest, what i do is use plain old javascript to change the location when the user is logged in

window.location = "/redirect"