2

I am developing a fullstack app using React & Node.

The following is the home screen.

enter image description here

After a user logs in, the name of the user is displayed in the navbar and the response from the server (including the JWT) is saved in the local storage as shown in the following pictures:

enter image description here

enter image description here

Now, when I refresh the page the user is logged out. This should not happen because I am sending the token on every request header using axios global defaults as shown in the code snippet below:

frontend/src/App.js

import React from "react";
import { Container } from "react-bootstrap";
import Header from "./components/Header";
import Footer from "./components/Footer";
import Products from "./components/Products";
import { Route, Switch } from "react-router-dom";
import SingleProductView from "./components/SingleProductView";
import Cart from "./components/Cart";
import LoginForm from "./components/LoginForm";
import RegisterForm from "./components/RegisterForm";
import Profile from "./components/Profile";
import Shipping from "./components/Shipping";
import PaymentMethod from "./components/PaymentMethod";
import PlaceOrder from "./components/PlaceOrder";
import axios from "axios";

const {token} = localStorage.getItem("loggedInUser");
axios.defaults.headers.common["Authorization"] = `Bearer ${token}`;

const App = () => {
  return (
    <>
      <Header />
      <main className="py-3">
        <Container>
          <Switch>
            <Route path="/placeorder" component={PlaceOrder} />
            <Route path="/payment" component={PaymentMethod} />
            <Route path="/shipping" component={Shipping} />
            <Route path="/profile" component={Profile} />
            <Route path="/register" component={RegisterForm} />
            <Route path="/login" component={LoginForm} />
            <Route path="/product/:id" component={SingleProductView} />
            <Route path="/cart/:id?" component={Cart} />
            <Route path="/" component={Products} exact />
          </Switch>
        </Container>
      </main>
      <Footer />
    </>
  );
};

export default App;

What am I doing wrong? I want the user to stay logged in even after page refresh.

HKS
  • 526
  • 8
  • 32
  • When you refresh, do you still see the token in localstorage? Where is the code that you make the call and set the token? I feel like it's an async issue where then you are getting the item from localstorage it has not been set yet. – Nick Mar 09 '21 at 15:54

1 Answers1

2

Axios will lose its custom (provided by you) default headers settings after page is refreshed.

So, you need to set it again after page refresh (i.e. at App Start). You can do it in a component which always gets mounted before other components do:

In most cases, App component would be the perfect place for that:

// App.jsx

useEffect(() => {
  const { token } = localStorage.getItem("loggedInUser");
  if (token) {
    axios.defaults.headers.common["Authorization"] = `Bearer ${token}`;
  }
}, [])

Assuming, you are setting the Token for the first time in localStorage after the Authentication is successful. E.g. in Login component.

Another option could be to write a Request Interceptor for Axios and check if this header exists or not; if not, then set it back from localstorage and let the request proceed.


Using Request Interceptor:

axios.interceptors.request.use(
  function (config) {
    if (!config.headers.Authorization) {
      const { token } = localStorage.getItem('loggedInUser')
      if (token) {
        axios.defaults.headers.common['Authorization'] = `Bearer ${token}` // this will be used in next requests
        config.headers.Authorization = `Bearer ${token}` // this is for current request
      }
    }
    return config
  },
  function (error) {
    return Promise.reject(error)
  }
)

Edit: You are passing token to each AsyncThunkAction. So, all you need to do is initialize the redux state properly. So, in the loginSlice:

function getToken() {
  const { token } = localStorage.getItem("loggedInUser");
  if (token) {
    return { token };
  }
  return null;
}

const initialState = {
  status: "idle",
  user: getToken(),
  error: null,
};
Ajeet Shah
  • 18,551
  • 8
  • 57
  • 87
  • I tried your solution. I am still getting logged out after page refresh. – HKS Mar 09 '21 at 16:25
  • still no luck even with request interceptor. I don't know what's wrong. – HKS Mar 09 '21 at 17:08
  • 1
    Your solution worked. Thanks. However, I am already using preloadedState in my store. Then, why is my solution not working? `const loggedInUserFromStorage = localStorage.getItem("loggedInUser") ? JSON.parse(localStorage.getItem("loggedInUser")) : null; const preloadedState = { login: { user: loggedInUserFromStorage, }, }; export default configureStore({ reducer: { products: productsReducer, cart: cartReducer, login: loginReducer, register: registerReducer, preloadedState, }, });` – HKS Mar 10 '21 at 04:56
  • 1
    I figured out the issue. I had put preloadedState inside the reducer. Everything is working fine now. – HKS Mar 10 '21 at 04:59
  • Can you please take a look at the following question? https://stackoverflow.com/questions/66748951/redux-store-not-found-and-not-able-to-render-a-component-for-a-specific-path-in – HKS Mar 23 '21 at 03:43
  • I added a comment, if that doesn't help you, I will have another look later. – Ajeet Shah Mar 23 '21 at 03:57