skip to Main Content

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


  1. Chosen as BEST ANSWER

    With the soluction from @nithin reddy, and this update from ProtectedRoute works well:

    import React from 'react';
    import { Route, Navigate } from 'react-router-dom';
    
    // Verifica se o usuário está autenticado
    const isAuthenticated = () => {
      const user = JSON.parse(localStorage.getItem('user'));
      return user && user.token;
    };
    
    // Componente de rota protegida
    const ProtectedRoute = ({ element, ...rest }) => {
      return isAuthenticated() ? (
        <Route {...rest} element={element} />
      ) : (
        <Navigate to="/login" replace />
      );
    };
    
    export default ProtectedRoute;
    

  2. Updated 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)

    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

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search