ShewaniPerera
ShewaniPerera

Reputation: 77

How to solve this problem ? PrivateRoute with React Router v6

PrivateRoute.js

import React from 'react';
import { Route, Navigate } from 'react-router';
    
    const PrivateRoute = ({component:Component, ...rest}) => {
        return <Route {...rest} component={(props) =>{
            const token = window.localStorage.getItem('token');
            if(token){
                return <Component {...props} />
            }else{
                return <Navigate to ={'/signin'}/>
            }
        }} /> 
    }
    
    export default PrivateRoute;

App.js

import React from "react";
import { createRoot } from "react-dom/client";
import App from "./main";
import { BrowserRouter as Router } from "react-router-dom";

createRoot(document.getElementById("app")).render(
    <Router>
        <App />
    </Router>,
);
export default App;

Main.jsx

import React from "react";
import { Routes, Route } from "react-router-dom";

import Test from "./Test";

function App() {
    return (
        <div>
            <Routes>
                <Route path='/' element={<Test />} />
            </Routes>
        </div>
    );
}

ERROR

Error: A <Route> is only ever to be used as the child of <Routes> element, never rendered directly. Please wrap your <Route> in a <Routes>.

Error: A is only ever to be used as the child of element, never rendered directly. Please wrap your in a . Error: A is only ever to be used as the child of element, never rendered directly. Please wrap your in a . Error: A is only ever to be used as the child of element, never rendered directly. Please wrap your in a .

I got some error like this. But I use new update of react router v6. Please help me to solve this problem.

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Shopping-Cart</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="app.jsx"></script>
</body>
</html>

app.js

import React from "react";
import { render } from "react-dom";
import { Routes, Route, BrowserRouter as Router } from "react-router-dom";
import InsertUser from "./Containers/User/InsertUser";
import ViewUser from "./Containers/User/ViewUser";
import ViewUsers from "./Containers/User/ViewUsers";

render(
  <Router>
    <Routes>
      <Route path="/" element={<InsertUser />} />
      <Route path="/viewUsers" element={<ViewUsers />} />
      <Route path="/viewUser/:id" element={<ViewUser />} />
    </Routes>
  </Router>,
  document.getElementById("app")
);

insertUser.jsx

import React, { useState } from "react";
import axios from "axios";
import { Link } from "react-router-dom";

function InsertUser() {
  const [firstName, setFirstName] = useState("");
  const [lastName, setLastName] = useState("");
  const insertData = () => {
    const user = {
      firstName: firstName,
      lastName: lastName,
    };
    axios
      .post("http://localhost:3000/api/user/add", user)
      //.then((event) => (window.location.href = "/view-inventory"))
      .catch((error) => {
        console.error("There was an error!", error);
      });
  };
  return (
    <div>
      <label for="fname">First name:</label>
      <input
        type="text"
        onChange={(e) => {
          setFirstName(e.target.value);
        }}
        id="fname"
        name="fname"
      />
      <br />
      <label for="lname">Last name:</label>
      <input
        type="text"
        onChange={(e) => {
          setLastName(e.target.value);
        }}
        id="lname"
        name="lname"
      />
      <br />
      <button
        onClick={() => {
          insertData();
        }}
        value="Submit"
      >
        insert
      </button>
      <br></br>
      <button
        onClick={() => {
          window.location.href = "/viewUsers";
        }}
      >
        View All Users
      </button>
    </div>
  );
}

export default InsertUser;

viweUsers.jsx

import React, { useEffect, useState } from "react";
import axios from "axios";

function ViewUsers() {
  const [tableData, setTableData] = useState("");

  useEffect(() => {
    axios
      .get("http://localhost:3000/api/user/getUsers")
      .then((response) => {
        setTableData(response.data);
      })
      .catch((error) => {
        console.error("There was an error!", error);
      });
  }, []);

  const deteleData = (id) => {
    axios
      .delete("http://localhost:3000/api/user/delete/" + id)
      .then(alert("Deleted"));
  };
  return (
    <div>
      <table>
        <thead>
          <tr>
            <th>FirstName</th>
            <th>LastName</th>
          </tr>
        </thead>
        <tbody>
          {tableData.length > 0
            ? tableData.map((data) => (
                <tr
                  key={data._id}
                  onClick={() => {
                    window.location.href = `/viewUser/${data._id}`;
                  }}
                >
                  <td>{data.firstName}</td>
                  <td>{data.lastName}</td>
                  <td>
                    <button
                      onClick={() => {
                        deteleData(data._id);
                      }}
                    >
                      Delete
                    </button>
                  </td>
                </tr>
              ))
            : null}
        </tbody>
      </table>
      <br></br>
    </div>
  );
}

export default ViewUsers;

Upvotes: 2

Views: 427

Answers (1)

Drew Reese
Drew Reese

Reputation: 202605

Route components are only valid as children of a Routes or other Route components, and as the error message says, you can't render it directly in an other component alone. In react-router-dom v6 custom Route components are no more, instead the suggested pattern is to use wrapper components to hold the auth logic and conditionally return the content or a Navigate redirect.

Convert PrivateRoute into a PrivateWrapper:

import { Navigate, Outlet } from "react-router-dom";

const PrivateWrapper = () => {
  const token = window.localStorage.getItem('token');
  return token ? <Outlet /> : <Navigate to='/signin' replace/>;
}

Render the wrapper around Route components you want to protect access to. The Outlet is where the children routes will be rendered

function App() {
  return (
    <div className="App">
      <Router>
        <Routes>
          <Route path="/" element={<PrivateWrapper/>}>
            <Route path="/" element={ <Home />}/>
          </Route>
          <Route path="/signin" element={ <Signin />} />
          <Route path="/signup" element={ <Signup />} />
        </Routes>
      </Router>
    </div>
  );
}

Upvotes: 1

Related Questions