I have a component called Header which exists across all routes, while the rest of the app changes. So to accomplish this, my main render code looks about like this (using ES6):
render(){
return (
<div>
<Header></Header>
<Router>
<Route path="/" component={Home} />
<Route path="/details/:id" component={Details} />
</Router>
</div>
);
}
The challenge is that the contents of the <Header> should vary slightly depending on the route, for example a unique title per route.
How can this be achieved?
Thanks for all the great answers! Still mulling them over.
To throw another solution into the mix, I found that I can actually put arbitrary properties on the Route, so I added title:
<Route title="My Title" component={App} />
And I re-shuffled around my route hierarchy to include the header in Router (in the top-level Route component instead of outside any route as before), so my main render code looks like this:
<Router>
<Route component={App}>
<Route path="/" component={Home} title="Home" />
<Route path="/detail/:id" component={Detail} title="Details" />
</Route>
</Router>
And my App contains the header and is passed the current route's title:
class App extends React.Component {
render(){
var route = this.props.routes[this.props.routes.length - 1];
return (
<div>
<Header title={route.title} />
{this.props.children}
</div>
)
}
}
But I can't say this is the best solution. I do like that I can just put title on each route now, but I worry about the coupling and the way I have to extract the properties from the routes array.
This is a good use case for flux. You have route handlers that create an action when they mount. This action goes into the HeaderStore. The Header component listens to the Header store and renders based on it.
You can see an example of this here:
CurrentBoardStore holds the current page info
BoardPage updates the store when it mounts/updates
SubBoardHeaderWrapper renders the header with data from CurrentBoardStore
The way I do it (I'm pretty sure there is a better way, but this might help you out) looks like the following:
index.js
// define routes
const routes = (
<Route component={App} path="/">
<IndexRoute component={Home} />
<Route path="/" component={Home} />
<Route path="/details/:id" component={Details} />
</Route>
);
ReactDOM.render(<Router>{routes}</Router>, document.getElementById('your_id'));
App.js
render() {
return (
<div>
<Header routerProps={this.props.children} />
{this.props.children}
</div>
);
}
Header.js
componentWillReceiveProps(nextProps) {
// Get access to the pathname, it contains a string like "/details/"
console.log(nextProps.routerProps.props.location.pathname);
}
instead of putting header there.. put the header in a layout component. each view should use the layout component and you can pass whatever you want the header to be.
export class Layout extends React.Component{
render() {
return <div>
<Header title={this.props.title} />
{this.props.children}
</div>
}
}
all of your views can use this same component like so
export class SomeComponent extends React.Component{
render() {
return <Layout title="Some Component Title">
<div>my elements here</div>
</Layout>
}
}
NOTE: the beauty of using something like this, is you can set up any other default messaging like for instance lets say you want to have a flash message appear... someone clicks on something and you want a message to say 'You've successfully registered!' (in this example). You can include your flash messaging in the layout and simply dispatch an event to show the message. This can be done with modals too and really whatever your app requirements are :)
Related
Hi I have seen this question and this is exactly what i am looking for but i can't get it to work.
React Router V4 Routers with Master Pages / Templates
I am using version 5.2
App component includes the navigation and wraps two paths with two template components, 'Public' and 'Private' and when i click each link i expect that page to be wrapped by it's template but instead only the first link works and shows the template. The second link only changes the url in the browser address bar and only the content of first template remains on the page and the content of the second page doesn't show. If i change the order of the links always only the top link gets the correct template.
This is where i define the routes and links to them.
class App extends Component {
render() {
return (
<Router>
<div>
<ul><li><Link to="/">Home</Link></li>
<li><Link to="/members">Members</Link></li>
</ul>
</div>
<Switch>
<Public>
<Route exact path="/"><Home /></Route>
</Public>
<Private>
<Route path="/members"><Members /></Route>
</Private>
</Switch>
</Router>
);
}
}
export default App;
My private and public templates are the same except for the titles one say private and the other public.
class Private extends Component {
render() {
return (
<div className="container">
<div className="row">
<div className="col-md-2"><h2>PRIVATE</h2></div>
<div className="col-md-8">{this.props.children}</div>
<div className="col-md-2"></div>
</div>
</div>
);
}
}
export default Private;
I have been looking for something like this for a while as it looks like a potentially neat solution. If anyone knows how to fix this i would really appreciate it.
Thanks
Here is what Switch Component does:
React.Children.forEach(this.props.children, child => {
if (match == null && React.isValidElement(child)) {
element = child;
const path = child.props.path || child.props.from;
match = path
? matchPath(location.pathname, { ...child.props, path })
: context.match;
}
});
as you see the Switch component only cares about the first level of its children and looks for a prop called path or from in that. if it doesn't find it it assumes that every path will match. so it always shows the first children no matter what.
what you need to do is change the order of the children like this:
<Switch>
<Route exact path="/">
<Public>
<Home />
</Public>
</Route>
<Route path="/members">
<Private>
<Members />
</Private>
</Route>
</Switch>
I edited my questions as I realized it was not clear:
Visit https://codesandbox.io/s/dry-glitter-7lk9i?fontsize=14&hidenavigation=1&theme=dark
I was using states for the purpose of having the following structure:
Header
BODY (CONTENT)
Footer
Onclick actions or other events, I used states to hide components and show components in the body content.
I then wanted to be able to access a certain page by url ex (localhost:3000/privacy) So I'm looking to use Router to do so.
When I do a switch command, it does not hide my main component and show the switch, rather it shows both of them. How do I get the UI to react to the way I was initially coding?
You should wrap LandingPage component inside Route. Please check below for detail.
App.js
export default function App() {
return (
<Router>
<div className="App">
<Switch>
<Route exact path="/">
<Landingpage />
</Route>
<Route exact path="/businessregister">
<BusinessRegister />
</Route>
</Switch>
</div>
</Router>
);
}
Baymax has the correct answer but answering to explain a bit more.
The Switch component renders routes exclusively; it matches and returns the first matched route component. The Landingpage component iss always being rendered by the router no matter what the path is.
By moving Landingpage onto a route you can conditionally render it based upon the current path. Placing it last and not specifying a path means that if any route declared before it is matched and returned then it won't render, but if no routes match, then the Landingpage component route will match all paths and render.
function App() {
return (
<Router>
<div className="App">
<Switch>
<Route exact path="/businessregister">
<BusinessRegister />
</Route>
<Route component={Landingpage} /> // <-- render if nothing matches above
</Switch>
</div>
</Router>
);
}
I'm trying to figure out how to change the state of my Dashboard when a new route is clicked. This new route change needs to update the TopMenu component.
This is the code for the Dashboard
class Dashboard extends Component {
constructor (props) {
super(props)
this.state = {
selectedMenuItem: 'Now'
}
}
render () {
return (
<Router>
<div id='dashboard-container'>
<LeftMenu/>
<div className='column'>
<TopMenu selectedMenuItem={this.state.selectedMenuItem} />
<div id='content-container'>
<div>
<Switch>
<Route exact path='/' component={Now} />
<Route exact path='/surveys' component={Surveys} />
<Route exact path='/my-questions' />
<Route exact path='/feedback' />
<Route exact path='/logout' />
<Route render={function () {
return <p>Not Found</p>
}} />
</Switch>
</div>
</div>
</div>
</div>
</Router>
)}}
This is the code for the TopMenu
class TopMenu extends Component {
render () {
return (
<h3>{this.props.selectedMenuItem}</h3>
)
}
}
How do I listen to a change in React Router such that I can change the 'selectedMenuItem' state variable in the Dashboard and pass that to TopMenu.
Thanks!
Assuming you are using version 4, check the usage of match in React Router 4: https://reacttraining.com/react-router/web/api/match
match will include all the path information you will need to handle route changes. You can access it by calling this.props.match in your top level page component.
I had the same problem and I solved it with withRouter which is build for integrate router with redux, the only thing you need to do is wrap your connect with withRouter. for more information read redux integration document
How to set Header Fixed for all components and only components should be render in the body section moreover header title also need to show dynamically based on components renders in body section
is it possible through Router or from redux
This is shown in the first example of the React Router Docs.
You want to wrap all routes in one main, container route usually called App:
render((
<Router history={browserHistory}>
<Route path="/" component={App}>
<Route path="about" component={About}/>
</Route>
</Router>
), document.getElementById('root'))
Inside the App component you can then make use of this.props.children to show the components based on the route.
class App extends Component {
render() {
return (
<div>
<HeaderAlwaysShown />
{this.props.children || <DefaultComponent />}
</div>
)
}
}
Assume this is your layout.jsx file
render() {
return(
<div>
<Header/>
<YourRouterHandler>
</div>
)
}
For your header - create store, and update it according to your currently rendered page. And take its value in your <Header/>
I am attempting to build out some nested routes with react-router.
My route are set up like so:
<Provider store={store}>
<Router history={history}>
<Route path="/" component={App}>
<Route component={Main}>
<IndexRoute component={RepositoryList} />
<Route path="profile" component={Profile} />
</Route>
<Route path="login" component={Login} />
<Route path="admin" component={Admin} />
</Route>
</Router>
</Provider>
The App component connects the redux state and actions to the props of ViewWrapper with connect(mapStateToProps, mapDispatchToProps)(ViewWrapper).
ViewWrapper render method contains {React.cloneElement(this.props.children, this.props)} along with some other global elements.
Now Main contains the following:
render() {
return (
<div className={styles.main}>
<Banner {...this.props} />
{React.cloneElement(this.props.children, this.props)}
</div>
)
}
My understanding is that RepositoryList or Profile will be pulled into {React.cloneElement(this.props.children, this.props)} depending on which route is hit. This is where I am running into problems, there is an infinite loop which is producing the following error.
TypeError: Cannot read property '__reactInternalInstance$s50mbbsix2n2nxx9dg8gmbo6r' of null
My gut tells me that Main is trying to nest itself inside itself when passing this.props.
I feel like this must be a common problem but have had no luck finding solutions that work for my case, any help is appreciated.
A big thank you to #Yongzhi for pointing me in the right direction on this one.
Okay, so it turns out I had been using redux in the wrong manner. I was passing props to all children that required them through {...this.props} which was hooking every component up to absolutely everything in the store. At the time this seemed expensive but I just went with it. This meant that the children prop was getting overridden. And it was recursively nesting itself.
It turns out I should have been connecting each component to only the relevant parts of the store with react-redux's connect method. After that, simple using {this.props.children} worked for my nested components.
A complete example:
import React from 'react'
import { connect } from 'react-redux'
const Component = React.createClass({
render() {
return (
<div>
<p>{this.props.globals.heading}</p>
{this.props.children}
</div>
)
}
})
function mapStateToProps(state) {
return { globals: state.globals }
}
export default connect(mapStateToProps)(Component)
If anyone is in the same boat, read this page on the connect method and it will all become very clear.