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.
Related
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'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.
When changing from indexRoute to the Route with path ':topic', the page just stays the same.
My routes look like this:
ReactDOM.render(
<Provider store={store}>
<Router history={browserHistory}>
<Route path='/' addHandlerKey={true} component={App}>
<IndexRoute component={ArticleList} />
<Route path='/:topic' component={ArticleList} />
<Route path='/:id' component={Article} />
</Route>
</Router>
</Provider>, document.getElementById('app'));
ArticleList looks like this:
const _ArticleList = React.createClass({
componentWillMount () {
this.props.fetchArticles();
},
render () {
return (
<div id='ArticleList' className='box'>
{this.props.articles.map(function (article, i) {
return <ArticleCard id={article._id} key={i} title={article.title} votes={article.votes} />;
})}
</div>
);
}
When I navigate to the route then refresh the page, it changes to what I want, just not when navigating to it directly.
To be clear, the url is changing (thanks to App component) so params.topic is changing but the component is not re-rendering so componentWillMount is not getting triggered. (Have tried componentWillReceiveProps but this didnt work)
The problem you are running into is that once the index page has loaded the component will have mounted and wont unmount unless you change to a completely different route all together. You are just updating a child route which doesn't retrigger the componentWillMount to fire off again.
You need to revisit componentWillReceiveProps because that would be the right way to do this.
something like:
componentWillReceiveProps(nextProps) {
// in our own app we have acces to routeParams as the :id portion not sure if that works on your app
// but idea should be if the topic has changed then refetch the articles
if (this.props.routeParams !== nextProps.routeParams) {
this.props.fetchArticles();
}
}
I noticed the same behavior when I had attempted to use conponentWillMount() -- which is a misspelling of componentWillMount().
The misspelling didn't trigger any warnings or errors, and of course didn't trigger the events I intended to call.
So I have my routes defines as follows:
<Route path="/manage" component={Manage}>
<IndexRoute component={Manage}></IndexRoute>
<Route path=":id" component={Edit}></Route>
</Route>
</Route>
Now when I click on a button in my Manage component I call following function:
handleEditClick(e) {
e.preventDefault();
let selectedId= this.state.selectedId;
this.props.router.replace("/manage/" + selectedId);
},
My browser does display me the correct link but my component is not loaded as should. It only renders me the Manage component and not the Edit component.
Am I missing something here?
UPDATE
changing the child route to <Route path="/manage/:id" component={Edit}></Route> also loads me the Manage component
UPDATE 2
if I do not use child routes but in stead create them on the same level, the Edit component does render, but I'd like to use child routes.
On your render() you need a {this.props.children} ... it's where React router knows where to put child Component
In your routes
<Route path="/manage" component={Manage}>
<IndexRoute component={Manage}></IndexRoute>
<Route path=":id" component={Edit}></Route>
</Route>
If you navigate to /manage/:id, React Router renders Manager and Edit components... but where to put Edit?
So
you need to have something like this.
class Manager extends Component {
render(){
return (
<div>
Hello
{this.props.children}
</div>
);
}
}
So React Router knows to put Edit along side when you go to /manage/:id
<div>
Hello
<Edit />
</div>
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