1

I have been working on a react login service for a website (from a YouTube video) and I have been running into an issue. To preface, JavaScript is not a language I am particularly good at and I know very little about react, so please forgive any gaping holes in my knowledge of the topic.

I have attempted to make a context called PrivateRoute that serves as a Route accessible only by a logged in user. The website works when the / extension is under a Route tag, but when I change it to my custom PrivateRoute tag it says that I cannot call anything other than a Route tag inside a Routes tag. I believe the video that I am watching is using a different version of NPM and react, so that may be the cause for a lot of the issues. If that is the case what is the common practice for achieving a website, accessible only by a logged in user?

This is my code at the moment; I believe there are only two applicable files but please comment and let me know if any additional files would be helpful!

./contexts/PrivateRoute.js

import React from 'react';
import { Route, Navigate } from 'react-router-dom';
import { useAuth } from '../contexts/AuthContext';

export default function PrivateRoute({ element: Element, ...rest }) {
    const { currentUser } = useAuth();
 
    return (
        <Route 
            {...rest} 
            render={props => {
                return currentUser ? <Route {...rest} element={<Element {...props} />} /> : <Navigate to="/login" replace={true} />;
            }}  
        />
    );
}

./components/App.js

import React from "react";
import Signup from './Signup';
import Dashboard from './Dashboard';
import Login from './Login';
import { Container } from 'react-bootstrap';
import { AuthProvider } from '../contexts/AuthContext';
import {BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import PrivateRoute from './PrivateRoute';

function App() {
  return (
    <Container className="d-flex align-items-center justify-content-center" style={{ minHeight: "100vh" }}>
      <div className="w-100" style={{ maxWidth: "400px" }}>
        <Router>
          <AuthProvider>
            <Routes>                                                  //Issue here, will not
              <PrivateRoute exact path="/" element={<Dashboard />} /> //allow PrivateRoute tag
              <Route path="/signup" element={<Signup />} />           //inside Routes tags
              <Route path="/login" element={<Login />} />
            </Routes>
          </AuthProvider>
        </Router>
      </div>
    </Container>    
  );
}

export default App;

I have attempted to turn the PrivateRoutes and Dashboard into one file/function but this was met with a lot of errors. I believe the issue might lie somewhere in the version of React I'm using coupled with my lack of knowledge over the changes between versions. Thanks in advance for any help!

Edit: I have now remade the PrivateRoutes file again to no avail. I am still getting the error code Uncaught Error: [PrivateRoute] is not a <Route> component. All component children of <Routes> must be a <Route> or <React.Fragment> I updated the files above accordingly.

Spailll
  • 23
  • 6
  • can you post your err logs? – Jagadish Shrestha Dec 24 '22 at 13:38
  • Just posted @JagadishShrestha . Thanks for helping me improve the question! – Spailll Dec 24 '22 at 14:09
  • You are looking at a very old (2+yrs) tutorial but using a newer version of `react-router-dom`. The first comment on the YouTube page describes the issue. Also, the issue is detailed [buried in the documentation of react-router](https://gist.github.com/mjackson/d54b40a094277b7afdd6b81f51a0393f#route-composition-in-react-router-v6); You'll find it much easier to think about protected routes if you write them as intended in the examples in those docs. – Randy Casburn Dec 24 '22 at 15:09
  • Thank you for the response! I see that, and have been trying to adapt the video to newer versions but its been a steep learning curve. I think I got it figured out now. – Spailll Dec 24 '22 at 16:28

2 Answers2

0

Inside private routes wrap your route component inside routes component :

 <Routes>
    <Route 
        {...rest} 
        render={props => {
            return currentUser ? <Route {...rest} element={<Element 
 {...props} />} /> : <Navigate to="/login" replace={true} />;
        }}  
    /></Routes>

Inside your app component place your privateRoute component outside routes component:

<Container className="d-flex align-items-center justify-content-center" style={{ minHeight: "100vh" }}>
  <div className="w-100" style={{ maxWidth: "400px" }}>
    <Router>
      <AuthProvider>
         <PrivateRoute exact path="/" element={<Dashboard />} /> 
        <Routes>                                                 
          <Route path="/signup" element={<Signup />} />       
          <Route path="/login" element={<Login />} />
        </Routes>
      </AuthProvider>
    </Router>
  </div>
</Container>    

The only valid component inside your routes component is the route component; you cannot use other components inside it, and these components must be wrapped by the Router component

Youssouf Oumar
  • 29,373
  • 11
  • 46
  • 65
  • This code is completely incorrect. The `Route` component hasn't any `render` prop, it is mixing RRD v5 and v6 `Route` props/syntax. I don't know how it possibly could have resolved any issues. – Drew Reese Dec 29 '22 at 00:58
0

I ended up taking the routes entirely out of the PrivateRoutes Method

import { useAuth } from '../contexts/AuthContext';
import Dashboard from './Dashboard';
import Login from './Login';

export default function PrivateRoutes () {
    const { currentUser } = useAuth();
 
    return currentUser ? <Dashboard /> : <Login />
}

and implemented it as

<Routes>
              <Route exact path="/" element={<PrivateRoute />} />
              <Route path="/signup" element={<Signup />} />
              <Route path="/login" element={<Login />} />
</Routes>

This way took a lot of the complication out of both files, and I don't think it would be any less secure

Spailll
  • 23
  • 6