React-Router: Nested route not rendering component - javascript

This is a simplified reproduction of the problem: The child with the nested URL does not render.
My file structure is basically this:
-App
--Home
--Pageone
---Childone
I can render Home or Pageone from App, then I can render Home or Pageone from Pageone or Home respectively, but I cannot render Childone from its 'parent page' Pageone. I am not quite sure what is done wrong.
The code is shared below, and this sandbox
App.jsx
import { BrowserRouter, Switch, Route } from "react-router-dom";
import Pageone from "./Pageone";
import Home from "./Home";
export default function App() {
return (
<BrowserRouter>
<Switch>
<Route exact path="/">
<Home />
</Route>
<Route exact path="/pageone">
<Pageone />
</Route>
</Switch>
</BrowserRouter>
);
}
Home.jsx
import { Link } from "react-router-dom";
const Home = () => {
return (
<div>
<p>This is HOME!</p>
<Link to="/pageone">Pageone</Link>
</div>
);
};
export default Home;
Pageone.jsx
import { Link, Route, useRouteMatch } from "react-router-dom";
import Childone from "./Childone";
const Pageone = () => {
const { path, url } = useRouteMatch();
return (
<div>
<Route exact path={path}>
<PageoneContent url={url} />
</Route>
<Route exact path={path + "/childone"}>
<Childone />
</Route>
</div>
);
};
const PageoneContent = (props) => {
return (
<div>
<p>This is pageone!</p>
<Link to="/">Go back Home</Link>
<br />
<Link to={props.url + "/childone"}>Go to Child One</Link>
</div>
);
};
export default Pageone;
Childone.jsx
import { Link } from "react-router-dom";
const Childone = () => {
return (
<div>
<p>This is Child one!</p>
<Link to="/">Go back Home</Link>
</div>
);
};
export default Childone;

As far as I know you need to use another Switch for routes, otherwise react will have no idea how to match that route path.
<div>
<Route exact path={path}>
Should be:
<Switch>
<Route exact path={path}>

Related

React Router v6.4 useNavigate(-1) not going back

I created a component in my project that consists of a simple button that returns to the previous page, using useNavigate hook.
As it is written in the documentation, just passing -1 as a parameter to the hook would be enough to go back one page. But nothing happens.
The component code:
import { useNavigate } from 'react-router-dom'
import './go-back.styles.scss'
const GoBack = () => {
const navigate = useNavigate()
const handleClick = () => {
navigate(-1)
}
return (
<button
className='go-back'
onClick={handleClick}
>
go back
</button>
)
}
export default GoBack
The app.js code:
import { lazy, Suspense } from 'react'
import { Routes, Route } from 'react-router-dom'
import Header from '../components/header/header.component'
import Footer from '../components/footer/footer.component'
import './App.scss'
const App = () => {
const HomePage = lazy(() => import('../pages/home/home.page'))
const SearchPage = lazy(() => import('../pages/search/search.page'))
const MostraPage = lazy(() => import('../pages/mostra/mostra.page'))
const AuthPage = lazy(() => import('../pages/auth/auth.page'))
const AccountPage = lazy(() => import('../pages/account/account.page'))
const PrenotaPage = lazy(()=> import('../pages/prenota/prenota.page'))
const SectionPage = lazy(() => import('../pages/section/section.page'))
return (
<div className='app'>
<Header />
<Suspense fallback={<span>Loading...</span>}>
<Routes>
<Route exact path='/' element={<HomePage />} />
<Route exact path='/auth:p' element={<AuthPage />} />
<Route exact path='/search' element={<SearchPage />} />
<Route exact path='/search:id' element={<SectionPage />} />
<Route exact path='/mostra' element={<MostraPage />} />
<Route exact path='/prenota' element={<PrenotaPage/>} />
<Route exact path='/profile' element={<AccountPage />} />
<Route exact path='*' element={<span>Page not found</span>} />
</Routes>
</Suspense>
<Footer />
</div>
)
}
export default App
The index.js code:
import { createRoot } from 'react-dom/client'
import { Provider } from 'react-redux'
import store, { persistor } from './redux/store/store'
import App from './app/App'
import reportWebVitals from './reportWebVitals'
import { PersistGate } from 'redux-persist/integration/react'
import { BrowserRouter } from 'react-router-dom'
const container = document.getElementById('root')
const root = createRoot(container)
root.render(
<BrowserRouter>
<Provider store={store}>
<PersistGate persistor={persistor}>
<App />
</PersistGate>
</Provider>
</BrowserRouter>
)
reportWebVitals()
I thank in advance anyone who tries to help.
In the project, when rendering to another page I used the useNavigate hook and passed { replace: true } as the second parameter.
However, in this way the navigation will replace the current entry in the history stack instead of adding a new one by not then making the GoBack component work properly.
So it was enough to remove { replace: true } from the calls to useNavigate and now it works.

Fetch Param from Route in Nav Bar, React-Router

I have a router set up in my App.js as follows:
import React from 'react';
import { BrowserRouter as Router, Route, Switch, Redirect } from 'react-router-dom';
import NavBar from './nav-bar';
import Landing from './landing-page';
import Dashboard from './dashboard';
import Analysis from './analysis';
import '../style.scss';
const App = (props) => {
return (
<Router>
<NavBar />
<Switch>
<Route exact path="/" component={Landing} />
<Route path="/dashboard/:prodID/search" component={Dashboard} />
<Redirect from="/dashboard/:prodID" to="/dashboard/:prodID/search" />
<Route path="/dashboard/:prodID/analyze" component={Analysis} />
<Route component={() => (
<div id="error">
<h1>404 ERROR</h1>
<h2>Page not found</h2>
</div>
)}
/>
</Switch>
</Router>
);
};
export default App;
and my NavBar component is set up as follows:
import React, { Component } from 'react';
import { NavLink, withRouter } from 'react-router-dom';
import { Navbar, Nav } from 'react-bootstrap';
import '../style.scss';
class NavBar extends Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
return (
<Navbar id="nav-bar" bg="dark" variant="dark">
<Navbar.Brand href="/">
My Project
</Navbar.Brand>
<Navbar.Collapse id="responsive-navbar-nav" className="justify-content-end">
<Nav>
<NavLink to="/dashboard/:prodID/search">Search</NavLink>
<NavLink to="/dashboard/:prodID/analyze">Analyze</NavLink>
</Nav>
</Navbar.Collapse>
</Navbar>
);
}
}
export default withRouter(NavBar);
I have two things that I'm trying to figure out:
I want to be able to access the prodID route param within my NavBar component so that when a user clicks on the route, it will take the valid prodID and render the route correctly.
I want to only display the NavLinks in NavBar if the user is on a route that has the prodID param. If they're on the home route / for example, the links wouldn't show up. But if they're on the route /dashboard/[valid prodID]/search, the links would show up.
How do I go about implementing this? I've looked at other posts on SO dealing with route params and nav bars, but none of them have answered my question. Any help is appreciated.
I believe you would have to move your navbar under each of the routes, so that it can be re-rendered and grab the correct params when the path changes.
In order to achieve it, you can create the Layout component which will wrap the component you pass and add a navbar to it:
// Layout.jsx
import React from "react";
import NavBar from './nav-bar';
export const Layout = () => {
return (
<div>
<Navbar />
<div>{children}</div>
</div>
);
};
Then in your App, you can wrap each component within the routes with the Layout component like so
// App.jsx
import React from "react";
import {
BrowserRouter as Router,
Route,
Switch,
Redirect
} from "react-router-dom";
import NavBar from "./nav-bar";
import Landing from "./landing-page";
import Dashboard from "./dashboard";
import Analysis from "./analysis";
import { Layout } from "./Layout";
import "../style.scss";
const App = props => {
return (
<Router>
<Switch>
<Route exact path="/">
<Layout>
<Landing />
</Layout>
</Route>
<Route path="/dashboard/:prodID/search">
<Layout>
<Dashboard />
</Layout>
</Route>
<Redirect from="/dashboard/:prodID" to="/dashboard/:prodID/search" />
<Route path="/dashboard/:prodID/analyze">
<Layout>
<Analysis />
</Layout>
</Route>
<Route
component={() => (
<div id="error">
<h1>404 ERROR</h1>
<h2>Page not found</h2>
</div>
)}
/>
</Switch>
</Router>
);
};
export default App;
This approach would help you achieve your second goal. Since the navbar is now nested under each route, you can easily fetch the params from the path and conditionally render the links, like so:
// NavBar.jsx
import React from "react";
import { NavLink, useParams } from "react-router-dom";
import { Navbar, Nav } from "react-bootstrap";
import "../style.scss";
const NavBar = () => {
const { prodID } = useParams();
return (
<Navbar id="nav-bar" bg="dark" variant="dark">
<Navbar.Brand href="/">My Project</Navbar.Brand>
<Navbar.Collapse
id="responsive-navbar-nav"
className="justify-content-end"
>
<Nav>
{prodID && (
<NavLink to={`/dashboard/:${prodID}/search`}>Search</NavLink>
)}
{prodID && (
<NavLink to={`/dashboard/:${prodID}/analyze`}>Analyze</NavLink>
)}
</Nav>
</Navbar.Collapse>
</Navbar>
);
};
export default NavBar;
I haven't tested it, but it should help you with your issues.

React-router v4, URL changing but component doesn't render

I've tried everything but fail to render component when URL changes. No error messages nothing, react-redux is installed but not using it yet, so it can't be the problem. When I check it from to Google chrome React dev tools, nothing happens, there is no match, history vs anything. I couldn't find a solution, is there any way to make it work?
https://codesandbox.io/s/vm3x9n4k67
Here is my NavBar.js. I import NavLink from react-router-dom and implement these
import React from 'react'
import classes from "./NavBar.css";
import { NavLink } from 'react-router-dom';
const NavBar = (props) => {
return (
<div className={classes.NavBar}>
<h1 className={classes.NavBar_list} >foodbase</h1>
<ul className={classes.NavBar_list}>
<NavLink to="/auth"> <li className={classes.NavBar_list_item}>Signin</li></NavLink>
<NavLink to="/"><li className={classes.NavBar_list_item}>Main Page</li></NavLink>
</ul>
</div>
)
}
export default NavBar
this is my Layout.js render property:
render() {
let recipes = [];
if (this.state.recipes.length > 0) {
recipes = this.state.recipes;
}
return (
<div>
<NavBar/>
<SearchBox
onChangeHandler={this.onChangeHandler}
value={this.state.inputValue}
onSearchClick={this.onClickedHandler} />
<section className={classes.SearchSection}>
<ul className={classes.SearchResultArea}>
<SearchResults
Results={recipes}
></SearchResults>
</ul>
</section>
</div>
)
}
and finally app.js:
import React, { Component } from 'react';
import { Switch, Route, BrowserRouter, withRouter } from 'react-router-dom';
import Auth from './components/Auth/Auth';
import SearchBox from './components/SearchBox/SearchBox';
import Layout from './containers/Layout/Layout';
import './App.css';
class App extends Component {
render() {
return (
<div>
<BrowserRouter>
<Switch>
<Layout>
<Route path="/auth" component={Auth} />
<Route path="/" exact component={SearchBox} />
</Layout>
</Switch>
</BrowserRouter>
</div>
);
}
}
export default withRouter(App);
I assume that you need to put your Route components directly into Switch and don't forget to render children in Layout. So try this:
app.js:
class App extends Component {
render() {
return (
<div>
<BrowserRouter>
<Layout>
<Switch>
<Route path="/auth" component={Auth} />
<Route path="/" exact component={SearchBox} />
</Switch>
</Layout>
</BrowserRouter>
</div>
);
}
}
Layout.js
render() {
// ...
return (
<div>
<NavBar />
{ this.props.children } // <-- your route specific components
</div>
)
}

Why cant I separate <Switch> to a different module?

Goal
I'm trying to extract the <Switch> with its <Route>'s out to a different module.
Problem
The url is being changed to the new path, but the content doesn't (only when I refresh it changes).
I'm trying to understand what am I missing.
EDITED:
live example: https://stackblitz.com/edit/separated-switch-module
working example:
<BrowserRouter>
<div>
<Link to="/"> Home </Link>
<Link to="contacts"> Contacts </Link>
<Switch>
<Route exact path="/" component={Home} />
<Route path="/contacts" component={Contacts} />
</Switch>
</div>
</BrowserRouter>
failing exmaple:
<BrowserRouter>
<div>
<Link to="/"> Home </Link>
<Link to="contacts"> Contacts </Link>
<SwitchedRoutes/>
</div>
</BrowserRouter>
EDITED:
SwitchedRoutes:
import React from "react";
import { observer, inject } from "mobx-react";
import { Switch, Route } from "react-router-dom";
#inject('pageStore')
#observer
export default class extends React.Component {
render(){
const {
home,
contacts
} = this.props.pageStore.pages;
return (
<Switch>
<Route exact path={home.path} render={()=> <Home />} />
<Route path={contacts.path} render={()=> <Contacts/>} />
</Switch>
)
}
}
Since react-router v4 changed an API a bit you need to give to the all underlying components such as Switch, Link and etc a router context. (Something like subscriber to the routing stuff), as soon as you disconnects the module to the separate file it loses the context.
just add this to the SwitchedRoutes.js
import React from 'react';
import { withRouter } from 'react-router'
import {Switch, Route} from 'react-router-dom';
import {inject, observer} from 'mobx-react';
const Home = () => <h1>Home</h1>;
const Contacts = () => <h1>Contents</h1>;
const switchedRouter = inject('store')(observer(props => {
const {home, contacts} = props.store.routes;
return(
<Switch>
<Route exact path={home.path} component={Home}/>
<Route path={contacts.path} component={Contacts}/>
</Switch>
);
}));
export default withRouter(switchedRouter)
we simply wrapped the component with withRouter HoC which provides us a correct react-router context.
https://separated-switch-module-j92psu.stackblitz.io

React Router only recognises index route

I have 2 routes, / and /about and i've tested with several more. All routes only render the home component which is /.
When I try a route that doesn't exist it recognises that fine and displays the warning
Warning: No route matches path "/example". Make sure you have <Route path="/example"> somewhere in your routes
App.js
import React from 'react';
import Router from 'react-router';
import { DefaultRoute, Link, Route, RouteHandler } from 'react-router';
import {Home, About} from './components/Main';
let routes = (
<Route name="home" path="/" handler={Home} >
<Route name="about" handler={About} />
</Route>
);
Router.run(routes, function (Handler) {
React.render(<Handler/>, document.body);
});
./components/Main
import React from 'react';
var Home = React.createClass({
render() {
return <div> this is the main component </div>
}
});
var About = React.createClass({
render(){
return <div>This is the about</div>
}
});
export default {
Home,About
};
I've tried adding an explicit path to about to no avail.
<Route name="about" path="/about" handler={About} />
I've stumbled upon this stackoverflow Q but found no salvation in its answer.
Can anyone shed some light on what might be the problem?
Using ES6 and the newest react-router would look like this:
import React from 'react';
import {
Router,
Route,
IndexRoute,
}
from 'react-router';
const routes = (
<Router>
<Route component={Home} path="/">
<IndexRoute component={About}/>
</Route>
</Router>
);
const Home = React.createClass({
render() {
return (
<div> this is the main component
{this.props.children}
</div>
);
}
});
//Remember to have your about component either imported or
//defined somewhere
React.render(routes, document.body);
On a side note, if you want to match unfound route to a specific view, use this:
<Route component={NotFound} path="*"></Route>
Notice the path is set to *
Also write your own NotFound component.
Mine looks like this:
const NotFound = React.createClass({
render(){
let _location = window.location.href;
return(
<div className="notfound-card">
<div className="content">
<a className="header">404 Invalid URL</a >
</div>
<hr></hr>
<div className="description">
<p>
You have reached:
</p>
<p className="location">
{_location}
</p>
</div>
</div>
);
}
});
Since you've nested About under Home you need to render a <RouteHandler /> component within your Home component in order for React Router to be able to display your route components.
import {RouteHandler} from 'react-router';
var Home = React.createClass({
render() {
return (<div> this is the main component
<RouteHandler />
</div>);
}
});

Categories