skip to Main Content

i am using the useNavigate in my middleWare.js to redirect the user to ‘/’
but i am gettin this error saying "useNavigate() may be used only in the context of a component"
i also tried using window.location.href instead of navigate but it always refreshes the page that makes the website so laggy

here is my app.js :

import './App.css';
import { BrowserRouter as Router, Route, Routes, useLocation } from 'react-router-dom';
import Login from './components/Login';
import Callback from './components/CallBack';
import Home from './components/Home';
import ResponsiveAppBar from './components/NavBar';
import Profile from './components/Profile';
import { useEffect, useState } from 'react';
import axios from 'axios';
import { refreshAccessToken } from './spotifyAuth';
import { useAuth } from './middleWare';

function App() {
  const [userData, setUserData] = useState(null);
  const [token, setToken] = useState(localStorage.getItem('spotifyAccessToken'));
  const isAuthenticated = useAuth(); // Use the middleware to check authentication
  const [isLoading, setIsLoading] = useState(true);

  // Fetch User Data
  const fetchUserData = async () => {
    try {
      const response = await axios.get('https://api.spotify.com/v1/me', {
        headers: {
          Authorization: `Bearer ${token}`,
        },
      });
      setUserData(response.data);
    } catch (error) {
      console.error('Error fetching user data:', error);
    }
  };

  // Logout function
  const handleLogout = () => {
    localStorage.removeItem('spotifyAccessToken');
    localStorage.removeItem('spotifyRefreshToken');
    localStorage.removeItem('spotifyTokenExpiry');
    setUserData(null);
    window.location.href = '/';
  };

  // Check Token Expiration
  const checkTokenExpiration = async () => {
    const expiry = localStorage.getItem('spotifyTokenExpiry');
    if (Date.now() >= expiry) {
      try {
        const refreshToken = localStorage.getItem('spotifyRefreshToken');
        const newTokens = await refreshAccessToken(refreshToken);
        localStorage.setItem('spotifyAccessToken', newTokens.access_token);
        localStorage.setItem('spotifyTokenExpiry', Date.now() + newTokens.expires_in * 1000);
        setToken(newTokens.access_token);
      } catch (error) {
        console.error('Error refreshing token:', error);
      }
    }
  };

  useEffect(() => {
    if (isAuthenticated) {
      fetchUserData(); // Fetch user data only if authenticated
    }
    if (isAuthenticated) {
      fetchUserData(); // Fetch user data only if authenticated
    }
    setIsLoading(false); // Set loading to false after checking authentication
  }, [isAuthenticated]);

  const AppWithAppBar = () => {
    const location = useLocation();
    const shouldShowAppBar = location.pathname !== '/';

    return (
      <>
        {shouldShowAppBar && (
          <div style={{ position: 'fixed', width: '100%', top: 0, zIndex: 1000 }}>
            <ResponsiveAppBar handleLogout={handleLogout} />
          </div>
        )}
        <div style={{ paddingTop: shouldShowAppBar ? '64px' : '0px' }}>
          <Routes>
            <Route path="/" element={<Login />} />
            <Route path="/callback" element={<Callback />} />
            {isAuthenticated && (
              <>
                <Route path="/home" element={<Home userData={userData} handleLogout={handleLogout} />} />
                <Route path="/profile" element={<Profile userData={userData} />} />
              </>
            )}
          </Routes>
        </div>
      </>
    );
  };

  // Display loading screen while checking authentication
  if (isLoading) {
    return (
      <div style={loadingContainerStyle}>
        <div style={loadingSpinnerStyle}></div>
        <h2 style={{ color: '#fff' }}>Loading...</h2>
      </div>
    );
  }

  return (
    <Router>
      <AppWithAppBar />
    </Router>
  );
}

const loadingContainerStyle = {
  height: '100vh',
  display: 'flex',
  flexDirection: 'column',
  justifyContent: 'center',
  alignItems: 'center',
  backgroundColor: '#1e1e2f', 
};

const loadingSpinnerStyle = {
  width: '50px',
  height: '50px',
  border: '6px solid #a29bfe', 
  borderTop: '6px solid transparent',
  borderRadius: '50%',
  animation: 'spin 1s linear infinite',
};


const spinnerAnimationStyle = document.createElement('style');
spinnerAnimationStyle.innerHTML = `
  @keyframes spin {
    0% { transform: rotate(0deg); }
    100% { transform: rotate(360deg); }
  }
`;
document.head.appendChild(spinnerAnimationStyle);

export default App;

and my middleWare :

import { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { refreshAccessToken } from './spotifyAuth';

export const useAuth = () => {
  const [isAuthenticated, setIsAuthenticated] = useState(false);
  const navigate = useNavigate();

  useEffect(() => {
    const checkAuth = async () => {
      const token = localStorage.getItem('spotifyAccessToken');
      const expiry = localStorage.getItem('spotifyTokenExpiry');

      if (!token || Date.now() >= expiry) {
        try {
          const refreshToken = localStorage.getItem('spotifyRefreshToken');
          if (refreshToken) {
            const newTokens = await refreshAccessToken(refreshToken);
            localStorage.setItem('spotifyAccessToken', newTokens.access_token);
            localStorage.setItem('spotifyTokenExpiry', Date.now() + newTokens.expires_in * 1000);
            setIsAuthenticated(true);
          } else {
            navigate('/');
          }
        } catch (error) {
          navigate('/');
        }
      } else {
        setIsAuthenticated(true);
      }
    };

    checkAuth();
  }, [navigate]);

  return isAuthenticated;
};

2

Answers


  1. Return a boolean from the useAuth hook instead of trying to navigate them from the hook itself. With the returned boolean value reroute the user from your APP component. Also update hook name to useIsAuthenticated

         useEffect(() => {
        const checkAuth = async () => {
          const token = localStorage.getItem('spotifyAccessToken');
          const expiry = localStorage.getItem('spotifyTokenExpiry');
    
          if (!token || Date.now() >= expiry) {
            try {
              const refreshToken = localStorage.getItem('spotifyRefreshToken');
              if (refreshToken) {
                const newTokens = await refreshAccessToken(refreshToken);
                localStorage.setItem('spotifyAccessToken', newTokens.access_token);
                localStorage.setItem('spotifyTokenExpiry', Date.now() + newTokens.expires_in * 1000);
                return true;
              } else {
                return false;
            } catch (error) {
              return false;
            }
          } else {
            return true;
          }
        };
    
        checkAuth();
      }, [navigate]);
    

    But if you want to navigate from there, don’t use usenavigate hook. instead use normal window.location.redirect()

    Login or Signup to reply.
  2. Issues

    • The App component is rendering the router that provides a routing context to its sub-ReactTree, so App itself cannot access it.
    • The AppWithAppBar component is declared within another React component, App, which is a bit of a React anti-pattern.

    Solution Suggestion

    • Move the Router component higher up the ReactTree such that it can provide the routing context to App so App can use the useNavigate and useLocation hooks.
    • Move the AppWithAppBar component out of App, or just apply its logic and returned JSX directly within App.

    Additional suggestions:

    • Move the fetchUserData callback handler into the useEffect hook to remove it as an external dependency.
    • Update fetchUserData to toggle the loading true when fetching data and move the setting the loading back to false in the finally block of the try/catch
    function App() {
      const navigate = useNavigate();
      const location = useLocation();
      
      const [userData, setUserData] = useState(null);
      const [token, setToken] = useState(
        localStorage.getItem('spotifyAccessToken')
      );
      const [isLoading, setIsLoading] = useState(true);
    
      // Use the middleware to check authentication
      const isAuthenticated = useAuth();
    
      // Logout function
      const handleLogout = () => {
        localStorage.removeItem('spotifyAccessToken');
        localStorage.removeItem('spotifyRefreshToken');
        localStorage.removeItem('spotifyTokenExpiry');
        setUserData(null);
        navigate('/', { replace: true });
      };
    
      // Check Token Expiration
      const checkTokenExpiration = async () => {
        const expiry = localStorage.getItem('spotifyTokenExpiry');
        if (Date.now() >= expiry) {
          try {
            const refreshToken = localStorage.getItem('spotifyRefreshToken');
            const newTokens = await refreshAccessToken(refreshToken);
            localStorage.setItem('spotifyAccessToken', newTokens.access_token);
            localStorage.setItem(
              'spotifyTokenExpiry',
              Date.now() + newTokens.expires_in * 1000
            );
            setToken(newTokens.access_token);
          } catch (error) {
            console.error('Error refreshing token:', error);
          }
        }
      };
    
      useEffect(() => {
        // Fetch User Data
        const fetchUserData = async () => {
          setIsLoading(true);
          try {
            const response = await axios.get('https://api.spotify.com/v1/me', {
              headers: {
                Authorization: `Bearer ${token}`,
              },
            });
            setUserData(response.data);
          } catch (error) {
            console.error('Error fetching user data:', error);
          } finally {
            // Set loading to false after checking authentication
            setIsLoading(false);
          }
        };
    
        if (isAuthenticated) {
          fetchUserData(); // Fetch user data only if authenticated
        }
      }, [isAuthenticated]);
    
      // Display loading screen while checking authentication
      if (isLoading) {
        return (
          <div style={loadingContainerStyle}>
            <div style={loadingSpinnerStyle}></div>
            <h2 style={{ color: '#fff' }}>Loading...</h2>
          </div>
        );
      }
    
      const shouldShowAppBar = location.pathname !== '/';
    
      return (
        <>
          {shouldShowAppBar && (
            <div
              style={{
                position: 'fixed',
                width: '100%',
                top: 0,
                zIndex: 1000
              }}
            >
              <ResponsiveAppBar handleLogout={handleLogout} />
            </div>
          )}
          <div style={{ paddingTop: shouldShowAppBar ? '64px' : '0px' }}>
            <Routes>
              <Route path="/" element={<Login />} />
              <Route path="/callback" element={<Callback />} />
              {isAuthenticated && (
                <>
                  <Route
                    path="/home"
                    element={(
                      <Home
                        userData={userData}
                        handleLogout={handleLogout}
                      />
                    )}
                  />
                  <Route
                    path="/profile"
                    element={<Profile userData={userData} />}
                  />
                </>
              )}
            </Routes>
          </div>
        </>
      );
    }
    
    <Router>
      <App />
    </Router>
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search