Why you can't render from 2 JSX element? - javascript

My goal is to render <Route /> with array.map method. Right now, I am trying to pass the return value using react hook, but the problem is localhost:3000/login return <div>Login</div> while localhost:3000/dashboard return nothing.
My expected result is when I visit localhost:3000/dashboard the JSX.element return <div>Dashboard</div>
App.js
import React from "react";
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
const PublicRouteComponents = () => {
return <Route exact path="/login" render={() => <div>Login</div>} />
};
const PrivateRouteComponents = () => {
return <Route exact path="/dashboard" render={() => <div>Dashboard</div>} />
};
function App() {
return (
<Router>
<Switch>
{/* <Route exact path="/login" render={() => <div>Login</div>} />
<Route exact path="/dashboard" render={() => <div>Dashboard</div>} /> */}
<PublicRouteComponents />
<PrivateRouteComponents />
</Switch>
</Router>
);
}
export default App;
edit:
the current solution is to give up the react hook and went straight to JSX.element
function App() {
const PublicRouteComponents = PublicRoutes.map(
({restricted, component, path}, key) => <PublicRoute restricted={restricted} component={component} exact path={path} key={key} />
)
const PrivateRouteComponents = PrivateRoutes.map(
({component, path}, key) => <PrivateRoute component={component} exact path={path} key={key} />
)
return (
<Router>
<Switch>
{PublicRouteComponents}
{PrivateRouteComponents}
</Switch>
</Router>
);
}
But, my instructor told me, if you gave an expression instead of class, when the component inside {PublicRouteComponents} changes, the app will got re-rendered. Instead if you use <PublicRouteComponents />, when the class change, only <PublicRouteComponents /> will be re-rendered.
I wish to achieve that.

After playing around a bit, I found the underlying issue in your first code sample.
Basically, React router demands that the route path and the exact props to be set in the children of the Router component.
import React from "react";
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
const LoginRoute = () => {
return <Route render={() => <div>Login</div>} />;
};
const DashboardRoute = () => {
return <Route render={() => <div>Dashboard</div>} />;
};
export default function App() {
return (
<Router>
<Switch>
<DashboardRoute exact path="/dashboard" />
<LoginRoute exact path="/login" />
</Switch>
</Router>
);
}
If you move the path and exact props to the DashboardRoute and LoginRoute components, then react router is unable to match those routes.
See codesandbox

Related

Route is not working after upgrading to react-route v6

I had this MainRouting.js component:
import routes from '../Routes.js';
import { Routes, Route } from "react-router-dom";
import NotFound from '../Panel/NotFound';
const MainRouting = () => {
return (
<Routes>
{
routes.map(route =>
<Route
key={route.path}
path={route.path}
exact
element={route.component} />)
}
<Route path='*' element={<NotFound />} />
</Routes>
);
}
export default MainRouting;
It was working just fine. But now it's not working anymore after I upgraded to react-router v6.
I see this error:
index.js:1 Warning: Functions are not valid as a React child. This may happen if you return a Component instead of from render. Or maybe you meant to call this function rather than return it.
at Routes
at MainRouting
at div
at div
at div
at Panel
at LocalizationProvider
at Router
at BrowserRouter
How can I fix this?
Note: I already updated NotFound to match new syntax.
For me, I had to do this:
<Routes>
{
routes.map(route => {
const Component = route.component;
return <Route
key={route.path}
path={route.path}
element={<Component />}
/>
})
}
<Route
path='*'
element={<NotFound />}
/>
</Routes>
The key was these lines:
const Component = route.component;
And
element={<Component />}

Error: [PrivateRoute] is not a <Route> component. All component children of <Routes> must be a <Route> or <React.Fragment>

I'm using React Router v6 and am creating private routes for my application.
In file PrivateRoute.js, I've the code
import React from 'react';
import {Route,Navigate} from "react-router-dom";
import {isauth} from 'auth'
function PrivateRoute({ element, path }) {
const authed = isauth() // isauth() returns true or false based on localStorage
const ele = authed === true ? element : <Navigate to="/Home" />;
return <Route path={path} element={ele} />;
}
export default PrivateRoute
And in file route.js I've written as:
...
<PrivateRoute exact path="/" element={<Dashboard/>}/>
<Route exact path="/home" element={<Home/>}/>
I've gone through the same example React-router Auth Example - StackBlitz, file App.tsx
Is there something I'm missing?
I ran into the same issue today and came up with the following solution based on this very helpful article by Andrew Luca
In PrivateRoute.js:
import React from 'react';
import { Navigate, Outlet } from 'react-router-dom';
const PrivateRoute = () => {
const auth = null; // determine if authorized, from context or however you're doing it
// If authorized, return an outlet that will render child elements
// If not, return element that will navigate to login page
return auth ? <Outlet /> : <Navigate to="/login" />;
}
In App.js (I've left in some other pages as examples):
import './App.css';
import React, {Fragment} from 'react';
import {BrowserRouter as Router, Route, Routes} from 'react-router-dom';
import Navbar from './components/layout/Navbar';
import Home from './components/pages/Home';
import Register from './components/auth/Register'
import Login from './components/auth/Login';
import PrivateRoute from './components/routing/PrivateRoute';
const App = () => {
return (
<Router>
<Fragment>
<Navbar/>
<Routes>
<Route exact path='/' element={<PrivateRoute/>}>
<Route exact path='/' element={<Home/>}/>
</Route>
<Route exact path='/register' element={<Register/>}/>
<Route exact path='/login' element={<Login/>}/>
</Routes>
</Fragment>
</Router>
);
}
In the above routing, this is the private route:
<Route exact path='/' element={<PrivateRoute/>}>
<Route exact path='/' element={<Home/>}/>
</Route>
If authorization is successful, the element will show. Otherwise, it will navigate to the login page.
Only Route components can be a child of Routes. If you follow the v6 docs then you'll see the authentication pattern is to use a wrapper component to handle the authentication check and redirect.
function RequireAuth({ children }: { children: JSX.Element }) {
let auth = useAuth();
let location = useLocation();
if (!auth.user) {
// Redirect them to the /login page, but save the current location they were
// trying to go to when they were redirected. This allows us to send them
// along to that page after they login, which is a nicer user experience
// than dropping them off on the home page.
return <Navigate to="/login" state={{ from: location }} />;
}
return children;
}
...
<Route
path="/protected"
element={
<RequireAuth>
<ProtectedPage />
</RequireAuth>
}
/>
The old v5 pattern of create custom Route components no longer works. An updated v6 pattern using your code/logic could look as follows:
const PrivateRoute = ({ children }) => {
const authed = isauth() // isauth() returns true or false based on localStorage
return authed ? children : <Navigate to="/Home" />;
}
And to use
<Route
path="/dashboard"
element={
<PrivateRoute>
<Dashboard />
</PrivateRoute>
}
/>
Complement to reduce lines of code, make it more readable and beautiful.
This could just be a comment but I don't have enough points, so I'll
put it as an answer.
Dallin's answer works but Drew's answer is better! And just to complete Drew's answer on aesthetics, I recommend creating a private component that takes components as props instead of children.
Very basic example of private routes file/component:
import { Navigate } from 'react-router-dom';
const Private = (Component) => {
const auth = false; //your logic
return auth ? <Component /> : <Navigate to="/login" />
}
Route file example:
<Routes>
<Route path="/home" element={<Home />} />
<Route path="/user" element={<Private Component={User} />} />
</Routes>
I know that this is not exactly the recipe on how to make PirvateRoute work, but I just wanted to mention that the new documentation recommends a slightly different approach to handle this pattern with react-router v6:
<Route path="/protected" element={<RequireAuth><ProtectedPage /></RequireAuth>} />
import { Navigate, useLocation } from "react-router";
export const RequireAuth: React.FC<{ children: JSX.Element }> = ({ children }) => {
let auth = useAuth();
let location = useLocation();
if (!auth.user) {
return <Navigate to="/login" state={{ from: location }} />;
}
return children;
};
And you are supposed to add more routes inside ProtectedPage itself if you need it.
See the documentation and an example for more details. Also, check this note by Michael Jackson that goes into some implementation details.
Just set your router component to element prop:
<Routes>
<Route exact path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/dashboard" element={<Dashboard />} />
</Routes>
You can also check for upgrading from v5.
Remove the PrivateRoute component from your project and use the following code in your App.js files:
import {Navigate} from "react-router-dom";
import {isauth} from 'auth'
...
<Route exact path="/home" element={<Home/>}/>
<Route exact path="/" element={isauth ? <Dashboard/> : <Navigate to="/Home" />}/>
It's 2022 and I did something like below:
// routes.tsx
import { lazy } from "react";
import { Routes, Route } from "react-router-dom";
import Private from "./Private";
import Public from "./Public";
const Home = lazy(() => import("../pages/Home/Home"));
const Signin = lazy(() => import("../pages/Signin/Signin"));
export const Router = () => {
return (
<Routes>
<Route path="/" element={Private(<Home />)} />
<Route path="/signin" element={Public(<Signin />)} />
</Routes>
);
};
// Private.tsx
import { Navigate } from "react-router-dom";
import { useEffect, useState } from "react";
function render(c: JSX.Element) {
return c;
}
const Private = (Component: JSX.Element) => {
const [hasSession, setHasSession] = useState<boolean>(false);
useEffect(() => {
(async function () {
const sessionStatus = await checkLoginSession();
setHasSession(Boolean(sessionStatus));
})();
}, [hasSession, Component]);
return hasSession ? render(Component) : <Navigate to="signin" />;
};
export default Private;
Hope this helps!
React Router v6, some syntactic sugar:
{auth && (
privateRoutes.map(route =>
<Route
path={route.path}
key={route.path}
element={auth.isAuthenticated ? <route.component /> : <Navigate to={ROUTE_WELCOME_PAGE} replace />}
/>
)
)}
I tried all answers, but it always displayed the error:
Error: [PrivateRoute] is not a component. All component children of must be a or <React.Fragment>
But I found a solution ))) -
In PrivateRoute.js file:
import React from "react"; import { Navigate } from "react-router-dom";
import {isauth} from 'auth'
const PrivateRoute = ({ children }) => {
const authed = isauth()
return authed ? children : <Navigate to={"/Home" /> };
export default ProtectedRoute;
In the route.js file:
<Route
path="/"
element={
<ProtectedRoute >
<Dashboard/>
</ProtectedRoute>
}
/>
<Route exact path="/home" element={<Home/>}/>
Children of Routes need to be Route elements, so we can change the ProtectedRoute:
export type ProtectedRouteProps = {
isAuth: boolean;
authPath: string;
outlet: JSX.Element;
};
export default function ProtectedRoute({
isAuth,
authPath,
outlet,
}: ProtectedRouteProps) {
if (isAuth) {
return outlet;
} else {
return <Navigate to={{pathname: authPath}} />;
}
}
And then use it like this:
const defaultProps: Omit<ProtectedRouteProps, 'outlet'> = {
isAuth: //check if user is authenticated,
authPath: '/login',
};
return (
<div>
<Routes>
<Route path="/" element={<ProtectedRoute {...defaultProps} outlet={<HomePage />} />} />
</Routes>
</div>
);
This is the simple way to create a private route:
import React from 'react'
import { Navigate } from 'react-router-dom'
import { useAuth } from '../../context/AuthContext'
export default function PrivateRoute({ children }) {
const { currentUser } = useAuth()
if (!currentUser) {
return <Navigate to='/login' />
}
return children;
}
Now if we want to add a private route to the Dashboard component we can apply this private route as below:
<Routes>
<Route exact path="/" element={<PrivateRoute><Dashboard /></PrivateRoute>} />
</Routes>
For longer elements
<Router>
<div>
<Navbar totalItems={cart.total_items}/>
<Routes>
<Route exact path='/'>
<Route exact path='/' element={<Products products={products} onAddToCart={handleAddToCart}/>}/>
</Route>
<Route exact path='/cart'>
<Route exact path='/cart' element={<Cart cart={cart}/>}/>
</Route>
</Routes>
</div>
</Router>
Header will stay on all page
import React from 'react';
import {
BrowserRouter,
Routes,
Route
} from "react-router-dom";
const Header = () => <h2>Header</h2>
const Dashboard = () => <h2>Dashboard</h2>
const SurveyNew = () => <h2>SurveyNew</h2>
const Landing = () => <h2>Landing</h2>
const App = () =>{
return (
<div>
<BrowserRouter>
<Header />
<Routes >
<Route exact path="/" element={<Landing />} />
<Route path="/surveys" element={<Dashboard />} />
<Route path="/surveys/new" element={<SurveyNew/>} />
</Routes>
</BrowserRouter>
</div>
);
};
export default App;
<Route path='/' element={<Navigate to="/search" />} />
You can use a function for a private route:
<Route exact path="/login" element={NotAuth(Login)} />
<Route exact path="/Register" element={NotAuth(Register)} />
function NotAuth(Component) {
if (isAuth)
return <Navigate to="/" />;
return <Component />;
}
I'm using "react-router-dom": "^6.3.0" and this is how I did mine
PrivateRoute Component and Route
import {Route} from "react-router-dom";
const PrivateRoute = ({ component: Compontent, authenticated }) => {
return authenticated ? <Compontent /> : <Navigate to="/" />;
}
<Route
path="/user/profile"
element={<PrivateRoute authenticated={true} component={Profile} />} />
For the error "[Navigate] is not a <Route> component. All component children of <Routes> must be a <Route> or <React.Fragment>", use the following method maybe solved:
DefaultPage is when no match router. Jump to the DefaultPage. Here use the <Route index element={} /> to replace the
<Navigate to={window.location.pathname + '/kanban'}/>
See Index Routes
<Routes>
<Route path={'/default'} element={<DefaultPage/>}/>
<Route path={'/second'} element={<SecondPage/>}/>
{/* <Navigate to={window.location.pathname + '/kanban'}/> */}
<Route index element={<DefaultPage/>} />
</Routes>
import { BrowserRouter as Router, Routes, Route, Link } from "react-router-dom";
function App() {
return (
<Router>
<Routes>
<Route path="/" element={<h1>home page</h1>} />
<Route path="/seacrch" element={<h1>seacrch page</h1>} />
</Routes>
</Router>
);
}
export default App;

Redirect all url with # from HashRouter to BrowserRouter

I changed HashRouter to BrowserRouter and now I would like to redirect all urls to remove # from them. (Url are in e.g. mailing - so I have to do it).
I found a similar topic to this, but none of the solutions there works for me.
import { BrowserRouter } from 'react-router-dom'
class Index extends Component {
render() {
return (
<BrowserRouter>
<Switch>
<Route exact path={"/"} component={() => <HomePage />}/>
<Redirect from='/#/bus/:category' to '/bus/:category' />
<Route exact path='/bus/:category' component={BusCategory} />
</Switch>
</BrowserRouter>
)
}
}
ReactDOM.render(<Index />, document.getElementById("index"));
It's redirecting only to HomePage.
Next solution also not working:
import { BrowserRouter } from 'react-router-dom'
class Index extends Component {
render() {
const history = useHistory()
if (location.hash.startsWith('#/')) {
history.push(location.hash.replace('#', '')) // or history.replace
}
return (
<BrowserRouter>
<Switch>
<Route exact path={"/"} component={() => <HomePage />}/>
<Route exact path='/bus/:category' component={BusCategory} />
</Switch>
</BrowserRouter>
)
}
}
ReactDOM.render(<Index />, document.getElementById("index"));
and the last one also nothning :(
import { BrowserRouter } from 'react-router-dom'
class Index extends Component {
render() {
return (
<BrowserRouter>
<Switch>
<Route exact path={"/"} component={() => <HomePage />}/>
<Route exact path='/bus/:category' component={BusCategory} />
<Route path={"/bus/:category"} render={({ location }) => <Redirect strict to={location.hash.replace('#', '')} />} />
</Switch>
</BrowserRouter>
)
}
}
ReactDOM.render(<Index />, document.getElementById("index"));
I've probably already tried all the options, if anyone can help me I'll be extremely grateful.
// EDIT
now everything works, except the HomePage...
import { BrowserRouter } from 'react-router-dom'
class Index extends Component {
render() {
return (
<BrowserRouter>
<Switch>
<Route path={"/bus/:category"} render={({ location }) => <Redirect strict to={location.hash.replace('#', '')} />} />
<Route exact path={"/"} component={() => <HomePage />}/>
<Route exact path='/bus/:category' component={BusCategory} />
</Switch>
</BrowserRouter>
)
}
}
ReactDOM.render(<Index />, document.getElementById("index"));
You can use something like
import React, { useEffect } from 'react'
import { BrowserRouter, Route, useHistory, useLocation } from 'react-router-dom'
const Index = () => {
const history = useHistory()
const location = useLocation()
useEffect(() => {
if (location.hash) {
history.replace(location.hash.replace('#', ''))
}
}, [history, location.hash, location.pathname])
return (
<>
<Route exact path={'/'} render={() => <h1>Home</h1>} />
<Route
exact
path="/bus/:category"
render={({ match: { params } }) => <div>{params.category}</div>}
/>
</>
)
}
export default () => (
<BrowserRouter>
<Index />
</BrowserRouter>
)
Your previous code didn't work because <Index /> component was not the child of BrowserRouter component, because of which the location, as well as history, would be undefined.
Now if you try to visit localhost:3000/#/bus/car it would redirect to localhost:3000/bus/car.
Two points:
Why do you have to do a redirect at all? If I understand you correctly, you only used the BrowserRouter instead of the HashRouter. Exchanged, so to speak. This automatically eliminates the # and the pages "/#/bus/:category" simply no longer exist.
Is the line <Redirect from='/#/bus/:category' to '/bus/category' /> really correct? When I read this, it looks to me like you are referring to the URL "/bus/category". But actually you want something like "/bus/cars".

React router rendering two child components instead of just one

I'm trying to add routes to my application but for some reason there are two components being rendered to the page instead of just one.
My code looks like this (the relevant part):
import React from "react";
import Board from "./Components/Board";
import Navbar from "./Components/Navbar";
import TaskDetail from "./Components/TaskDetail";
import { LanesProvider } from "./Context/lanes.context";
import { TasksProvider } from "./Context/tasks.context";
import { BrowserRouter, Route, Switch } from "react-router-dom";
function App(props) {
const getTask = props => {
return <TaskDetail />;
};
return (
<>
<LanesProvider>
<TasksProvider>
<Navbar />
<Board />
<BrowserRouter>
<Switch>
<Route exact path="/" render={() => <Board />} />
<Route exact path="/board" render={() => <Board />} />
<Route exact path="/board/:taskName" render={() => getTask()} />
</Switch>
</BrowserRouter>
</TasksProvider>
</LanesProvider>
</>
);
}
Now basically when I'm navigating to "localhost/board/test" I would expect to just see the <TaskDetail/> component but instead I get the <Board /> AND <TaskDetail/>.
I didn't expect this to happen because of the exact boolean.
FYI: getTask() is only returning a component right now because I wanted to get the routes to work first before implementing further logic.
So far I could not find a solution to this.
Thank you in advance.
There is a <Board /> component outside your <BrowserRouter>
import React from "react";
import Board from "./Components/Board";
import Navbar from "./Components/Navbar";
import TaskDetail from "./Components/TaskDetail";
import { LanesProvider } from "./Context/lanes.context";
import { TasksProvider } from "./Context/tasks.context";
import { BrowserRouter, Route, Switch } from "react-router-dom";
function App(props) {
const getTask = props => {
return <TaskDetail />;
};
return (
<>
<LanesProvider>
<TasksProvider>
<Navbar />
<Board /> --> Remove this component from here
<BrowserRouter>
<Switch>
<Route exact path="/" render={() => <Board />} />
<Route exact path="/board" render={() => <Board />} />
<Route exact path="/board/:taskName" render={() => getTask()} />
</Switch>
</BrowserRouter>
</TasksProvider>
</LanesProvider>
</>
);
}

Programmatically navigate while using HashRouter

I'm using HashRouter for my routes in a react.js app. I then have a function which does the following:
this.props.history.push('/somePath');
The problem is, ever since I started using HashRouter, the page doesn't push to that path when that function gets called.
I logged this.props.history, and push was there.
How can I programmatically navigate while using HashRouter?
Note: I used withRouter
Here's the code:
import React, { Component } from 'react';
import { HashRouter, Route, Switch, Redirect, withRouter } from 'react-router-dom';
// Other imports
class App extends Component {
navigate = () => {
this.props.history.push('/route1');
console.log(this.props.history);
};
render() {
return (
<div className="App">
<Header />
<HashRouter>
<Switch>
<Route
path="/route1"
exact
render={() => (
<FirstRoute someSetting='setting1'/>
)}
/>
<Route
path="/route2"
exact
render={() => (
<SecondRoute anotherSetting='Really Cool'/>
)}
/>
<Route path="/404" component={NotFound} />
<Route exact path="/" render={() => <HomePage someSettings='Home Page' />} />
<Redirect to="/404" />
</Switch>
</HashRouter>
<BottomBar />
</div>
);
}
}
export default withRouter(App);

Categories

Resources