I'm using MUIv5 and React-Router v6 in my project, in which I want to wrap a layout around my pages, but the pages aren't rendering and all i'm getting is an empty div
This is my App component:
import React from "react";
import { Routes, Route } from "react-router-dom";
import { CssBaseline } from "#mui/material";
import MainLanding from './routes/MainLanding';
import StoreLanding from "./routes/StoreLanding";
import Layout from "./components/Layout";
const App = () =>{
return(
<>
<CssBaseline/>
<Routes>
<Route element={<Layout/>}>
<Route path="/" element={<MainLanding/>}/>
<Route path="/store" element={<StoreLanding/>}/>
</Route>
</Routes>
</>
)
}
export default App
This is the layout component where i'm calling the children via props:
import React from 'react';
const Layout = ({ children }) => {
return (
<div>
{children}
</div>
)
}
export default Layout;
Output:
A layout component should render an Outlet for nested Route components to be rendered into. This is different from wrapper components that consume and render the children prop.
See Layout Routes and Outlet for more details.
import { Outlet } from 'react-router-dom';
const Layout = () => {
return (
<div>
<Outlet /> // <-- nested routes rendered here!
</div>
)
};
For comparison, wrapper components are used to wrap a child component in the element prop. Example:
<Routes>
<Route
path="/"
element={(
<Layout>
<MainLanding />
</Layout>
)}
/>
<Route
path="/store"
element={(
<Layout>
<StoreLanding />
</Layout>
)}
/>
</Routes>
Related
I am trying to nest a path within another path in react-router-dom version 6 and whenever I try to visit the nested argument's page (/blog/1), it displays a blank non-styles HTML page but when I put a child path to the root ex. /blog it renders perfectly fine.
Example router layout with nested argument:
import React from "React";
import { BrowserRouter, Routes, Route } from "react-router-dom"
import Main from "./pages/Main.tsx";
import ChatGPT from "./pages/ChatGPT.tsx";
const App = () => {
return (
<BrowserRouter>
<Routes>
<Route index element={<Main />} />
<Route path="blog">
<Route path=":id" element={<Blog />} />
</Route>
</Routes>
</BrowserRouter>
);
};
Main.tsx:
import React from "react";
const Main = () => {
return (
<div>
<p>Home page</p>
</div>
);
};
export default Main;
Blog.tsx
import React from "react";
import { useParams } from "react-router-dom";
const Blog = () => {
const params = useParams();
const id = params.id;
return (
<div>
<p>Blog ID: {id}</p>
</div>
);
};
export default Blog;
I tried to put the <Outlet /> tag into the root of my component's <div> tag that didn't work & I was expecting the react to render the nested argument's page correctly. I am also using Webpack's dev server to view the pages.
React: v18.2.0 | React Router DOM: v6.6.2 | Webpack: v5.75.0 | Webpack-cli: v5.0.1 | Webpack-dev-server: 4.11.1
Main and Blog are default exports, which means they are default imports. Calling Main.default or Blog.default is likely returning undefined. Just Render <Main /> and <Blog />.
import React from "React";
import { BrowserRouter, Routes, Route } from "react-router-dom"
import Main from "./pages/Main";
import Blog from "./pages/Blog";
const App = () => {
return (
<BrowserRouter>
<Routes>
<Route index element={<Main />} />
<Route path="blog">
<Route path=":id" element={<Blog />} />
</Route>
</Routes>
</BrowserRouter>
);
};
I'm learning react routing but when I try to use BrowserRouter I'm getting the following error:
Matched leaf route at location "/project" does not have an element.
This means it will render an <Outlet /> with a null value by default
resulting in an "empty" page.
in Routes (created by Profile)
in Profile (created by App)
in App
PFA the code below.
index.js
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import App from "./App";
const rootElement = document.getElementById("root");
const root = createRoot(rootElement);
root.render(
<StrictMode>
<App />
</StrictMode>
);
APP.JS
import "./styles.css";
import Profile from "./Profile";
import { BrowserRouter } from "react-router-dom";
export default function App() {
return (
<div className="App">
<BrowserRouter>
<Profile />
</BrowserRouter>
</div>
);
}
PROFILE.JS
import Home from "./Home";
import Projects from "./Projects";
import Blogs from "./Blogs";
import { Route, Routes, Switch } from "react-router-dom";
import { BrowserRouter, Link } from "react-router-dom";
const Profile = () => (
<div>
<Routes>
<Route exact path="/" component={Home} />
<Route path="/project" component={Projects} />
<Route path="/blog" component={Blogs} />
</Routes>
</div>
);
export default Profile;
HOME.JS
const Home = () => {
return <div>Hi im Home page</div>;
};
export default Home;
Please note project.js and blog.js is similar to home.js
You appear to be using react-router-dom#6. The Route component API changed significantly from v5. A single element prop replaced the component and render and children function props that takes a ReactNode, a.k.a. JSX, value.
Replace the component prop with the element prop and render the routed components as JSX.
const Profile = () => (
<div>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/project" element={<Projects />} />
<Route path="/blog" element={<Blogs />} />
</Routes>
</div>
);
See the Upgrading from v5 migration guide for other breaking changes between v5 and v6.
I am new to React router here and I am trying to make clicking on a recipe in my 'BrowseRecipes' page redirect to a page dedicated to that recipe. However, when I click on the recipe, the URL shows the correct URL /browse/${recipeID}, but the page I assign to this route does not render. Only the /browse page with a list of all the recipes renders. Does anyone know why?
Here is my APP.js
import AddNewRecipe from './components/AddNewRecipe'
import BrowseRecipes from './components/BrowseRecipes'
import { currentState } from './components/redux';
import ReactDOM from 'react-dom'
import { BrowserRouter as Router, Switch, Route, Routes, Link, useParams} from "react-router-dom";
import AuthReqPage from "./components/AuthReqPage"
import Navbar from "./components/Navbar"
import RecipePage from "./components/BrowseRecipes/RecipePage"
import PageNotFound from "./components/PageNotFound"
function App(props) {
return (
<Router>
<div className="App">
<Navbar />
<Routes>
<Route path='/add' element={<AddNewRecipe />} />
<Route path='/' element={<BrowseRecipes />} />
<Route path='/browse' element={<BrowseRecipes />}>
<Route path=':recipeID' element={<RecipePage />}/>
</Route>
<Route path='/authrequired' element={<AuthReqPage />} />
<Route path='/*' element={<PageNotFound />} />
</Routes>
</div>
</Router>
);
}
export default App;
Here is my BrowseRecipe component/page:
export function BrowseRecipes (props){
console.log('browseRecipe running')
let navigate = useNavigate()
let params=useParams()
console.log(params.recipeID)
if(props.recipeStore.length>0)
{
var displayRecipes = props.recipeStore.map(
elem=>
{
return (<li key={elem.recipeID} className='recipeDisplayBox' onClick={()=>navigate(`/browse/${elem.recipeID}`)}>
{elem.title},
Prep: {elem.prepTime.numeral} {elem.prepTime.unit}
</li>)
}
)
}
return(
<div>
<h1>Browse Recipes</h1>
<h2>Your recipes:</h2>
<ul>
{displayRecipes}
</ul>
</div>
)
}
const mapStateToProps=(state)=>{
return {recipeStore: state.recipe}}
export default connect(mapStateToProps)(RequireAuth(BrowseRecipes))
And here is the individual recipe page that failed to render:
export function RecipePage (props){
console.log('RecipePage running')
let params=useParams()
return(
<div>
<h1>{params.recipeID}</h1>
</div>
)
}
const mapStateToProps=(state)=>{
return {recipeStore: state.recipe}}
export default connect(mapStateToProps)(RequireAuth(RecipePage))
"RequireAuth" here is a higher-order component that redirects the page to 'Please Sign In' page if the user is not signed in.
Did I misunderstand something about the use of UseParams? Please help me shed some light! Thank you very much
You've rendered the RecipePage component on a nested route from the "/browse" route rendering the BrowseRecipes component.
<Route path='/browse' element={<BrowseRecipes />}>
<Route path=':recipeID' element={<RecipePage />}/>
</Route>
In this configuration the BrowseRecipes is required to render an Outlet component for the nested routes to be rendered into.
Example:
import { Outlet, useNavigate, useParams } from 'react-router-dom';
export function BrowseRecipes (props) {
const navigate = useNavigate();
const params = useParams();
let displayRecipes;
if (props.recipeStore.length) {
displayRecipes = props.recipeStore.map(elem => {
return (
<li
key={elem.recipeID}
className='recipeDisplayBox'
onClick={() => navigate(`/browse/${elem.recipeID}`)}
>
{elem.title},
Prep: {elem.prepTime.numeral} {elem.prepTime.unit}
</li>
);
});
}
return (
<div>
<h1>Browse Recipes</h1>
<h2>Your recipes:</h2>
<ul>
{displayRecipes}
</ul>
<Outlet /> // <-- nested routes render here
</div>
);
}
If you don't want to render both BrowseRecipes and RecipePage at the same time, then create a nested index route specifically for BrowseRecipes.
Example:
<Route path='/browse'>
<Route index element={<BrowseRecipes />} /> // <-- "/browse"
<Route path=':recipeID' element={<RecipePage />} /> // <-- "/browse/:recipeID"
</Route>
For more information, see:
Index Routes
Layout Routes
I was trying to display my layout.jsx on the browser but keep getting "A <Route> is only ever to be used as the child of Routes element, never rendered directly;" error I tried wrapping it in Routes but keep getting the error.
import React from 'react'
import Sidebar from '../sidebar/Sidebar';
import Routes from '../Routes';
import {BrowserRouter, Route} from 'react-router-dom';
const Layout = () => {
return (
<BrowserRouter>
<Route render={(props) =>(
<div className='layout'>
<Sidebar {...props} />
<div className="layout__content">
<div className="layout__content-main">
<Routes />
</div>
</div>
</div>
)
} />
</BrowserRouter>
)
}
export default Layout
import React from 'react';
import {Route, Routes} from 'react-router-dom';
import Dashboard from '../pages/Dashboard';
import Customers from '../pages/Customers';
const RRoutes = () => {
return (
<Routes>
<Route path='/' component={Dashboard} />
<Route path='/customers' component={Customers}/>
</Routes>
)
}
export default RRoutes
import React from 'react'
const Sidebar = () => {
return (
<div>
Hello Sidebar
</div>
)
}
export default Sidebar
Since you are using react-router-dom#6 the Route component API changed quite a bit. There are no longer any component or render and children function props, they were replaced by a single element prop taking a ReactNode, a.k.a. JSX. Additionally, all Route components must be rendered by a Routes component, the spiritual successor to the v5 Switch component that handles route matching and rendering.
import React from 'react'
import {BrowserRouter, Route} from 'react-router-dom';
import Sidebar from '../sidebar/Sidebar';
import MyRoutes from '../Routes';
const Layout = () => {
return (
<BrowserRouter>
<div className='layout'>
<Sidebar />
<div className="layout__content">
<div className="layout__content-main">
<MyRoutes />
</div>
</div>
</div>
</BrowserRouter>
);
}
...
import React from 'react';
import { Route, Routes } from 'react-router-dom';
import Dashboard from '../pages/Dashboard';
import Customers from '../pages/Customers';
const MyRoutes = () => {
return (
<Routes>
<Route path='/' element={<Dashboard />} />
<Route path='/customers' element={<Customers />}/>
</Routes>
);
}
export default MyRoutes;
Consider following components:
1)
import React from 'react';
const toolbar = (props) => (
<header>
<nav>
<ul>
<li style={{display: props.displayHome}}>Home</li>
<li>About Us</li>
<li>Contact</li>
</ul>
</nav>
</header>
);
export default toolbar;
2)
import React, {Component} from 'react';
import Toolbar from '../../components/Navigation/Toolbar/Toolbar';
class Layout extends Component {
render() {
return (
<div>
<Toolbar displayHome={this.props.displayHome}/>
<main>
{this.props.children}
</main>
</div>
);
}
}
export default Layout;
3)
import React, { Component } from 'react';
import {Route, Switch} from 'react-router-dom';
import Layout from './hoc/Layout/Layout';
import Homepage from './components/Homepage/Homepage';
import CategoryPage from './containers/CategoryPage/CategoryPage';
import SingleMealPage from './containers/SingleMealPage/SingleMealPage';
import SearchPage from './containers/SearchPage/SearchPage';
import AboutUsPage from './components/AboutUs/AboutUsPage';
import ContactPage from './components/Contact/ContactPage';
class App extends Component {
render() {
return (
<div>
<Layout>
<Switch>
<Route path="/category" component={CategoryPage} />
<Route path="/singleMeal" component={SingleMealPage} />
<Route path="/searchResults" component={SearchPage} />
<Route path="/aboutUs" component={AboutUsPage} />
<Route path='/contact' component={ContactPage} />
<Route path="/" component={Homepage} />
</Switch>
</Layout>
</div>
);
}
}
export default App;
I want to display "Home" tab on every page except on Homepage.
I can obviously do that by importing Toolbar component in every relevant component and then manage "displayHome" property individually, but is there a way to achieve this with the current setup?
The most simple way in your case is to check (this.props.location.pathname !== '/') in App component and pass the result to Layout
Another way is to wrap Toolbar call in routing
class Layout extends Component {
render() {
return (
<div>
<Switch>
<Route exact path="/" component={() => <Toolbar displayHome={false} />} />
<Route path="/*" component={() => <Toolbar displayHome={true} />} />
</Switch>
<main>
{this.props.children}
</main>
</div>
);
}
}
Ofcourse. Right now, you have managed the components without state as stateless components. Now make a stateful component to say, when the Home to be rendered : (I Used react hooks as you already have a function component)
Use render props of route to pass props to component like this:
Option 1 : Use render props
const [needsHomeMenu, setNeedsHomeMenu] = useState(false); // you can use this.setState({needsHomeMenu: true}) to achieve.
.
.
.
<Toolbar displayHome={needsHomeMenu}/> //Toolbar depends on this state
.
.
// update state from within the component that propagates to Toolbar
<Route path="/category" render={() => (<CategoryPage doesNeedHome={setNeedsHomeMenu} />)} />
In CategoryPage.js , get the prop and on mount , doesNeedHome(true)
In HomePage.js, doesNeedHome(false). Now , the state changes and updates the Toolbar props and home goes away.
Option 2:
Use router props to decide the path location instead of route render in Option 1.
In CategoryPage.js and HomePage.js , get the prop and on mount , doesNeedHome(this.props.location.pathname==='/') or doesNeedHome(this.props.location.pathname==='/home')
Rest state update is same as above.
Component needs to be refactored accordingly in diff files.