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.
Related
I've been following this tutorial for creating a react project. The tutorial led to creating multiple react components that have a simple sample text within them. This would allow for the testing of the react-router-dom.
example of the simple component, all other components are similar.
import React, { Component } from 'react'
export default class Cart extends Component {
render() {
return (
<div>
<h3>Hello From Cart</h3>
</div>
)
}
}
The components are displayed using a react router which switches the displayed component depending on the url
class App extends React.Component {
render() {
return (
<React.Fragment>
<NavBar/>
<Switch>
<Route path="/details" Component={Details} />
<Route path="/cart" Component={Cart} />
<Route path="/" Component={ProductList} />
<Route Component={Default} />
</Switch>
</React.Fragment>
);
}
}
Furthermore to avoid confusion, my browser router is encapulating my App component from the index.js
ReactDOM.render(
<Router>
<App />
</Router>
, document.getElementById('root'));
When I navigate to the /cart url on my local host these are my results.
What should be displayed:
https://imgur.com/fZw5QnP.png
However, what is displayed is:
https://i.imgur.com/F1O07Y8.png
Please help me fix this issue, thank you.
I realized my error, I had "Component={}" within the Route, when it was supposed to be "component={}" (lowercase "c"). I'm posting this for all those, who have the same issue.
I have a component, App component, NewHome Component and Results component.
My route in component App looks like
<Route exact path="/" component={NewHome} />
<Route path="flights/search/roundtrip" component={Results} />
NewHome Routes look like
<Route path="/flights" component={FlightSearch} />
<Route path="/hotels" component={HotelSearch} />
Results component is class based react component
Only App component gets render, but component inside, I can see null in react devtools and Ui only has the part of App not part of results
<Route path="flights/search/roundtrip" component={Results}>
null
<Route />
Ideally, it should work, but I am not able to understand what I am doing wrong here. Any pointers or suggetions will be helpful. What can be the probable reasons for this kind of behaviour
This is the code where i am doing reactdom.render in index.js file
ReactDOM.render(
<Provider store={store}>
<ConnectedRouter history={history}>
<Route path="/" component={App} />
{/* {routes} */}
</ConnectedRouter>
</Provider>,
document.getElementById("root")
)
Note: full url is something like this
http://localhost:3001/flights/search/roundtrip?search_type=roundtrip&origin=BLR&destination=MAA&travel_date=2018-01-29&return_date=2018-02-13&class_type=ECONOMY&adult_count=1&child_count=0&residentof_india=true&infant_count=0&originAndDestinationSwapped=false&activeIndexMulticity=0
NewHome gets a match prop. With that, simply contact its url with nested routes you might need.
NewHome component:
render() {
const { match } = this.props
return (
<div>
<Route path={`${match.url}/flights`} component={FlightSearch} />
<Route path={`${match.url}/hotels`} component={HotelSearch} />
</div>
)
}
I think its because you forgot a '/' in your route.
try
<Route path="/flights/search/roundtrip" component={Results} />
instead of
<Route path="flights/search/roundtrip" component={Results} />
try exporting your component after wrapping your component in withRouter HOC
import { withRouter} from 'react-router-dom';
and export the component as follows
export default withRouter(Results);
I have found out the issue, in my app i was importing Route from react-router-dom. Its available in react-router. May be a correct error message would have helped :) Thanks #sujit and #mersocarlin for giving valuable inputs.
I'm using a route config as defined here. But, when using this method of router configuration how do I pass props to the components?
Looking at the following method from the docs there doesn't seem to be a way to provide props:
const RouteWithSubRoutes = (route) => (
<Route path={route.path} render={props => (
// pass the sub-routes down to keep nesting
<route.component {...props} routes={route.routes}/>
)}/>
)
Doing something like the following doesn't work. The prop doesn't get passed through:
{routes.map((route, i) => (
<RouteWithSubRoutes key={i} user={this.state.user} {...route}/>
))}
I am using React's default state management. Do I need Redux for this?
Here is example of how to use Flux + React together to pass props. I hope this is useful for you. Let me know if you have other questions.
AppComponent.js
In your AppComponent, you would need to pass only the pages you need to render.
render () {
<div>
<Route path="/login" component={Login} />
<Route exact path= "/:date?" component={Dashboard} />
</div>
}
In your app.js component, you import all services, actions, and main store component you need. Checkout bottle.js for easier way to pass values and services. However, you would need to just render
ReactDOM.render(
(
<BrowserRouter>
<Route path="/" AppStore={AppStore} render={props => <AppComponent {...props} AppStore={AppStore} />} />
</BrowserRouter>
),
document.getElementById("view-container")
);
You should let the parent component like the component Dashboard in AppComponment.js get the data passed from app.js to AppComponent to Dashboard (and Dashboard's children components).
As for AppStore, this would be like a container for all your other stores. You would need import all your other data store components and actions.
I have the following JSON object...
{ name: "Jessie" }
And I want to be able to pass it through my Router so that it can be displayed on my pages. For example, this is my root page...
StaticPage.jsx
export default class StaticPage extends React.Component {
render() {
return (
<div>
<Router history={hashHistory}>
<Route path='/' component={Search} />
<Route path='/favorites' component={Favorites} />
</Router>
</div>
);
}
}
So passing this data to Search, I would imagine might look something like this...
<Route path='/' component={Search} name = {this.props.name}/>
However, nothing gets rendered when I do that. I have researched this quite a bit and understand, from what I've read, that you cannot pass objects through the Router. It's very odd bc Router looks like a traditional React component but does not function as such. None of the explanations of a work around seem clear to me. Could anyone provide me with an example using this code? I am using react-router 3.0. There didn't seem to be any magical solution with 4.0 so I figured I'd ask before upgrading. Thanks!
It's because the component prop of <Route> only renders the component with route props, not your supplied props.
You can use the render or component prop on a <Route> in React Router v4 to pass a function which returns a <Search> element that explicitly passes the name:
<Route path="/" render={() => <Search name={this.props.name} />} />
Or with component:
<Route path="/" component={() => <Search name={this.props.name} />} />
But you should prefer render or else you'll have lots of remounting. If you still plan to use route props, you can also do:
render={routeProps => <Search name={this.props.name} {...routeProps} />}
A whole different approach, one more elegant in my opinion is to use route params and pass the name directly through the URL:
<Route path="/:name" component={Search} />
When you navigate to /Bob, you can access this.props.match.params.name which'll give you "Bob".
It is not a good practice to pass the object data via the routes directly. It is recommended to pass a param such as name or id in this way:
<Route path='/favorites/:name' component={Favorites} />
And retrieve it from your request in the destination.
This is a duplicate issue: Pass object through Link in react router
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 :)