reactjsauthenticationjwtcomponents

Profile component doesn't render but waits a refresh


After I click login, the home page renders but without a user, so the profile component doesn't render. The backend works properly. Is there something wrong with my AuthContext.jsx?

// src/context/AuthContext.jsx
import { createContext, useContext, useEffect, useState, useCallback } from 'react';
import { useNavigate } from 'react-router-dom';
import api from '../api/axios';

// Create AuthContext with default values
const AuthContext = createContext(null);

export function AuthProvider({ children }) {
  const [user, setUser] = useState(null);
  const [token, setToken] = useState(() => localStorage.getItem('token'));
  const [loading, setLoading] = useState(true);
  const [initialized, setInitialized] = useState(false);

  const navigate = useNavigate();

  console.log("AuthContext state", { user, token, initialized, loading });

  useEffect(() => {
    const initializeAuth = async () => {
      if (!token) {
        setLoading(false);
        setInitialized(true);
        return;
      }

      // Skip if user is already loaded (prevents duplicate calls from login)
      if (user && initialized) {
        setLoading(false);
        return;
      }

      try {
        const { data } = await api.get('/api/users/me', {
          headers: { Authorization: `Bearer ${token}` }
        });
        setUser(data);
        console.log("User loaded from token:", data);
      } catch (error) {
        console.error('Error fetching user on init:', error);
        setUser(null);
        setToken(null);
        localStorage.removeItem('token');
      } finally {
        setInitialized(true);
        setLoading(false);
      }
    };

    initializeAuth();
  }, [token]);

  // Login - fetch user data immediately after setting token
  const login = async (email, password) => {
    try {
      setLoading(true);
      const { data } = await api.post('/api/auth/login', { email, password });
      const newToken = data.token;

      localStorage.setItem('token', newToken);
      setToken(newToken);
      
      // Immediately fetch user data with the new token
      const userData = await api.get('/api/users/me', {
        headers: { Authorization: `Bearer ${newToken}` }
      });
      
      setUser(userData.data);
      setInitialized(true);
      
      console.log("Login successful, user loaded:", userData.data);
      
      // Navigate after user is set
      navigate('/');
      
      return data;
    } catch (error) {
      console.error('Login error:', error);
      // Clean up on error
      localStorage.removeItem('token');
      setToken(null);
      setUser(null);
      throw error;
    } finally {
      setLoading(false);
    }
  };

  // Logout
  const logout = () => {
    localStorage.removeItem('token');
    setToken(null);
    setUser(null);
    setInitialized(false);
    navigate('/login');
  };

  // Sync across tabs
  useEffect(() => {
    const handleStorageChange = ({ key, newValue }) => {
      if (key === 'token') {
        setToken(newValue);
        if (!newValue) setUser(null);
      }
    };
    window.addEventListener('storage', handleStorageChange);
    return () => window.removeEventListener('storage', handleStorageChange);
  }, []);

  return (
    <AuthContext.Provider value={{ user, token, loading, initialized, login, logout }}>
      {children}
    </AuthContext.Provider>
  );
}

// Hook to use Auth context
export function useAuth() {
  const context = useContext(AuthContext);
  if (!context) throw new Error('useAuth must be used within an AuthProvider');
  return context;
}


Solution

  • you should extract the token like so:

    const response = await api.post('/api/auth/login', { email, password }); const newToken = response.data.data.token; 
    

    Updated login logic

    const login = async (email, password) => {
      setLoading(true);
      try {
        const response = await api.post('/api/auth/login', { email, password });
        const newToken = response.data.data.token;
    
        localStorage.setItem('token', newToken);
        setToken(newToken);
    
        const { data: userProfile } = await api.get('/api/users/me', {
          headers: { Authorization: `Bearer ${newToken}` }
        });
        setUser(userProfile);
        setInitialized(true);
        navigate('/');
        return response.data;
      } catch (error) {
        localStorage.removeItem('token');
        setToken(null);
        setUser(null);
        throw error;
      } finally {
        setLoading(false);
      }
    };