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
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
But if you want to navigate from there, don’t use usenavigate hook. instead use normal window.location.redirect()
Issues
App
component is rendering the router that provides a routing context to its sub-ReactTree, soApp
itself cannot access it.AppWithAppBar
component is declared within another React component,App
, which is a bit of a React anti-pattern.Solution Suggestion
Router
component higher up the ReactTree such that it can provide the routing context toApp
soApp
can use theuseNavigate
anduseLocation
hooks.AppWithAppBar
component out ofApp
, or just apply its logic and returned JSX directly withinApp
.Additional suggestions:
fetchUserData
callback handler into theuseEffect
hook to remove it as an external dependency.fetchUserData
to toggle the loading true when fetching data and move the setting the loading back to false in thefinally
block of thetry/catch