I'm using MobX #observer and #withRouter (react-router-v4) wrap page component like this
#withRouter
#inject('stores')
#observer
class Page extends Component {
render() {
return (
<div>
<NavBar />
<Header title={this.props.stores.UIStore.title} />
<Switch>
<Route exact path="/" component={HomePage} />
<Route exact path="/about" component={AboutPage} />
<Route component={NotFound} />
</Switch>
</div>
)
}
Problem
When route location change the NavBar and Header component alway re-render with same props (no any state update). react-perf show many wasted render (no props/state update) when route change.
NavBar include Link and some MobX state (NavBar wrap with #observer+#inject only)
Header is just a stateless component.
Page component require #withRouter cause of #observer (MobX) break react-router (https://github.com/mobxjs/mobx-react/issues/210)
How to prevent NavBar and Header re-render from route location change? Allow re-render only when mobx state update.
I know this is quite old, but that’s how I solved the same problem in my project:
Don’t use withRouter and observer together. Observer implementation of shouldComponentUpdate will return true if location or match objects change.
When you just need a history or location objects use routerStore from mobx-react-router (https://github.com/alisd23/mobx-react-router)
When you need match (usually because of params), make a non-observer component that uses withRouter and pass necessary params to lower levels of hierarchy.
Related
I'm using React Router with TypeScript. Here's two routes, namely / and /pages/id.
class MyComponent extends React.Component
{
render()
{
return <BrowserRouter>
<div>
<Route exact path='/' children={
() =>
<TopPage />} />
<Route exact path='/pages/:id' children={
(props: RouteComponentProps<{id: string}>) =>{
console.log(props)
return <IndividualPage match={props.match} />}} />
</div>
</BrowserRouter>
}
}
I expect, since my Routes are exact:
when I access /, only TopPage component is rendered
when I access /pages/1, only IndividualPage component is rendered
when I access /no-pages, nothing is rendered
However, I observed:
when I access /, or /no-pages, IndividualPage component is rendered with match=null (causing Cannot read property 'params' of null error)
when I access /pages/1, both TopPage and IndividualPage components are rendered
What am I doing wrong?
There are four different ways to set the render component for a Route, some of which behave slightly differently than others. Setting the children prop to a render function is one of those ways, but it's not the one that you want based on the behavior you expect. Unlike the other methods, the contents of a children function are always rendered even if the current page is not a match. So you will have both components rendering on every page. Per the docs:
Sometimes you need to render whether the path matches the location or not. In these cases, you can use the function children prop. It works exactly like render except that it gets called whether there is a match or not.
Instead you can use the render prop or the component prop. These will only render if the route is a match.
The default behavior of the Router is to render all Routes which match, not just the first. In practice, this is a non-issue with your example because you have two exact routes so there is no possibility that both will match. But as a habit I would recommend putting your Route components inside of a Switch component which will only render the first matching Route.
class MyComponent extends React.Component {
render() {
return (
<BrowserRouter>
<Switch>
<Route exact path="/" component={TopPage} />
<Route exact path="/pages/:id" component={IndividualPage} />
</Switch>
</BrowserRouter>
);
}
}
No typescript annotations are needed here because the Route component knows that RouteComponentProps will be passed to the component.
I’m using the last release of React Router and I want to know what is the best solution to show different component that are nested in a parent component.
I’ll try to explain myself better.
I have a route R with path /r1.
This route loads component A.
Component A has inside others 3 components B, C and D that I should show in the same page with component A only when the user press a specific button in component A.
So I want to be able to add a description in route R to manage this. For example to show component B the router could be /r1/b.
Now I did this with a state variable inside component A but I think should be better if I can use some React Router property.
Thanks
You can create nested routes component, and it will manage nested routes.
export default function NestedRoutes() {
return (
<Switch>
<Redirect exact from={"/r1"} to={`/r1/A`} />
<Route path={`/r1/A`}>
<ComponentA />
</Route>
<Route path={`/r1/B`}>
<ComponentB />
</Route>
// Or to some not found component
<Redirect to="/r1/A" />
</Switch>
);
}
I'm using Switch with my route entries. The problem was that I didn't know how to render a component that I wanted to pass by props to another component.
I added a prop component to my parent component A and in my route I wrote something like this:
<Route path="/r1/hub/A" render={() => <A /> //this render only A
<Route path="/r1/hub/A/B" render={() => <A component={B} /> //this render A with B
In component A I used React.createElement to render component B with others properties that component A has to inject.
I think I've got some issue with react router. How can I prevent react router from re-rendering a previously rendered page?
I have router code like this:
class App extends React.Component {
render() {
return (
<div>
<NavBar/>
{this.props.children}
</div>
);
}
}
ReactDOM.render(
(
<Router history={hashHistory}>
<Route path="/" component={App}>
<IndexRoute component={Gateway} />
<Route path="home" component={Gateway} />
<Route path="categories" component={Categories} />
</Route>
</Router>
), document.getElementById('my-app')
);
When I first visit the page, it hits index, Gateway component got rendered. Then I click on "categories" link, and Categories component got rendered. Then when I click "home" link again, the component Gateway got re-rendered. Its state got RESET. This is really frustrating, as I could not figure our why its state got reset.
Is there any solution for this?
If there is any state you want to be saved, you should store it somewhere, such as the component's state (or the redux state if you use redux).
In react, you can define the function shouldComponentUpdate() within components in order to force React not to re-render your DOM. But in the case of the react-router, the DOM of the first route is destroyed (not just hidden) and therefore should be re-rendered.
My app index start with:
ReactDOM.render(
<Provider store={store}>
<Router history={browserHistory}>
<Route path="/" component={App}>
<IndexRoute component={Main} />
<Route path="/search" component={MovieSearch} />
<Route path="/movies" component={MovieList} />
</Route>
</Router>
</Provider>,
document.getElementById('root')
);
My Main.js:
render() { return (<div>{this.props.children}</div> )}
When I access URI: /movies MovieList props stay undefined until I connect the component to the store. Is this correct? I need to connect every component to the store to access state? I can't get props from Main parent props without this?
I really need to call mapStateToProps and sometimes mapDispatchToProps to access state and actions ?
This is the default/good practice?
The answer is, Yes
Unless you have a layout component, you need to connect each component to work with redux.
You can take a look at one of my project. I used page.js as my router. It is very simple and solves my purpose. For every route, I pass the name of the component to be rendered. So, a layout page will be loaded where I connect redux and pass the state as props to the child components. And based on the render passed by the router. I will render the component inside the layout.
Using layout is a very good practice. It becomes a common place where all your components gets rendered.
Useful links:
mapStateToProps in Redux
[Update]
You should also take a look at Redux Router
I have a component which cannot traditionally inherit props from a parent component. This component is rendered via a route and not by a parent, I am talking about the <Single /> component which is the 'detail' component in this setup:
<Router history={browserHistory}>
<Route path="/" component={App}>
<IndexRoute component={ProfileList} />
<Route path="/profile/:username" component={Single} /></Route>
</Router>
Props are available in the ProfileList component and that component renders a Profile component like so:
/* ProfileList render method */
render() {
return (
<div>
{this.state.profiles.map((profile, i) =>
<Profile {...this.state} key={i} index={i} data={profile} />)}
</div>
);
}
I am trying to reuse the Profile component in both the ProfileList and Single component:
<Link className="button" to={`/profile/${username}`}>
{name.first} {name.last}
</Link>
But in the Single component I have no way of accessing state or props - so I have no way of rendering this details view. I know I can either use redux for passing global state or use query parameters in my Link to=""
But I don't want tor reach out for redux yet and don't feel right about query params. So how do I access something like this.props.profiles in my Single component?
the redux connect() can completely do the job. I think you should use it, because "in fine" you will reimplement a redux connect like