React Router v5: Route defined in child component not working - javascript

My application is using react-router-dom v5.3 and I'm having trouble routing from the root url of my application to a child component (called the "See All" Page) while also passing props down. Currently, my code just renders an empty page whenever I navigate to the child component.
RootRouter.js:
export default function RootRouter() {
return (
<BrowserRouter>
<Switch>
<Route
path="/"
exact
render={() => <HomeView />}
/>
</Switch>
</BrowserRouter>
);
}
Homeview.js:
function HomeView() {
const seeAllViewTitle = "some_title_here"
return (
<div>
<div>Some content here!</div>
<Link to={`/seeall/${seeAllViewTitle}`}}>
<Button/>
</Link>
<Route path={`/seeall/${seeAllViewTitle}`}>
<SeeAllView
groupTitle={""}
pageData={[]}
eventHandler={some_function_here}
/>
</Route>
</div>
);
}
If I were to put the Route that is currently in homeview.js inside of Rootrouter.js, the component shows up, but I can't pass any props into it from there.

Issue
The HomeView component is rendered only when the path is exactly "/". When the link is clicked and navigates to "/seeall/some_title_here " the path no longer exactly matches and the HomeView component unmounts.
Solution
Remove the exact prop from the root route so nested/sub routes can also be matched and rendered.
export default function RootRouter() {
return (
<BrowserRouter>
<Switch>
<Route path="/" component={HomeView} />
</Switch>
</BrowserRouter>
);
}
If you did not intend for these components to be rendered at the same time then move the nested route out to the RootRouter component.
export default function RootRouter() {
return (
<BrowserRouter>
<Switch>
<Route path="/seeall/:title">
<SeeAllView
groupTitle={""}
pageData={[]}
eventHandler={some_function_here}
/>
</Route>
<Route path="/" component={HomeView} />
</Switch>
</BrowserRouter>
);
}
...
function HomeView() {
const seeAllViewTitle = "some_title_here"
return (
<div>
<div>Some content here!</div>
<Link to={`/seeall/${seeAllViewTitle}`}}>
<Button/>
</Link>
</div>
);
}

Are you remembering to receive props in the function declaration for HomeView? usually, you'll need to explicitly define that you are receiving props, either with a props variable or by defining specific prop names in an object syntax

Related

React Router hierarchy with rendering components

I'm passing an object to a class component and want to have that component open in a different route. The routing works, but all the time the props are undefined in the child component unless I move the <Route path=''...> line before every other component. The props work, but the page display is not correct.
PARENT
import React, { Component } from "react";
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
import Header from "./Header";
import DarbaiLT from "./DarbaiLT";
import AnObject from "./AnObject";
let clickeddiv = ''
class App extends Component {
onObjectClick = (clickeddivffromdarbai) => {
clickeddiv = clickeddivffromdarbai;
console.log("clickeddiv: ", clickeddiv);
};
*//clickeddiv is data coming from DarbaiLT component*
};
render() {
return (
<Router>
<div>
<Switch>
<Route path="/object/:id" exact component={AnObject} />
<Route path="/about" exact component={About} />
<Route path="/contacts" exact component={Contacts} />
<Route path="/partners" exact component={Partneriai} />
<DarbaiLT onObjectClick={this.onObjectClick} />
<AnObject dataforComponent={clickeddiv}/> //when this line is the last, it's not working
</Switch>
<Footer />
</div>
</Router>
);
}
}
export default App;
CHILD
import React, { Component } from "react";
class AnObject extends Component {
constructor(props) {
super(props);
}
render() {
return (
<>
<div onClick={() => console.log(this.props.dataforComponent)}>
<img src='../smth/pic.jpg' width="100%" />
</div>
</>
);
}
}
export default AnObject;
if I move the line to the top, passing of props works, but then all pages show only the AnObject, and doesn't render the About, Contacts and so on...
<Router>
<div>
<Header />
<Slides />
<Switch>
<AnObject stateforyou={clickeddiv}/> //if the line is here, routing doesn't work
<Route path="/object/:id" exact component={AnObject} />
<Route path="/about" exact component={About} />
<Route path="/contacts" exact component={Contacts} />
<Route path="/partners" exact component={Partneriai} />
<DarbaiLT onObjectClick={this.onObjectClick} />
</Switch>
<Footer />
</div>
</Router>
the documentation of React Router states that: "All children of a < Switch > should be < Route >".
Using the react-router Switch is like using switch case statement of javascript, whenever the link is matched to the route, the passed component gets rendered. Your problem here, however, is how to pass props to the rendered component which is done this way:
<Switch>
<Route path="/object/:id" exact component={() => <AnObject stateforyou={clickeddiv}/> } />
<Route path="/about" exact component={About} />
<Route path="/contacts" exact component={Contacts} />
<Route path="/partners" exact component={Partneriai} />
</Switch>
I'm not sure I totally understand the issue. But when you use <Switch> in the react-router, it will render only the first match.
You have a switch set up like this:
<Switch>
<Route path="/about" exact component={About} />
<DarbaiLT onObjectClick={this.onObjectClick} />
<AnObject dataforComponent={clickeddiv}/>
</Switch>
This means that if the visitor is at the url /about, it will render only the About component and nothing else. If you want to be able to render multiple components simultaneously as siblings, remove the <Switch>...</Switch>.

Is there a best practice way to hide component using react router?

To hide the navbar on the home component I am doing the following
const NavbarComponent = (props) => {
console.log(props);
if (props.match.path === '/') {
return null;
} else
return (
it works fine, I need to have access to the router so I can send people to locations dependant on the props object , is there a better way to do it such that I have all router logic in the same place?
this is the current state of my router
return (
<div>
<Router>
<Route component={Navbar} />
<Switch>
<Route exact path="/" component={Home} />
<Route exact path="/api/:city/electronics" component={Electronics} />
<Route exact path="/api/:city/labour" component={Labour} />
<Route exact path="/api/posts/item/:id" component={ItemDetails} />
<Route exact path="/create/:city/:category" component={CreatePost} />
</Switch>
</Router>
</div>
);
thanks for your time.
I'm not sure I understand why your NavBar component is in it's own Route. Any components contained within the Router have access to the entire Router api, including Link - they do not need to be a Route to do so.
I would suggest wrapping all the Routes that include the NavBar with that component. The Routes will then be displayed as children of the Navbar component.
Here is a simplified example:
// App.js
return (
<div>
<Router>
<Switch>
<Route exact path="/" component={Home} />
<NavBar>
<Route exact path="/electronics" component={Electronics} />
<Route exact path="/labour" component={Labour} />
</NavBar>
</Switch>
</Router>
</div>
);
//NavBar.js
return (
<>
<div>
<Link to="/electronics">Electronics</Link>
<Link to="/labour">Labour</Link>
</div>
<div>{props.children}</div>
</>
);
codesandbox

React & Material UI - How to Remove Nav Buttons/Links based on Routes?

The code below is the main app.js entry of a React app and it includes the routers endpoints.
Below the app.js code is the code of a Nav (navigation) component. I want to know how to structure this code so that when a user goes to a specific route the respective link in the Nav is removed.
In other words if the user is at localhost:3000/calendar the tab with the word "calendar" should not appear in the Nav component.
I could parse the endpoints and do this with a bunch of ugly conditionals that render different Nav code based on the parsed endpoints - but I figure there is a simpler way that I don't see to do what I want.
Thanks.
App.js
function App(){
...code
function redirectToClientsList(){
window.location.href = "/";
}
function redirectToCalendar(){
window.location.href = "/calendar";
}
return (
<div className="App">
<MuiPickersUtilsProvider utils={MomentUtils}>
<Nav redirectToClientsList = {redirectToClientsList} redirectToCalendar={redirectToCalendar} />
<BrowserRouter>
<div>
<Switch>
<Route exact={true} path="/" component={Landing} />
<Route exact={true} path="/test" component={Test} />
<Route exact={true} path="/client/:id/client-name/:client/workflows" component={Workflows} />
<Route exact={true} path="/calendar" component={Calendar} />
<Redirect from="/*" to="/" />
</Switch>
</div>
</BrowserRouter>
</MuiPickersUtilsProvider>
</div>
);
}
export default App;
Nav component.
function Navbar(props){
const {classes} = props;
return (
<AppBar className={classes.bgColor} >
<Toolbar>
<Button color="inherit" onClick ={props.redirectToClientsList}>Clients</Button>
<Button color="inherit" onClick ={props.redirectToCalendar}>Calendar</Button>
<Button className={classes.saveDataButton} style={{float:"right"}} color="inherit">SAVE</Button>
</Toolbar>
</AppBar>
)
}
Navbar.propTypes = {
classes: PropTypes.object.isRequired,
};
const styles = theme => (navbarStyle(theme));
export default withStyles(styles)(Navbar);
You can use the withRouter provided by the react-router-4 package.
Here's an example using the withRouter
You can get access to the history object’s properties and the closest <Route>'s match via the withRouter higher-order component. withRouter will pass updated match, location, and history props to the wrapped component whenever it renders.
import React, { Component } from "react";
import { withRouter } from "react-router";
// A simple component that shows the pathname of the current location
class ShowTheLocation extends Component {
render() {
const { match, location, history } = this.props;
return <div>You are now at {location.pathname}</div>;
}
}
// Create a new component that is "connected" to the router
const ShowTheLocationWithRouter = withRouter(ShowTheLocation);
After getting access to the current location aka route object you can do a simple if check to determine which buttons should be displayed on that particular route. I would create a helper function that should determine just that, put it inside of the componentDidMount lifecycle or the equivalent useEffect hook (if you choose that approach)`, save the result to the state and finally do an if check and depending on its results, show/ hide buttons
Important Note:
withRouter does not subscribe to location changes like React-Redux’s connect does for state changes. Instead, re-renders after location changes propagate out from the <Router> component. This means that withRouter does not re-render on route transitions unless its parent component re-renders.
You could wrap your app.js component with the withRouter HOC and get the necessary props in your Nav.js component
// ....
<div className="App">
<MuiPickersUtilsProvider utils={MomentUtils}>
<BrowserRouter>
<div>
<Nav redirectToClientsList = {redirectToClientsList} redirectToCalendar={redirectToCalendar} />
<Switch>
<Route exact path="/" component={Landing} />
<Route exact path="/test" component={Test} />
<Route exact path="/client/:id/client-name/:client/workflows" component={Workflows} />
<Route exact path="/calendar" component={Calendar} />
<Redirect from="/*" to="/" />
</Switch>
</div>
</BrowserRouter>
</MuiPickersUtilsProvider>
</div>
// LET'S NOW WRAP OUR App.js COMPONENT WITH THE withRouter HOC - [don't forget to import it first] :)
export default withRouter(App);
Nav.js
function Navbar(props){
const {classes} = props;
// We're interested in the current path,
// so, we'll destructure it from the location property
const { pathname } = props.location;
return (
<AppBar className={classes.bgColor} >
<Toolbar>
// Here we're going to do our check
{pathname !== "calendar" ?
<Button color="inherit" onClick ={props.redirectToCalendar}>Calendar</Button> : null}
<Button color="inherit" onClick ={props.redirectToClientsList}>Clients</Button>
<Button className={classes.saveDataButton} style={{float:"right"}} color="inherit">SAVE</Button>
</Toolbar>
</AppBar>

Nested react routes with different page layouts

I'm having two issues with my code:
Number One:
My whole application seems to work fine, I can access all my routes when I first load my web app. My app consists of a Landing Page with its navbar that has a login button that takes me straight to my application's home page (haven't added authentication yet). This home page has a navbar that is different from the one on the landing page. The navbar items are (home, about and LandingPage). My App.js has routes to the landing page component, the home page which is the Gitapp component, and a PageNotFound component. The Gitapp component contains routes to the about page and other components. If i happen to reload the page while i'm on one of the routes on App.js (first-level routes) it reloads fine. However, if I'm on the routes (second-level routes) that exist on my Gitapp component, like the route for the about page and i reload the page, I get the PageNotFound Component.
Number Two:
My second Navbar has a logout button that should take me back to the landing page. For some Reason I can't get it to work because If I add the route to the landing page in my Gitapp component, React will try to display the landing page underneath the Home page.
This is App.js:
const App = () => {
return (
<Fragment>
<Router>
<Switch>
<Route exact path='/' component={LandingPage} />
<Route exact path='/gitapp' component={GitApp} />
<Route component={PageNotFound} />
</Switch>
</Router>
</Fragment>
);
};
This is LandingPage.js:
const LandingPage = () => {
return (
<div>
<NavbarLanding />
<SideNavBar />
<LandingSection1 />
<LandingSection2 />
<LandingSection3 />
<Route exact path='/gitapp' component={GitApp} />
</div>
);
};
This is Gitapp.js:
const GitApp = ({ match }) => {
return (
<GithubState>
<Router>
<div style={containerStyling}>
<Navbar />
<Switch>
<Route exact path={match.url} component={Home} />
<Route
exact
path={`${match.url}/user/:login`}
component={UserProfile}
/>
<Route exact path={`${match.url}/about`} component={About} />
</Switch>
<Footer />
</div>
</Router>
</GithubState>
);
};
const containerStyling = {
minHeight: '100vh',
overflow: 'hidden',
display: 'block',
position: 'relative',
paddingBottom: '70px'
};
I have resolved both issues! I had to get rid of the word exact in the Gitapp route defined in App.js.
So instead of:
const App = () => {
return (
<Fragment>
<Router>
<Switch>
<Route exact path='/' component={LandingPage} />
<Route exact path='/gitapp' component={GitApp} /> {/* Wrong! */}
<Route component={PageNotFound} />
</Switch>
</Router>
</Fragment>
);
};
It should be:
const App = () => {
return (
<Fragment>
<Router>
<Switch>
<Route exact path='/' component={LandingPage} />
<Route path='/gitapp' component={GitApp} /> {/* Correct! */}
<Route component={PageNotFound} />
</Switch>
</Router>
</Fragment>
);
};
Don't know exactly why but I can reload the second level components instead of receiving the NotFound Component. Would appreciate it if someone could explain why the word exact made a different here.
As for my second issue, I just used redirect with conditional rendering. So my context api will update my global 'logout' state and pass it down to the component which then would be waiting for it ('logout' state) to become true and will then redirect me to the landing page.

`react-router` warning solution and passing root props to sub routes

Sometime I was sawing the well known warning, browser.js:49 Warning: [react-router] You cannot change <Router routes>; it will be ignored and I found two trend issues that friends discussed about this issue and the solution is const routes components and putting them inside Router component.
https://github.com/ReactTraining/react-router/issues/2704
https://github.com/reactjs/react-router-redux/issues/179
Just like below:
you will see warning with this code:
class Root extends Component {
render() {
return (
<Router history={browserHistory} createElement={this.createElement}>
<Route component={App}>
<Route path="/" component={MainPage}/>
<Route path="/page2" component={Page2}/>
<Route path="/settings" component={SettingsPage}/>
</Route>
</Router>
)
}
}
but you won't see warning with this code:
const routes = (
<Route component={App}>
<Route path="/" component={MainPage}/>
<Route path="/page2" component={Page2}/>
<Route path="/settings" component={SettingsPage}/>
</Route>
);
class Root extends Component {
render() {
return (
<Router history={browserHistory} createElement={this.createElement}>
{routes}
</Router>
)
}
}
This is OK, awesome solution to vanish [react-router] warning, and for Root Component changing state the routes was static and you won't see any warnings. BUT my issue is: I pass Root Component props to each Route and I can not do the above solution 😞 ,
I must put App Route inside Router so with this method absolutely this is not solution method and I will saw the known warning again, see my router code:
export default class AppRoutes extends Component {
render() {
return (
<Router history={hashHistory}>
<Route path="/" {...this.props} component={App}>
<IndexRoute component={Home} {...this.props}/>
<Route path="/transaction" component={Transaction} {...this.props}/>
<Route path="/users-management" component={UsersManagement} {...this.props}/>
<Route path="/issues" component={Issues} {...this.props}/>
<Route path='/not-found' component={NotFound}/>
<Route path='/settlement-management' component={SettlementManagement} {...this.props}/>
<Route path='/categories-management' component={CategoriesManagement} {...this.props}/>
<Route path='/gifts-management' component={GiftsManagement} {...this.props}/>
<Redirect from='/*' to='/not-found'/>
</Route>
</Router>
);
}
}
And the Root Component render code is:
render(){
return(
<AppRoutes {...this}/>
);
}
I passed this as a props to AppRoutes component and I need to pass inherited this.props to sub Routes and use them. how I could won't see warning and pass props to any Routes?
One of my solution is that, I write all Routes as static and call Root Component props directly inside each component, but how? I don't know how I can call and keep props of Root Component inside the component that need to have props of Root Component as the component is not direct Root Component children?
You can use render route prop instead of component to pass props to your components :
<Route path="/transaction" render={() => <Transaction {...this.props} />} />
Edit : Or this to also pass route props :
<Route path="/transaction" render={(routeProps) => <Transaction parentProps={this.props} {...routeProps} />} />
(I think it's better to pass individual custom parent props to not enter in conflict with routeProps)

Categories

Resources