This is driving me crazy. When I try to use React Router\'s Link within a nested route, the link updates in the browser but the view isn\'t changing. Yet if I refresh the page t
Could not get to the bottom of this, but I was able to achieve my goals with ComponentWillRecieveProps:
componentWillReceiveProps(nextProps){
if (nextProps.params.slug !== this.props.params.slug) {
const {dispatch, params} = nextProps;
PortfolioDetail.readyOnActions(dispatch, params, true);
}
}
In other words, for whatever reason when I use React Router Link to link to a page with the SAME PARENT COMPONENT, it doesn't fire componentWillUnMount/componentWillMount. So I'm having to manually trigger my actions. It does work as I expect whenever I link to Routes with a different parent component.
Maybe this is as designed, but it doesn't seem right and isn't intuitive. I've noticed that there are many similar questions on Stackoverflow about Link changing the url but not updating the page so I'm not the only one. If anyone has any insight on this I would still love to hear it!
componentWillReceiveProps is the answer to this one, but it's a little annoying. I wrote a BaseController "concept" which sets a state action on route changes EVEN though the route's component is the same. So imagine your routes look like this:
<Route path="test" name="test" component={TestController} />
<Route path="test/edit(/:id)" name="test" component={TestController} />
<Route path="test/anything" name="test" component={TestController} />
So then a BaseController would check the route update:
import React from "react";
/**
* conceptual experiment
* to adapt a controller/action sort of approach
*/
export default class BaseController extends React.Component {
/**
* setState function as a call back to be set from
* every inheriting instance
*
* @param setStateCallback
*/
init(setStateCallback) {
this.setStateCall = setStateCallback
this.setStateCall({action: this.getActionFromPath(this.props.location.pathname)})
}
componentWillReceiveProps(nextProps) {
if (nextProps.location.pathname != this.props.location.pathname) {
this.setStateCall({action: this.getActionFromPath(nextProps.location.pathname)})
}
}
getActionFromPath(path) {
let split = path.split('/')
if(split.length == 3 && split[2].length > 0) {
return split[2]
} else {
return 'index'
}
}
render() {
return null
}
}
You can then inherit from that one:
import React from "react"; import BaseController from './BaseController'
export default class TestController extends BaseController {
componentWillMount() {
/**
* convention is to call init to
* pass the setState function
*/
this.init(this.setState)
}
componentDidUpdate(){
/**
* state change due to route change
*/
console.log(this.state)
}
getContent(){
switch(this.state.action) {
case 'index':
return <span> Index action </span>
case 'anything':
return <span>Anything action route</span>
case 'edit':
return <span>Edit action route</span>
default:
return <span>404 I guess</span>
}
}
render() {
return (<div>
<h1>Test page</h1>
<p>
{this.getContent()}
</p>
</div>)
}
}
I got stuck on this also in React 16.
My solution was as follows:
componentWillMount() {
const { id } = this.props.match.params;
this.props.fetchCategory(id); // Fetch data and set state
}
componentWillReceiveProps(nextProps) {
const { id } = nextProps.match.params;
const { category } = nextProps;
if(!category) {
this.props.fetchCategory(id); // Fetch data and set state
}
}
I am using redux to manage state but the concept is the same I think.
Set the state as per normal on the WillMount method and when the WillReceiveProps is called you can check if the state has been updated if it hasn't you can recall the method that sets your state, this should re-render your component.
It's good to share the components code also. However, I tried to recreate the same locally and is working fine for me. Below is the sample code,
import { Route, Link } from 'react-router';
import React from 'react';
import App from '../components/App';
const Home = ({ children }) => (
<div>
Hello There Team!!!
{children}
</div>
);
const PortfolioDetail = () => (
<div>
<Link to={'/portfolio/previous-item'}>
<button className="button button-xs">Previous</button>
</Link>
<Link to={'/portfolio/next-item'}>
<button className="button button-xs">Next</button>
</Link>
</div>
);
const PortfolioItemDetail = () => (
<div>PortfolioItemDetail</div>
);
const NoMatch = () => (
<div>404</div>
);
module.exports = (
<Route path="/" component={Home}>
<Route path='/' component={Home}>
<Route path="/index:hashRoute" component={Home} />
</Route>
<Route path="/portfolio" component={PortfolioDetail} />
<Route path="/portfolio/:slug" component={PortfolioItemDetail} />
<Route path="*" component={NoMatch} />
</Route>
);
I am uncertain whether it fixes the original problem, but I had a similar issue which was resolved by passing in the function callback () => this.forceUpdate()
instead of this.forceUpdate
.
Since no one else is mentioning it, I see that you are using onClick={this.forceUpdate}
, and would try onClick={() => this.forceUpdate()}
.