I am making a chat app with React 18 and Firebase 9.
The chat route – which is the root app – is supposed to be accessible only after authentication.
In App.js
I have:
import { useContext } from 'react';
import {
createBrowserRouter,
createRoutesFromElements,
Route,
RouterProvider,
Navigate
} from 'react-router-dom';
import RootLayout from './layouts/RootLayout';
import AuthLayout from './layouts/AuthLayout';
import Chat from './pages/Chat';
import Register from './pages/Register';
import Login from './pages/Login';
import { AuthContext } from './contexts/AuthContext';
const router = createBrowserRouter(
createRoutesFromElements(
<Route path="/" element={<RootLayout />}>
<Route index
element={
<ProtectedRoute>
<Chat />
</ProtectedRoute>
} />
<Route path="auth" element={<AuthLayout />}>
<Route path="register" element={<Register />} />
<Route path="login" element={<Login />} />
</Route>
</Route>
)
);
function App() {
const { currentUser } = useContext(AuthContext);
const ProtectedRoute = ({ children }) => {
if (!currentUser) {
return <Navigate to="/auth/login" />;
}
return children
};
return (
<RouterProvider router={ router } ProtectedRoute={ ProtectedRoute } />
);
}
export default App;
In srccontextsAuthContext.js
I have:
import { createContext, useEffect, useState } from "react";
import { auth } from "../firebaseConfig";
import { onAuthStateChanged } from "firebase/auth";
export const AuthContext = createContext();
export const AuthContextProvider = ({ children }) => {
const [currentUser, setCurrentUser] = useState({});
useEffect(() => {
const unSubscribe = onAuthStateChanged(auth, (user) => {
setCurrentUser(user);
console.log(user);
});
return () => {
unSubscribe();
};
}, []);
return (
<AuthContext.Provider value={{ currentUser }}>
{children}
</AuthContext.Provider>
);
};
I use the AuthContextProvider in index.js:
import { AuthContextProvider } from './contexts/AuthContext';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<AuthContextProvider>
<React.StrictMode>
<App />
</React.StrictMode>
</AuthContextProvider>
);
reportWebVitals();
The Chrome console throws this error from App.js
:
Uncaught ReferenceError: ProtectedRoute is not defined
What am I doing wrong?
UPDATE
This version of App.js throws a React Hook "useContext" cannot be called at the top level
error:
const { currentUser } = useContext(AuthContext);
const ProtectedRoute = ({ children, currentUser }) => {
if (!currentUser) {
return <Navigate to="/auth/login" />;
}
return children
};
const router = createBrowserRouter(
createRoutesFromElements(
<Route path="/" element={<RootLayout />}>
<Route index
element={
<ProtectedRoute currentUser={ currentUser }>
<Chat />
</ProtectedRoute>
} />
<Route path="auth" element={<AuthLayout />}>
<Route path="register" element={<Register />} />
<Route path="login" element={<Login />} />
</Route>
</Route>
)
);
export default function App() {
return (
<RouterProvider router={router} />
);
}
2
Answers
For RouterProvider from react-router-dom you don’t have ProtectedRoute prop.
Let’s address the issues step by step:
Issue 1: Uncaught ReferenceError: ProtectedRoute is not defined
The error you’re getting is due to the fact that you’re trying to use ProtectedRoute inside the router definition before it is actually defined. JavaScript functions get hoisted, but the const and let variable declarations do not.
To solve this, we can refactor the App component structure to define ProtectedRoute before using it.
Issue 2: React Hook "useContext" cannot be called at the top level
Hooks can only be called inside the body of a function component. Your update tried to call useContext outside of any component, which is why React throws an error.
Here’s a refactored version of your `App.js:
By placing the ProtectedRoute component and the useContext hook inside the App component, you’ll ensure hooks are used in the correct context and variables are accessed in the proper sequence.