27

I make a system jsonwebtoken in React and use Next.js. I find a problem when I run the code in the browser, that is, "localStorage is not defined". How can I fix it?

This is my code in file AuthStudentContext.js:

import React from 'react'
import axios from 'axios'

const axiosReq = axios.create()
const AuthStudentContext = React.createContext()

export class AuthStudentContextProvider extends React.Component {

    constructor() {
        super()
        this.state = {
            students: [],
            student: localStorage.getItem('student') || {},
            token: localStorage.getItem('token') || "",
            isLoggedIn: (localStorage.getItem('student' == null)) ? false : true
        }
    }

    login = (credentials) => {
        return axiosReq.post("http://localhost:4000/api/login", credentials)
            .then(response => {
                const { token } = response.data
                localStorage.setItem("token", token)

                this.setState({
                    token,
                    isLoggedIn: true
                })

                return console.log(response)
            })
    }

And it shows error "localStorage is not defined".

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Toni Suwendi
  • 321
  • 1
  • 3
  • 7

7 Answers7

26

As everyone already mentioned, Next.js runs both on the client and server. On the server, there isn't any localStorage, hence the undefined error.

However, an alternative solution is to check if Next.js is running on the server before accessing the localStorage. I.e.,

const ISSERVER = typeof window === "undefined";

if(!ISSERVER) {
    // Access localStorage
    ...localStorage.get...
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
SILENT
  • 3,916
  • 3
  • 38
  • 57
15

In the constructor, as well as componentWillMount lifecycle hooks, the server is still rendering the component. On the other hand, localStorage exists as part of the browser's window global, and thus you can only use it when the component is rendered. Therefore you can only access localStorage in the componentDidMount lifecycle hook. Instead of calling localStorage in the constructor, you can define an empty state, and update the state in componentDidMount when you can start to call localStorage.

constructor() {
  super()
  this.state = {
    students: [],
    student: undefined
    token: undefined,
    isLoggedIn: undefined
  };
}

componentDidMount() {
  this.login();
  this.setState({
    student: localStorage.getItem('student') || {},
    token: localStorage.getItem('token') || "",
    isLoggedIn: (localStorage.getItem('student' == null)) ? false : true
  });
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
wentjun
  • 40,384
  • 10
  • 95
  • 107
12

I never touched Next.js, but I guess its equivalent to Nuxt.js. So it does server-side rendering while you try to access localstorage on the client side.

You will need to use componentDidMount() for this. Here is an example:

componentDidMount(){
   localStorage.setItem('myCat', 'Tom');
   alert("Tom is in the localStorage");
}

Otherwise, you could try with process.browser:

if (process.browser) {
   localStorage.setItem("token", token);
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
bill.gates
  • 14,145
  • 3
  • 19
  • 47
5

In addition to what SILENT said, this works for me:

React.useEffect(() => {
    if (localStorage) {
        const getLocalState = localStorage.getItem("headless");
        console.log("LocalState: ", getLocalState)
    }
}, []);
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Smart Samuel
  • 51
  • 1
  • 2
  • it will work only if your localstorage data is not depend with your initial browser data. if your browser data is depend with localstorage then it will give issue cause next js render on server side and local storage work for client side – lodey Mar 23 '22 at 18:09
2

The window object and Localstorage won't be available when Next.js is building. So you need to check if the code is running in the browser. If you are running in React hooks you don't need to do this because hooks are always running browser side in React.

Just add these two utility functions to your Next.js project.

export const isBrowser = (): boolean => {
  return typeof window !== 'undefined'
}

export const nextLocalStorage = (): Storage | void => {
  if (isBrowser()) {
    return window.localStorage
  }
}

Then you can use it in your code like this:

nextLocalStorage()?.setItem('user', JSON.stringify(user))
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Richard Torcato
  • 2,504
  • 25
  • 26
0

I have created a function getLocalStorageItem and called this in useEffect with the required key name. After getting the value from localStorage, saved it in a state(i.e currentUser) and used it in initialState.

 const [currentUser, setCurrentUser] = useState({});

  const getLocalStorageItem = (key) => {
    return typeof window !== undefined
      ? window.localStorage.getItem(key)
      : null;
  };

  useEffect(() => {
    setCurrentUser({
      token: getLocalStorageItem("token"),
      refreshToken: getLocalStorageItem("refreshToken"),
    });
  }, []);

  const initialState = {
    auth: {
      isLoggedIn: true,
      currentUser: currentUser,
    },
  };
Azad Ansari
  • 138
  • 1
  • 10
0

localStorage is only available client side. So Next.js will throw an error if you attempt to access localStorage to getItem or setItem.

Instead, inside the component, use a useEffect hook to access the localStorage on first client side render.

useEffect(() => {
  localStorage.setItem("abc", "def");
}, []);
ashuvssut
  • 1,725
  • 9
  • 17