I am new to React in general and am trying to build a simple application. I want to build an application where users can sign in using their Spotify Account. I have the following files:
App.js
:
import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom';
import './App.css';
import OAuthSignInPage from './components/oauth';
import Callback from './components/callback';
import Welcome from './components/Welcome';
import Options from './components/Options';
import Header from './components/Header';
import { AuthProvider, useAuth } from './hooks/useAuth';
const ProtectedRoute = ({ children }) => {
const { user } = useAuth();
if (!user) {
return <Navigate to="/" />;
}
return children;
};
function App() {
return (
<Router>
<AuthProvider>
<div className="App">
<Header />
<div className="content">
<Routes>
<Route path="/" element={<OAuthSignInPage />} />
<Route path="/callback" element={<Callback />} />
<Route path="/welcome" element={
<ProtectedRoute>
<Welcome />
</ProtectedRoute>
} />
<Route path="/options" element={
<ProtectedRoute>
<Options />
</ProtectedRoute>
} />
</Routes>
</div>
</div>
</AuthProvider>
</Router>
);
}
export default App;
oauth.js
import * as React from 'react';
import { AppProvider } from '@toolpad/core/AppProvider';
import { SignInPage } from '@toolpad/core/SignInPage';
import { useTheme } from '@mui/material/styles';
import { useAuth } from "../hooks/useAuth";
import { useNavigate } from 'react-router-dom';
const providers = [
{ id: 'spotify', name: 'Spotify' }
];
const signIn = async (provider) => {
if (provider.id === 'spotify') {
// Redirect to Spotify login
const clientId = process.env.REACT_APP_SPOTIFY_CLIENT_ID;
const redirectUri = encodeURIComponent(`${window.location.origin}/callback`);
const scope = 'user-read-private user-read-email user-top-read';
const spotifyAuthUrl = `https://accounts.spotify.com/authorize?client_id=${clientId}&response_type=code&redirect_uri=${redirectUri}&scope=${scope}`;
window.location.href = spotifyAuthUrl;
return new Promise(() => {}); // Promise won't resolve due to redirect
}
return { error: 'Invalid provider' };
};
export default function OAuthSignInPage() {
const { user } = useAuth();
const navigate = useNavigate();
React.useEffect(() => {
if (user) {
navigate('/options');
}
}, [user, navigate]);
const theme = useTheme();
return (
<AppProvider theme={theme}>
<SignInPage signIn={signIn} providers={providers} />
</AppProvider>
);
}
protectedRoute.js
:
import { Navigate } from "react-router-dom";
import { useAuth } from "../hooks/useAuth";
export const ProtectedRoute = ({ children }) => {
const { user } = useAuth();
if (!user) {
// user is not authenticated
return <Navigate to="/" />;
}
return children;
};
Callback.js
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
import { useAuth } from '../hooks/useAuth';
function Callback() {
const { login } = useAuth();
const location = useLocation();
useEffect(() => {
const code = new URLSearchParams(location.search).get('code');
if (code) {
login({ code });
}
}, [location, login]);
// No need for loading state since login handles navigation
return null;
}
export default Callback;
useAuth.jsx
import { createContext, useContext, useMemo } from "react";
import { useNavigate } from "react-router-dom";
import { useLocalStorage } from "./useLocalStorage";
const AuthContext = createContext();
export const AuthProvider = ({ children }) => {
const [user, setUser] = useLocalStorage("code", null);
const navigate = useNavigate();
// call this function when you want to authenticate the user
const login = async (data) => {
setUser(data);
};
// call this function to sign out logged in user
const logout = () => {
setUser(null);
navigate("/", { replace: true });
};
const value = useMemo(
() => ({
user,
login,
logout,
}),
[user]
);
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
};
export const useAuth = () => {
return useContext(AuthContext);
};
Now the issue is, the OAuth part is working fine. I can login and Spotify is calling my /callback
endpoint with the code. But the redirection is not working properly. After login it’s going to the /options
route but it is a blank component. The error is:
Cannot update a component (`AuthProvider`) while rendering a different component (`Callback`). To locate the bad setState() call inside `Callback`, follow the stack trace as described in https://react.dev/link/setstate-in-render
And this error is being called in an infinite loop.
I just can’t figure out how to solve this. One more thing is when I manually type in the URL /welcome
after login, it works. Help would be really appreciated.