Search code examples
reactjsreact-router-domantdredux-toolkit

React JS React-router-dom6 doesn't navigate to the page by useNavigate


I'm using react-router-dom6 & redux-toolkit & antd form and I'm saving the token to localstorage. what I want is that after logging in, redirecting to dashboard page but instead it redirects to empty page with the same url of dashboard & when I reload the dashboard page contents displayed

routes.tsx

{!token ? (
  <Routes>
    <Route path="/" element={<Navigate to="/login" />} />
    <Route path="/login" element={<Login />} />
    <Route path="/signUp" element={<SignUp />} />
    <Route path="/resetPassword" element={<ResetPassword />} />
  </Routes>
) : (
  <>
    <Sidebar collapsed={collapsed} />
    <Layout className="site-layout">
      <Header collapsed={collapsed} collapseHandler={collapseHander} />
      <div className="contentContainer">
        <Routes>
          <Route path="/" element={<Navigate to="/dashboard" />} />
          <Route path="/dashboard" element={<Dashboard />} />
          {role === "1" && <Route path="/groupList" element={<GroupList />} />}
          <Route path="/newOrder" element={<NewOrder />} />
        </Routes>
      </div>
      <Footer>footer</Footer>
    </Layout>
  </>
)}

loginSlice.ts

export const postLogIn = createAsyncThunk(
  "login/postLogIn",
  ({ email, password }: Login, { dispatch }) => {
    return axios
      .post("/login", {
        email,
        password
      })
      .then((response) => {
        successMessage(response.data.status);
        if (response.status === 200) {
          console.log(response.data.data, "login");
          localStorage.setItem("token", response.data.data.Token);
          localStorage.setItem("role", response.data.data.role);
          localStorage.setItem("user_id", response.data.data.user_id);
          localStorage.setItem("user_name", response.data.data.full_name);
          localStorage.setItem("email", response.data.data.email);
        }
      })
      .catch((err) => {
        errorMessage(err.message);
      });
  }
);
const logInSlice = createSlice({
  name: "login",
  initialState,
  reducers: {},
  extraReducers: (builder) => {
    builder.addCase(postLogIn.pending, (state) => {
      state.loading = true;
      state.error = false;
    });
    builder.addCase(postLogIn.fulfilled, (state) => {
      state.loading = false;
      state.error = false;
    });
    builder.addCase(postLogIn.rejected, (state) => {
      state.loading = false;
      state.error = true;
    });
  }
});

export default logInSlice.reducer;

loginForm.tsx

  const navigate = useNavigate();
  const onFinish = (values: any) => {
    dispatch(postLogIn(values));
    if (!error) {
      navigate("/dashboard", { replace: true });
    }
  };

              <Form
                className="form"
                name="login-form"
                wrapperCol={{
                  span: 24
                }}
                initialValues={{
                  remember: true
                }}
                onFinish={onFinish}
                autoComplete="off">
---
</Form>

Solution

  • Issue

    You missed sharing a complete code example, i.e. where token is declared/set/read in the routes.tsx file, but I don't think that is the cause of the issue. The issue is that postLogIn is an asynchronous action and the onFinish callback doesn't wait for it to complete, i.e. resolve, prior to redirecting to "/dashboard". The reason the URL changes to "/dashboard" and nothing is rendered is because there is no !token route matching "/dashboard" and no general fallback to redirect to a known handled path. I'd be willing to bet there's a RRD warning in the console about no matching route found for path "/dashboard".

    {!token ? (
      <Routes>
        <Route path="/" element={<Navigate to="/login" />} />
        <Route path="/login" element={<Login />} />
        <Route path="/signUp" element={<SignUp />} />
        <Route path="/resetPassword" element={<ResetPassword />} />
        // <-- no matched route for "/dashboard", render nothing
      </Routes>
    ) : (
      ...
    )}
    

    Solution

    dispatch(postLogIn(values)); returns a Promise object so the onFinish should wait for Promise to resolve.

    Example:

    const onFinish = async (values: any) => {
      try {
        await dispatch(postLogIn(values));
        navigate("/dashboard", { replace: true });
      } catch (error) {
        // catch and handle any rejected Promises or thrown errors
      }
    };
    

    Suggestion

    When you conditionally render the routes there's potential for navigating/redirecting to a conditionally rendered route prior to the condition changing and the route not actually being rendered just yet. Your code might work more smoothly if you implemented actual route protection and unconditionally render all the routes. Here's an simple example:

    import { Navigate, Outlet, useLocation } from 'react-router-dom';
    const ProtectedRoute = () => {
      const location = useLocation();
      const token = /* wherever the token value is coming from */
    
      return token
        ? <Outlet />
        : <Navigate to="/login" replace state={{ from: location }} />;
    };
    

    ...

    <Routes>
      <Route path="/" element={<Navigate to="/login" />} />
      <Route path="/login" element={<Login />} />
      <Route path="/signUp" element={<SignUp />} />
      <Route path="/resetPassword" element={<ResetPassword />} />
      <Route element={<ProtectedRoute />}>
        <Route element={<LayoutWithSidebarLayoutHeaderFooter />}>
          <Route path="/dashboard" element={<Dashboard />} />
          {role === "1" && <Route path="/groupList" element={<GroupList />} />}
          <Route path="/newOrder" element={<NewOrder />} />
        </Route>
      </Route>
    </Routes>