localstorage not defined in Next.js

im trying to move an application from React to Next

In React I get no error on this code

let [authTokens, setAuthTokens] = useState(() => localStorage.getItem('authTokens') ? JSON.parse(localStorage.getItem('authTokens')) : null)

let [user, setUser] = useState(()=> localStorage.getItem('authTokens') ? jwt_decode(localStorage.getItem('authTokens')) : null)

but when I try to use it in Next, I get the error

localstorage not defined in Next.js

Which I assume is because Next is rendering on server side first and theres no local storage on server side.

This is the function that’s setting the local storage.

let loginUser = async (e) => {
        e.preventDefault();
        let response = await fetch('http://127.0.0.1:8000/api/token/', {
            method:'POST',
            headers:{
                'Content-Type': 'application/json'
            },
            body:JSON.stringify({'email':e.target.username.value, 'password':e.target.password.value})
        })
        let data = await response.json()
        console.log(data)
        console.log(data.access)
        if(response.status == 200) {
            setAuthTokens(data)
            setUser(jwt_decode(data.access))
            localStorage.setItem('authTokens', JSON.stringify(data))
            router.push('/')
        } else {
            alert('something went wrong')
        }
    }

Here is Solutions:

We have many solutions to this problem, But we recommend you to use the first solution because it is tested & true solution that will 100% work for you.

Solution 1

you can use this code

if (typeof window !== "undefined") {
   if(response.status == 200) {
            setAuthTokens(data)
            setUser(jwt_decode(data.access))
            localStorage.setItem('authTokens', JSON.stringify(data))
            router.push('/')
        } else {
            alert('something went wrong')
        }
  }

Solution 2

When your application renders on the server, there are no browser APIs there.
What you need to do is to access the localStorage only when the app is rendered in the browser. You can do that by waiting for the component to be mounted and then accessing the localStorage

function MyComponent(){
  // this runs only in the browser
  useEffect(()=>{
  // access local storage here
  },[])
}

Solution 3

I seemed to have fixed it by moving the logic from the useState and moving it in a useEffect

Instead of having

let [authTokens, setAuthTokens] = useState(() => localStorage.getItem('authTokens') ? JSON.parse(localStorage.getItem('authTokens')) : null)

let [user, setUser] = useState(()=> localStorage.getItem('authTokens') ? jwt_decode(localStorage.getItem('authTokens')) : null)

I moved the logic in useEffect like so

let [authTokens, setAuthTokens] = useState(null)
let [user, setUser] = useState(null)

    useEffect(() => {
        
            if(localStorage.getItem('authTokens')) {
                setAuthTokens(JSON.parse(localStorage.getItem('authTokens')))
                setUser(jwt_decode(localStorage.getItem('authTokens')))
            } else {
                setAuthTokens(null)
                setUser(null)
            }
        

Note: Use and implement solution 1 because this method fully tested our system.
Thank you 🙂

All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply