0

I feel like i lose brain cells everytime i use typescript. I am not liking it at all. I am also not getting a single error but it isnt working how it is supposed to. Anyways, i have a Login Component and a Dashboard component. using useContext, i created a user state and setUser function and used context to pass it into the login component. After logging in, I am redirected to the dashboard route. The dashboard route only has a single button that logs me out and removes the current user. The Problem is that everytime i refresh on the dashboard route, it logs me out and redirects me to login, instead of getting the user from localStorage, parsing the value and using setUser. since a user would still be present at this point, it shouldnt redirect me to login. Also, when i change the initial value of user to {} instead of null, then it doesnt log me out on a refresh but the dashboard route becomes unprotected and i can visit it even though a user is not present. What am i doing wrong, the code seems like it should be working.

export type UserProps = {
    user: {
        name?: string,
        token?: string
    } | null,
    setUser: React.Dispatch<React.SetStateAction<{} | null>>
} 

UserContext.tsx:

import {createContext, useEffect, useState} from 'react'
import { UserProps } from './types'

export const UserContext  = createContext<UserProps>({} as UserProps)

type UserContextProviderProps = {
    children: React.ReactNode

}

const UserContextProvider = ({children}: UserContextProviderProps) => {
    const [user, setUser] = useState<{} | null>(null)

    useEffect(() => {
        const loggedInUser = localStorage.getItem("user");
        if (loggedInUser) {
        setUser(JSON.parse(loggedInUser));
        }
        
    }, [])

    return (
        <UserContext.Provider value={{user, setUser}}>
            {children}
        </UserContext.Provider>
    )
}

export default UserContextProvider

LoginPage.tsx: This is the login page where i am using the context

const [email, setEmail] = useState<string>('')
const [password, setPassword] = useState<string>('')
const [redirect, setRedirect] = useState<boolean>(false)

const {setUser, user}: UserProps = useContext(UserContext)

const handleLogin = async (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault()
    try {
        const {data} = await axios.post('/auth/login', {email,password})
        setUser(data)
        localStorage.setItem('user', JSON.stringify(data))
        setRedirect(true)
        console.log(user)
    } catch (error) {
        return error
    } 

if(redirect) {
        return <Navigate to={'/dashboard'} />
    }

Dashboard .tsx:

import React, {useContext, useState} from 'react'
import { Navigate } from "react-router-dom";
import { UserContext } from '../UserContext'
import { UserProps } from '../types'

const Dashboard = () => {
    const {setUser, user}: UserProps = useContext(UserContext)
    const [redirect,setRedirect] = useState<boolean>(false);

    const handleLogout = () => {
      setUser(null)
      localStorage.removeItem("user")
      setRedirect(true)
    }

    if(redirect){
      return <Navigate to={'/'} />
    }

    if(!user){
      return <Navigate to={'/login'}/> 
    }

    return (
      <div className='flex flex-col'>
        <p>DashBoard</p>
        <p>{user?.name}</p>
        <button className='border border-black w-[100px]' onClick={handleLogout} >Log Out</button>
      </div>
    )
}

export default Dashboard
Drew Reese
  • 165,259
  • 14
  • 153
  • 181
gerard
  • 221
  • 1
  • 2
  • 10

1 Answers1

1

It seems you render Dashboard.tsx too soon. Because when you refresh the page the user is gone and it takes some time to fetch the user from localStorage and when the user is found you are already rendering Dashboard.tsx and then redirected to LoginPage.tsx.

One of the simplest solutions I've come up with is to render some kind of loading before rendering children and load the children only after fetching user state from localStorage is done.

UserContext.tsx:

import React, { createContext, useEffect, useState } from 'react'
import { UserProps } from './types'

export const UserContext = createContext<UserProps>({} as UserProps);

type UserContextProviderProps = {
    children: React.ReactNode
}

const UserContextProvider = ({ children }: UserContextProviderProps) => {
    const [user, setUser] = useState<{} | null>(null)
    const [fetchingUser, setFetchingUser] = useState(true); // initially loading user

    useEffect(() => {
        const loggedInUser = localStorage.getItem("user");
        if (loggedInUser) {
            setUser(JSON.parse(loggedInUser));
        }
        setFetchingUser(false);
    }, [])

    return (
        <UserContext.Provider value={{ user, setUser }}>
            {fetchingUser && <p>Please wait...</p>}
            {!fetchingUser && children}
        </UserContext.Provider>
    )
}

export default UserContextProvider;