React - Component Will Mount not getting called when changing routes? - reactjs

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.

Related

React Router rendering wrong component

I am using a HashRouter to render different pages. The problem I am having is that when I visit
/service/app/sort, it renders <MainPage /> when I am expecting it to render <ChildPage />.
Here's similar to what my router looks like
<HashRouter>
<Switch>
<Route path="/service/app">
<MainPage />
</Route>
<Route path="/service/app/sort">
<ChildPage />
</Route>
</Switch>
</HashRouter>
Additional information that may help:
on my <MainPage />, I have a button to redirect onClick
const history = useHistory();
const handleSortClick = () => {
console.log('click me')
let path = "/service/app/sort";
history.push(path);
};
When I click it once, I get the 'click me' console.log and the link changes to /service/app/sort, but it's still the <MainPage />. If I click it one more time, I get the warning
"Warning: Hash history cannot PUSH the same path; a new entry will not be added to the history stack"
You're probably facing this problem because the start of the URL is being interpreted as truthy by react-router-dom, and is letting you access the /service/app anyway.
For example, the /service/app route will not only detect this route, but will also detect /service/app/1 or /service/app1 just because it has /service/app in the path.
To prevent this, you need to pass the exact property to the route, so react-router will understand that you need to access exactly this route to render that component.
<Route path="/service/app" exact={true}>
<MainPage />
</Route>

when I use history.push url changes but doesn't render the component

I've seen problems like this but neither of them solved my problem.
I have a p tag that onClick is supposed to render a page.
But when I click on it and use this.props.history.push(/posts/${id}) It changes the url but doesn't render the component I want.
I have tried using a
onClick={(e, id) => this.postPage(e, post.id)}
to trigger a function that will trigger the component I want to show up. I also tried using
<Link to-=''>
insted of
<p onClick>
but it doesn't work either.
This is what I have done so far, this is the function that triggers when I click the p tag:
postPage = (e, id) => {
const { history } = this.props;
history.push(`/post/${id.toString()}`);
}
and also, I used withRouter to have access to the history prop.
export default withRouter(connect(null, mapDispatchToProps)(Post));
And these are the routes I mapped:
render() {
return (
<div className="Routes">
<Switch>
<Route exact path='/' component={Home}></Route>
<Route path='/post/:id' render={(history) => (
<PostPage {...history}></PostPage>
)}></Route>
</Switch>
</div>
);
}
}
And of course, when I call this component I also pass using BrowserRouter
ReactDOM.render(
<Provider store={store}>
<BrowserRouter>
<Routes />
</BrowserRouter>
</Provider>
, document.getElementById('root'));
I just wanted to render the component but only the URL changes and nothing happens.
A good approach would be to implement react-redux and react-router-dom one at a time, to ensure that there are no clashes. The double HOC here:
export default withRouter(connect(null, mapDispatchToProps)(Post));
Is worth breaking down to:
export default withRouter(Post);
To ensure that react-router-dom works as expected, then implementing react-redux once routing is in place.
For those who have had the same problem the solution was to change the BrowserRouter tag location from the index.js to inside of the component that uses the Route. I was doing something like:
ReactDOM.render(<Provider>
<BrowserRouter>
<App>
</BrowserRouter>
</Provider>, document.getElementById('root'));
and now inside the Routes.js component
render() {
<BrowserRouter>
<Route ...>
<Route ...>
</BrowserRouter>
}

How to avoid component rerendering when route change which similar to keep-alive in vuejs?

is there something in react which similar to keep-alive in vuejs?
I do not want to rerender pages when route changes,can anyone help me?
It seems that react doesn't support the similar feature keep-alive. We need to implement it by ourselves.
According to the source code of keep-alive component of vue, we need to cache the virtual dom (vnode in vue) of children in state. The keep-alive component is the wrapper of router, thus the cache will never be pruned when router changed.
In addition, I have a simple approach in my app, if you don't care about
the performance. The main idea is to hide/show the view component on router changing event, instead of mount/unmount.
<Router>
<Layout>
<Switch>
<Route path="/foo" component={Foo} />
<Route path="/bar" component={Bar} />
</Switch>
// the alive route match any path so that it will be never rerendered
<Route path="/:any" component={AliveComponent} />
<Layout>
</Router>
Codes in AliveComponent:
...
state = {isShown: false};
componentWillMount () {
const {history, location} = this.props;
this.setState({isShown: location.pathname === '/alive-view-path'});
const unlistenHistory = history.listen(
({pathname, state}, action) => {
this.setState({isShown: pathname === '/alive-view-path'});
}
);
this.setState({unlistenHistory});
}
componentWillUnmount () {
if (_.isFunction(this.state.unlistenHistory)) {
this.state.unlistenHistory();
}
}
render () {
return <div style={{display: this.state.isShown ? 'block' : 'none'}} />
}
...

React Router causing component remount on Firebase update

I have an App component which, using react-router, holds a few components in two routes. I also have a Firebase data store which I want to bind to the state of App (using rebase) so I can pass it down to any component I wish as a prop. This is my App class:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
items: {}
};
}
componentDidMount () {
rebase.bindToState('items', {
context: this,
state: 'items'
})
}
render() {
return (
<Router>
<div className='container'>
<div className="header">
<h3>Header</h3>
<Nav/>
</div>
<Switch>
<Route exact path='/' component={() => <Home items={this.state.items} rebase={rebase} />} />
<Route render={function () {
return <p>Not Found</p>
}} />
</Switch>
</div>
</Router>
)
}
}
Now, when I load my page I get two mounts of the Home component. This in itself is not great. However, I have several actions in the Home component that use rebase to modify/read from Firebase. As a callback of these actions they also change the Home component's state. The problem is, whenever I do a Firebase call, it remounts the Home component and any state I have is lost.
If I remove the Router wrappers from the Home component, and render it purely as render( <Home items={this.state.items} rebase={rebase} /> ), my app works perfectly as intended. I don't know why wrapping it in Router stuff makes it not work. I thought it was because I had additional URL parameters that also changed when I call firebase updates (e.g. /?p=sgergwc4), but I have a button that changes that parameter without a firebase update and it doesn't cause any problems (i.e. doesn't cause a remount). So what's up with the Router?
Turns out the answer is simple; instead of component={}, I should use render={}. Fixes everything. It was in the docs too.

How to prevent react router from re-rendering a previously rendered page?

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.

Resources