I've had a problem with migration between react-router-dom v5 to v6. After update my package I do a few upgrades in the code. Below is my version of code in v5 and with v6 and also Routing component which give me a problem.
v5
export default function App() {
return (
<div className="App">
<Switch>
<AppContextProvider>
<Routing />
</AppContextProvider>
</Switch>
</div>
);
}
v6
export default function App() {
return (
<div className="App">
<AppContextProvider>
<Routes>
<Routing />
</Routes>
</AppContextProvider>
</div>
);
}
<Routing /> component
const Routing = (): JSX.Element => {
return (
<>
{publicRouting.map(({ path, component }, i) => (
<Route key={i} path={path} element={component} />
))}
{isAuthenticated() && (
<Main>
{routing.map(({ path, component }, i) => (
<Route key={i} path={path} element={component} />
))}
</Main>
)}
</>
);
};
Right now console throw me this error:
index.tsx:19 Uncaught Error: [Routing] is not a <Route> component. All component children of <Routes> must be a <Route> or <React.Fragment>
can someone tell me how to solve this problem? It looks like this component has incompatible type and cannot go as children between Routes component, but why they changed it? How to correct my Routing component?
Thanks for any help!
In react-router-dom v6 the Routes component can have only Route or React.Fragment as a valid child, and the Route component can only have Routes or another Route component as parent.
The Routes component effectively replaced the RRDv5 Switch component in that it handles the route matching and rendering logic, and is now required to directly wrap the Route components it's managing.
Move the Routes component into Routing to wrap directly the Route components being mapped.
export default function App() {
return (
<div className="App">
<AppContextProvider>
<Routing />
</AppContextProvider>
</div>
);
}
...
const Routing = (): JSX.Element => {
return (
<>
<Routes>
{publicRouting.map(({ path, component }, i) => (
<Route key={i} path={path} element={component} />
))}
</Routes>
{isAuthenticated() && (
<Main>
<Routes>
{routing.map(({ path, component }, i) => (
<Route key={i} path={path} element={component} />
))}
</Routes>
</Main>
)}
</>
);
};
Related
I have a component that needs to be rendered under the Home page component. Now I have nested, but the component is not rendered. Although if you do without nesting, then everything works. How can I do this?
export default function App() {
return (
<div className="App">
<Routes>
<Route path="/" element={<Home />}>
<Route path="route1" element={<Route1 />} />
</Route>
</Routes>
</div>
);
}
export default function Route1() {
return (
<>
<h2>Route1</h2>
<Outlet />
</>
);
}
export default function Home() {
return (
<>
<h1>Home Page</h1>
</>
);
}
The Home component is rendered as a Layout Route so it should render an Outlet component for the nested routes.
export default function App() {
return (
<div className="App">
<Routes>
<Route path="/" element={<Home />}> // <-- Layout route
<Route path="route1" element={<Route1 />} /> // <-- Nested route
</Route>
</Routes>
</div>
);
}
export default function Home() {
return (
<>
<h1>Home Page</h1>
<Outlet /> // <-- Nested routes render element here
</>
);
}
Route1 only needs to render an Outlet if it is also a layout route. The Outlet can be removed if this is not the case.
export default function Route1() {
return (
<>
<h2>Route1</h2>
</>
);
}
Try wrapping your Routes with Router
Link to my stackblitz and play with it..
Also, a quick read >> https://dev.to/tywenk/how-to-use-nested-routes-in-react-router-6-4jhd
import * as React from 'react';
import {
BrowserRouter as Router,
Route,
Routes,
Link,
Outlet,
} from 'react-router-dom';
export default function App() {
return (
<div className="App">
<Router>
<nav>
<Link to="/">Home</Link> <Link to="route1">Route 1</Link>
</nav>
<Routes>
<Route path="/" element={<Home />}>
<Route path="route1" element={<Route1 />} />
</Route>
</Routes>
</Router>
</div>
);
}
function Route1() {
return <h1>"In Route 1"</h1>;
}
function Home() {
return (
<React.Fragment>
<h1>"In Home"</h1>
<Outlet />
</React.Fragment>
);
}
Using <nav> is optional.. added just for demo.. you can use your own method for routing actions (using links, buttons, etc.)
I have a web app which is under development which is just like google drive using firebase. I have this useParams() in Dashboard Screen which is the main page of the App with All the different Folder Routes. So for this screen i have used useParams and now when i console.log(params) it shows an empty object {} and also when i click the button it does not navigate only the URL changes
Github Code :- https://github.com/KUSHAD/RDX-Drive/
In App.js
import { BrowserRouter, Switch, Route } from 'react-router-dom';
import PrivateRoute from './Components/Route/PrivateRoute';
import Dashboard from './Screens/Main/Dashboard';
import ViewProfile from './Screens/Profile/ViewProfile';
import Signup from './Screens/Auth/Signup';
import Login from './Screens/Auth/Login';
import ForgotPassword from './Screens/Auth/ForgotPassword';
function App() {
return (
<>
<div className='App'>
<div className='main'>
<BrowserRouter>
<Switch>
{/* Drive */}
<PrivateRoute exact path='/' component={Dashboard} />
<PrivateRoute
exact
path='/folder/:folderId'
component={Dashboard}
/>
{/* Profile */}
<PrivateRoute path='/profile' component={ViewProfile} />
{/* Auth */}
<Route path='/signup' component={Signup} />
<Route path='/login' component={Login} />
<Route path='/forgot-password' component={ForgotPassword} />
</Switch>
</BrowserRouter>
</div>
</div>
</>
);
}
export default App;
In Dashboard.js
import NavBar from '../../Components/Shared/NavBar';
import Container from 'react-bootstrap/Container';
import AddFolderButton from '../../Components/Main/AddFolderButton';
import { useDrive } from '../../services/hooks/useDrive';
import Folder from '../../Components/Main/Folder';
import { useParams } from 'react-router-dom';
export default function Dashboard() {
const params = useParams();
console.log(params);
const { folder, childFolders } = useDrive();
return (
<div>
<NavBar />
<Container fluid>
<AddFolderButton currentFolder={folder} />
{childFolders.length > 0 && (
<div className='d-flex flex-wrap'>
{childFolders.map(childFolder => (
<div
key={childFolder.id}
className='p-2'
style={{ maxWidth: '250px' }}>
<Folder folder={childFolder} />
</div>
))}
</div>
)}
</Container>
</div>
);
}
Issue
After scouring your repo looking for the usual suspect causes for "it does not navigate only the URL changes" I didn't find anything odd like multiple Router components, etc. I think the issue is your PrivateRoute component isn't passing the props to the Route correctly. You're destructuring a prop called rest and then spread that into the Route, but you don't pass a rest prop to the PrivateRoute
export default function PrivateRoute({ component: Component, rest }) { // <-- rest prop
const { currentUser } = useAuth();
return (
<Route
{...rest} // <-- nothing is spread/passed here
render={props => {
return currentUser ? (
<Component {...props} />
) : (
<Redirect to='/login' />
);
}}
/>
);
}
The routes, these are not passed any prop named rest:
<PrivateRoute exact path='/' component={Dashboard} />
<PrivateRoute
exact
path='/folder/:folderId'
component={Dashboard}
/>
What I believe to be occurring here is the exact and path props aren't passed to the underlying Route component and so the first nested component of the Switch is matched and rendered, the "/" one that doesn't have any route params.
Solution
The fix is to spread the rest of the passed props into rest instead of destructuring a named rest prop.
export default function PrivateRoute({ component: Component, ...rest }) {
const { currentUser } = useAuth();
return (
<Route
{...rest}
render={props => {
return currentUser ? (
<Component {...props} />
) : (
<Redirect to='/login' />
);
}}
/>
);
}
An improvement of your private route may be as follows:
export default function PrivateRoute(props) {
const { currentUser } = useAuth();
return currentUser ? (
<Route {...props} />
) : (
<Redirect to='/login' />
);
}
This checks your user authentication and renders either a Route or Redirect. This pattern allows you to use all the regular Route props so you aren't locked into using the render prop to render the component.
I am developing an react js app using functional components.
I am trying to reuse components in the code. I have a component Frame which has Sider and Header. I am trying to add Content component to that frame to display in the middle, but its not working though.
Frame.tsx
const Frame : React.FC = (props) => {
const [collapsed, onCollapse] = useState(false);
const Content = props.content;
console.log('Content: ',Content);
return (
<Layout>
<SideBar state={{ collapsed: [collapsed, onCollapse]}}/>
<Layout>
<HeaderBar state={{ collapsed: [collapsed, onCollapse]}}/>
{Content}
<Footer>footer</Footer>
</Layout>
</Layout>
);
}
export default Frame;
PublicRoute.tsx
interface PublicRouteProps extends RouteProps {
// tslint:disable-next-line:no-any
component: any;
isAuthorized: boolean;
content: Content;
}
const PublicRoute = (props: PublicRouteProps) => {
const { component: Component, isAuthorized, content: Dummy, ...rest } = props;
return (
<Route
{...rest}
render={(routeProps) =>
isAuthorized ? (
<Component {...routeProps}/>
) : (
<Redirect
to={{
pathname: '/login',
state: { from: routeProps.location }
}}
/>
)
}
/>
);
};
export default PublicRoute;
App.tsx
return (
<BrowserRouter>
<div>
<Switch>
<PublicRoute path="/" component={Frame} exact isAuthorized={true} content={Dummy}/>
<Route path="/login" component={NewLogin} exact isAuthorized={true}/>
</Switch>
</div>
</BrowserRouter>
);
I am not able to pass contents dynamically and I am not sure whats wrong.
Thank you for your time.
You have to pass to the component with <Content /> otherwise it won't be instantiated.
Here's a full example
import React from "react";
import "./styles.css";
function Parent({content}) {
return (
<div>
{content}
</div>
)
}
function Content() {
return (
<h1>Hello</h1>
)
}
export default function App() {
return (
<div className="App">
<Parent content={<Content/>} />
</div>
);
}
You pass the components like so: .
Try something like this:
return (
<BrowserRouter>
<div>
<Switch>
<PublicRoute path="/" component={<Frame />} exact isAuthorized={true} content={<Dummy />}/>
<Route path="/login" component={<NewLogin />} exact isAuthorized={true}/>
</Switch>
</div>
</BrowserRouter>
);
I guess we can use the createElement function in place of {content}.
{React.createElement("NewLogin");}
I am trying to create reusable menu component which will be totally based on react-router. I could show the menu items but when clicked, the component that need to be rendered is not rendered. But if i define routes outside of menu component like inside <Router> component it works. How can i make it work on either way?
Here is how i have done
Menu.js
const Menu = ({ children }) => {
return (
<>
<Header padding>
<Container>
<Row>
<NavTabs>{children}</NavTabs>
</Row>
</Container>
</Header>
</>
);
};
export default Menu;
MenuItem.js
const MenuItem = ({ to, children, match }) => {
return (
<>
<NavItem>
<NavLink to={`${match.path}${to}`}>{children}</NavLink>
</NavItem>
</>
);
};
export default withRouter(MenuItem);
This is how I am using it
const items = [
{ id: 1, name: "Home", path: "home", component: HomeC },
{ id: 2, name: "Jobs", path: "jobs", component: Jobs }
];
const Home = () => {
return (
<>
Menu
<Menu>
{items.map(item => (
<MenuItem key={item.id} to={item.path}>
{item.name}
</MenuItem>
))}
{/* if Route is defined inside Menu component, render component from here only else from outside */}
<>
<Switch>
<Route path="/home" component={HomeC} />
<Route path="/jobs" component={Jobs} />
</Switch>
</>
</Menu>
</>
);
};
export default Home;
Here is the full code with demo
https://codesandbox.io/s/vq0w113140
You can remove the Routes from Menu.js
and place them in the index.js just below the Home Component Route.
you can use this code:
Home.js:
const Home = () => {
return (
<>
Menu
<Menu>
{items.map(item => (
<MenuItem key={item.id} to={item.path}>
{item.name}
</MenuItem>
))}
{/* if Route is defined inside Menu component, render component from here only else from outside */}
<></>
</Menu>
</>
);
};
index.js:
please remove exact in first route
function App() {
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<Router>
<Route path="/" component={Home} />
<Route path="/jobs" render={() => <h1>jobs component</h1>} />
<Route path="/home" render={() => <h1>Home component</h1>} />
</Router>
</div>
);
}
Here is the working code link: https://codesandbox.io/s/pjx7pv581j
You can use Switch from react-router-dom. Switch returns only one first matching route.
function App() {
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<BrowserRouter>
<Switch>
<Route path="/" component={Home} />
<Route path="/jobs" component={Jobs} />
</Switch>
</BrowserRouter>
</div>
);
}
Here is my solution: https://codesandbox.io/embed/wn4j5525n8
The problem is in your index.js, your router is not matching any of the Routes for /jobs.
<Router>
<Route exact path="/" component={Home} />
<Route path="/hello" render={() => <h1>Hello</h1>} />
<Route path="/home" render={() => <h1>Hello</h1>} />
</Router>
You'll need to add a route here for /jobs, or if you'd like to default the Home component, wrap the routes in a Switch and add a Route with no path= at the bottom, with the component={Home} option.
EDIT - Switch example
<Router>
<Switch>
<Route path="/hello" render={() => <h1>Hello</h1>} />
<Route path="/home" render={() => <h1>Hello</h1>} />
<Route component={Home} />
<Switch>
</Router>
With this example, any route other than /hello and /home will render the Home component. A switch works much like a switch in code would - it will pick the first one from the list that matches, and render that. If none match, it will render the last item in the list.
This would allow you to render the Jobs component with the job routes inside the Home component, to include the menu.
I want to use layouts with my react-router-dom, at this moment i am doing that like this
const DefaultLayout = ({children, ...rest}) => {
return (
<div className={styles.wrapper}>
<Header/>
{children}
<Footer/>
</div>
)
};
const DefaultRoute = ({component: Component, ...rest}) => {
return (
<Route {...rest} render={matchProps => (
<DefaultLayout>
<Component {...matchProps} />
</DefaultLayout>
)}/>
)
};
render(
<Provider store={store}>
<HashRouter>
<Switch>
<DefaultRoute exact path="/" component={AdvertList}/>
<DefaultRoute exact path="/user" component={UserOptions}/>
<Route path="/login" children={Login}/>
<Route render={
() => (
<div>
Not found
</div>
)
}/>
</Switch>
</HashRouter>
</Provider>,
document.querySelector('#app')
);
it works okay, both UserOptions and AdvertList components are rendered inside DefaultLayout, and Login component does not, but in official documentation i didn't find solution like that, instead there is "nested routing" where you adding new nested routes in subclasses, like
if you need default layout u make it on route /, then if you need advert list with that layout, in layout component you defined route /adverts and adding link to it, and so on, each sub component uses layout of parent one.
But in my case there is already product list on route /, and i need to change that content to other products list regarding link pressed, not to add to parent layout, but to change it part. Here is my code,
const { BrowserRouter, Route, Switch, Link } = window.ReactRouterDOM;
const { Component } = window.React;
const About = () => ('About');
const MiscProducts = () => ('Misc products');
class AdvertsList extends Component {
render() {
return (
<div className="container">
<header>Header</header>
<main>
<nav>
<Link to="/miscProducts">Misc Products</Link> #
<Link to="/about">About</Link>
</nav>
<div className="content">
Main Products
</div>
</main>
<footer>Footer</footer>
<Route path="/miscProducts" component={MiscProducts} />
</div>
)
};
};
class App extends Component {
render() {
return (
<BrowserRouter>
<Switch>
<Route path="/" component={AdvertsList} />
<Route path="/about" component={About} />
<Route path="*" render={
() => (
<div>
Not found
</div>
)
}/>
</Switch>
</BrowserRouter>
);
}
}
ReactDOM.render(<App />, document.querySelector("#app"));
http://jsfiddle.net/gmcke2a4/6/ here main products loaded by default, and when i press misc products, misc products must be loaded instead of main one.
p.s. And why about doesn't work?
Login Fix
<Route path="/login" children={Login}/> this seems wrong because children component expects function which return nodes i think.Try <Route path="/login" children={() => (</Login />)}
Layout
But in my case there is already product list on route /, and i need to
change that content to other products list regarding link pressed, not
to add to parent layout
You can create component which renders specific products like this.
const MainProducts = () => 'Main Products'
const GummyBearsProducts = () => 'GummyBears'
const Products = props => (
<div className="products-container">
<Switch>
<Route path={`${props.location.pathname}`} component={MainProducts}/>
<Route path={`${props.location.pathname}/gummy-bears`} components={GummyBearProducts}/>
</Switch>
</div>
)
And then use it as follows.
class AdvertsList extends Component {
render() {
return (
<div className="container">
<header>Header</header>
<main>
<nav>
<Link to="/products">Products</Link> #
<Link to="/about">About</Link>
</nav>
<div className="content">
<Route path="/products" component={Products} />
</div>
</main>
<footer>Footer</footer>
</div>
)
};
};
React router is great in rendering specific components.I hope it answers your question.Cheers!
If you are using react-router-dom v6. Then follow the below procedure to configure react-router-dom,
App.jsx:
import './App.css';
import { BrowserRouter, Routes, Route } from 'react-router-dom'
import Home from './Components/Home'
import About from './Components/About'
import Layout from './Components/Layout'
function App() {
return (
<div className="App">
<BrowserRouter>
<Routes>
<Route path="/" element={<Layout />}>
<Route path="/" element={<Home />} />
<Route path="about" element={<About />} />
</Route>
</Routes>
</BrowserRouter>
</div>
);
}
export default App;
After configuring the react router in App.jsx. I am creating 3 components Home, About and Layout. Home and About are regular components and Layout component is to handle the Layout part in react-router-dom using Outlet.
Layout.jsx
import { Outlet, Link } from 'react-router-dom'
export default function Layout() {
return (
<>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
<Outlet />
</>
)
}
Home.jsx
export default function Home() {
return (
<>
<p>This is Home</p>
</>
)
}
About.jsx
export default function About() {
return (
<>
<p>This is About Us</p>
</>
)
}