useNavigate is not redirecting on login v6 react-router-dom

Viewed 7364

On submitting a form the useNavigate hook should detect the isAuthenticated state and redirect to "/app/dashboard" route, but it remains on the "/login" route

App.js Here isAuthenticated is passed to the routes and renders relevant routes depending on its global boolean value

import React from "react";
import jwtDecode from "jwt-decode";

// Routing
import { useRoutes } from "react-router-dom";
import routes from "./routes"

const token = localStorage.FBIdToken;

let isAuthenticated;

if (token) {
  const decodedToken = jwtDecode(token);
  console.log(decodedToken);
  //  Check whether the token is expired
  if (decodedToken.exp * 1000 < Date.now()) {
    window.location.href = "/login";
    isAuthenticated = false;
  } else {
    isAuthenticated = true;
  }
}

function App() {
  const routing = useRoutes(routes(isAuthenticated));

  return (
    <ThemeProvider theme={theme}>
      <GlobalStyles />
      {routing}
    </ThemeProvider>
  );
}

export default App;

routes.js the isAuthenticated is passed to determine which view will be displayed

import React from "react";
import { Navigate } from "react-router-dom";
const routes = (isAuthenticated) => [
  {
    path: "/app",
    element:
      isAuthenticated === true ? <DashboardLayout /> : <Navigate to="/login" />,
    children: [{ path: "dashboard", element: <Dashboard /> }],
  },
  {
    path: "/",
    element: !isAuthenticated ? (
      <MainLayout />
    ) : (
      <Navigate to="/app/dashboard" />
    ),
    children: [
      { path: "login", element: <Login /> },
      { path: "signup", element: <Signup /> },
      { path: "/", element: <Navigate to="/app/dashboard" /> },
    ],
  },
];

export default routes;

login.js an axios request is sent and a successful response should lead to a page redirect to the "/app/dashboard" route

import React, { useState } from "react";
import { Link as RouterLink, useNavigate } from "react-router-dom";
const Login = () => {
  const classes = useStyles();
  const navigate = useNavigate();

  const [state, setState] = useState({
    email: "",
    password: "",
    loading: false,
    errors: {},
  });

  const handleChange = (e) => {
    // Set the value of state to corresponding input
    setState({
      ...state,
      [e.target.name]: e.target.value,
    });
  };

  const handleSubmit = (e) => {
    e.preventDefault();

    setState({
      ...state,
      loading: true,
    });

    const userData = {
      email: state.email,
      password: state.password,
    };

    axios
      .post("/login", userData)
      .then((response) => {
        console.log(response.data);

        localStorage.setItem("FBIdToken", `Bearer ${response.data.token}`);

        setState({
          ...state,
          loading: false,
        });

        navigate("/app/dashboard", { replace: true });
      })
      .catch((err) => {
        setState({
          ...state,
          errors: err.response.data,
          loading: false,
        });
      });
  };

  return (
    // Some form
  );
};

export default Login;
2 Answers

The approach I took was a dirty one, but it works non the less for history version 5.0.0

src/utils/history.js

import { createBrowserHistory } from "history"
export default createBrowserHistory();

src/pages/auth/login.js

import React, { useState } from "react";
import { Link as RouterLink} from "react-router-dom";
import history from "../../utils/histroy";

const Login = () => {
  const classes = useStyles();
  const navigate = useNavigate();

  const [state, setState] = useState({
    email: "",
    password: "",
    loading: false,
    errors: {},
  });

  const handleChange = (e) => {
    // Set the value of state to corresponding input
    setState({
      ...state,
      [e.target.name]: e.target.value,
    });
  };

  const handleSubmit = (e) => {
    e.preventDefault();

    setState({
      ...state,
      loading: true,
    });

    const userData = {
      email: state.email,
      password: state.password,
    };

    axios
      .post("/login", userData)
      .then((response) => {
        console.log(response.data);

        localStorage.setItem("FBIdToken", `Bearer ${response.data.token}`);

        setState({
          ...state,
          loading: false,
        });

        history.push("/app/dashboard");
        histrory.go();
      })
      .catch((err) => {
        setState({
          ...state,
          errors: err.response.data,
          loading: false,
        });
      });
  };

  return (
    // Some form
  );
};

export default Login;

The useNavigate has no problem. The issue was the boolean parameter passed to my routes. Here is how i resolved it

created a file called src/redux/reducers/authReducer

import * as actionType from "../types";

let decodedUser = localStorage.getItem("SomeToken");

//  Initialize state to match auth state
const initialState = decodedUser
  ? { isAuthenticated: true }
  : { isAuthenticated: false };

export default function (state = initialState, action) {
  switch (action.type) {
    case actionType.SET_AUTHENTICATED:
      return {
        ...state,
        isAuthenticated: true,
      };

    //  Unauthenticated is in the userReducer

    default:
      return state;
  }
}

persist the reducer to combined reducer in the store

const reducers = combineReducers({
  
  auth: authReducer,
});

then in App.js do the following

function App() {
  const { isAuthenticated } = useSelector((state) => state.auth);

  const routing = useRoutes(routes(isAuthenticated));

  return (
  <div>Some filler code</div>
  );
}

then call the action type in an action being performed. e.g.

userAction.js

import * as actionType from "../types";
export const login = (data) => (dispatch) =>{
   dispatch({type: actionType.SET_AUTHENTICATED });
}

reference: https://bezkoder.com/react-hooks-redux-login-registration-example/

Related