I’m trying to create a protected route component in my React application using react-router-dom, but I’m encountering an issue.
What I’ve Tried:
Ensured ProtectedRoute is exported correctly.
Confirmed the useAuth hook is working as expected.
Verified that components are being imported correctly.
What Could Be Wrong?
I appreciate any help to resolve this issue. Thanks!
// ./components/ProtectedRoute
import React from 'react';
import { Navigate, Outlet } from 'react-router-dom';
import { useAuth } from '../hooks/useAuth';
export const ProtectedRoute = ({ children }) => {
const { user } = useAuth();
if (!user) {
return <Navigate to="/login" replace />;
}
return children ? children : <Outlet />;
};
./hooks/useAuth:
import React, { createContext, useContext, useMemo } from 'react';
import { useNavigate } from 'react-router-dom';
const AuthContext = createContext();
export const AuthProvider = ({ children }) => {
const [user, setUser] = useLocalStorage('user', null);
const navigate = useNavigate();
const login = async (data) => {
setUser(data);
navigate('/profile');
};
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);
};
And I try to protect the dashboard for test:
import React from 'react';
import { Navigate } from 'react-router-dom';
import { useSelector } from 'react-redux';
import { ProtectedRoute } from './components/ProtectedRoute';
import { useAuth } from './hooks/useAuth';
import { element } from 'prop-types';
const Dashboard = React.lazy(() => import('./views/dashboard/Dashboard'));
const routes = [
{ path: '/', exact: true, name: 'Home' },
{ path: 'dashboard', name: 'Dashboard', element: <ProtectedRoute><Dashboard /></ProtectedRoute> },
];
export default routes;
But I always get this error:
Warning: React.jsx: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: <ProtectedRoute />. Did you accidentally export a JSX literal instead of a component?
at AppContent
at div
at div
at div
at DefaultLayout
at RenderedRoute (http://localhost:3000/node_modules/.vite/deps/react-router-dom.js?v=b83527eb:3653:5)
app.js:
import React, { Suspense, useEffect } from 'react'
import { HashRouter, Route, Routes } from 'react-router-dom'
import { useSelector } from 'react-redux'
import { CSpinner, useColorModes } from '@coreui/react'
import './scss/style.scss'
// Containers
const DefaultLayout = React.lazy(() => import('./layout/DefaultLayout'))
// Pages
const Login = React.lazy(() => import('./views/pages/login/Login'))
const Register = React.lazy(() => import('./views/pages/register/Register'))
const Page404 = React.lazy(() => import('./views/pages/page404/Page404'))
const Page500 = React.lazy(() => import('./views/pages/page500/Page500'))
const DeviceItem = React.lazy(() => import('./views/device/DeviceItem'));
const App = () => {
const { isColorModeSet, setColorMode } = useColorModes('coreui-free-react-admin-template-theme')
const storedTheme = useSelector((state) => state.theme)
useEffect(() => {
const urlParams = new URLSearchParams(window.location.href.split('?')[1])
const theme = urlParams.get('theme') && urlParams.get('theme').match(/^[A-Za-z0-9s]+/)[0]
if (theme) {
setColorMode(theme)
}
if (isColorModeSet()) {
return
}
setColorMode(storedTheme)
}, []) // eslint-disable-line react-hooks/exhaustive-deps
return (
<HashRouter>
<Suspense
fallback={
<div className="pt-3 text-center">
<CSpinner color="primary" variant="grow" />
</div>
}
>
<Routes>
<Route exact path="/login" name="Login Page" element={<Login />} />
<Route exact path="/register" name="Register Page" element={<Register />} />
<Route exact path="/404" name="Page 404" element={<Page404 />} />
<Route exact path="/500" name="Page 500" element={<Page500 />} />
<Route exact path="/device/panel/:id" element={<DeviceItem/>} /> {/* Defina a rota para o DeviceItem com um parâmetro de ID */}
<Route path="*" name="Home" element={<DefaultLayout />} />
</Routes>
</Suspense>
</HashRouter>
)
}
export default App
AppContent.js:
import React, { Suspense } from 'react'
import { Navigate, Route, Routes } from 'react-router-dom'
import { CContainer, CSpinner } from '@coreui/react'
// routes config
import routes from '../routes'
const AppContent = () => {
return (
<CContainer className="px-4" lg>
<Suspense fallback={<CSpinner color="primary" />}>
<Routes>
{routes.map((route, idx) => {
return (
route.element && (
<Route
key={idx}
path={route.path}
exact={route.exact}
name={route.name}
element={<route.element />}
/>
)
)
})}
<Route path="/" element={<Navigate to="dashboard" replace />} />
</Routes>
</Suspense>
</CContainer>
)
}
export default React.memo(AppContent)
AppBreadcrumb.js:
import React from 'react'
import { useLocation } from 'react-router-dom'
import routes from '../routes'
import { CBreadcrumb, CBreadcrumbItem } from '@coreui/react'
const AppBreadcrumb = () => {
const currentLocation = useLocation().pathname
const getRouteName = (pathname, routes) => {
const currentRoute = routes.find((route) => route.path === pathname)
return currentRoute ? currentRoute.name : false
}
const getBreadcrumbs = (location) => {
const breadcrumbs = []
location.split('/').reduce((prev, curr, index, array) => {
const currentPathname = `${prev}/${curr}`
const routeName = getRouteName(currentPathname, routes)
routeName &&
breadcrumbs.push({
pathname: currentPathname,
name: routeName,
active: index + 1 === array.length ? true : false,
})
return currentPathname
})
return breadcrumbs
}
const breadcrumbs = getBreadcrumbs(currentLocation)
return (
<CBreadcrumb className="my-0">
<CBreadcrumbItem href="/">Home</CBreadcrumbItem>
{breadcrumbs.map((breadcrumb, index) => {
return (
<CBreadcrumbItem
{...(breadcrumb.active ? { active: true } : { href: breadcrumb.pathname })}
key={index}
>
{breadcrumb.name}
</CBreadcrumbItem>
)
})}
</CBreadcrumb>
)
}
export default React.memo(AppBreadcrumb)
2
Answers
With the soluction from @nithin reddy, and this update from ProtectedRoute works well:
Updated AppContent.js
Route component expects "element" prop to be JSX which you already formed in routes JSON config and now you don’t need to again explicitly wrap route.element in JSX