Just a heads up: this is a really strange problem. I'll do my best to clearly explain the issue. This is happening in my ReactJs
app using React Router
.
I have some ponents that require some type of parameter ing from the URL. I also have ponents that do NOT depend on a parameter.
If I go to ponents that do not require any parameters, there are no problems whatsoever.
So, I go to my Home
, then Account List
, neither of which require any parameters, I can go back and forth as many times as I like, there's no problem.
However, if I go to a ponent that uses a parameter, then try to go another ponent that uses a parameter, I then get an error. The error indicates that the ponent react-router
is supposed to load is NOT mounted properly which throws an error telling me that the data the child ponent needs is missing. The errors I'm getting are pretty simple ones. They simply say something like:
this.props.something is required but undefined
Here's my routes in App.jsx
:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { Route, Switch, withRouter } from 'react-router-dom';
// Import ponents here
// Just showing one for brevity
import Home from '../ponents/home/Home';
import * as appActions from '../actions/app-actions';
class App extends Component {
constructor(props) {
super(props);
}
render() {
return(
<div>
<Switch>
<Route exact path="/member" ponent={Home} />
<Route exact path="/member/accounts" ponent={Accounts} />
<Route exact path="/member/projects" ponent={ProjectsList} />
<Route path="/member/projects/profile/:id" ponent={ProjectProfile} />|
<Route exact path="/member/tasks" ponent={TasksList} />
<Route path="/member/tasks/profile/:id" ponent={TaskProfile} />
</Switch>
</div>
);
}
}
function mapStateToProps(state) {
return {
member: state.member.memberData
}
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(appActions, dispatch)
};
}
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(App));
As you can see from the routes, both ProjectProfile
and TaskProfile
ponents require ID's. Furthermore, both ProjectProfile
and TaskProfile
ponents are rather simple parent ponents that do two things:
- Make an API call to load data in the
ponentDidMount()
event - They also load the correct child ponent based on user's screen resolution
Here's my ProjectProfile
ponent and the TaskProfile
is pretty much identical to this.
import React, { Component } from 'react'
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
// Actions
import * as projectProfileActions from '../../../actions/project-profile-actions';
// Components
import Desktop from './ProjectProfileDesktop';
import Mobile from './ProjectProfileMobile';
class ProjectProfile extends Component {
constructor(props) {
super(props);
};
ponentDidMount() {
const id = this.props.match.params.id;
this.props.actions.getData(id);
}
render() {
return (
<div className="height-100 width-100">
<div className="height-100 width-100 row row-clean">
{this.props.ui.isDesktop || this.props.ui.isTablet ? <Desktop />
: this.props.ui.isMobile ? <Mobile />
: null}
</div>
</div>
);
}
}
function mapStateToProps(state) {
return {
ui: state.app.window
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(projectProfileActions, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(ProjectProfile);
The strangest part about this is that if I keep going back and forth between ProjectProfile
and any other ponent and stay away from TaskProfile
, there are no problems. I can even hit ProjectProfile
with different ID's and everything works fine. It all fails only when I go to another ponent with parameter. I can do the same with TaskProfile
. I can keep hitting TaskProfile
with different ID's OR go back and forth between TaskProfile
and any ponent without a parameter and everything works fine.
Only if I go to ProjectProfile
first, then try to go to TaskProfile
or vice versa, the error occurs.
The error is simply telling me that the data ProjectProfileDesktop
requires is not there.
Further inspection shows me that after loading a ponent with a parameter, if I go to another ponent with a parameter, I'm just NOT hitting the ponentDidMount()
method in the second ponent. Looks like something in the render()
method is causing an issue and preventing it from going to ponentDidMount()
. I'm not sure what the issue is because I'm not getting any errors. I put a debugger
at the top of the render()
method. Though I'm not seeing an exact error, the flow shows me that something is definitely going wrong.
Please also notice that I'm using withRouter
. I've seen some issues involving withRouter
but again, I couldn't find anything concrete so far.
I also want to mention that I also have Stripe provider wrapping my App
ponent. Not sure if this is playing a role in this problem as well. Here's what the render()
looks like in my index.js
:
render(
<Provider store={store}>
<BrowserRouter history={browserHistory}>
<StripeProvider apiKey="my_key">
<App />
</StripeProvider>
</BrowserRouter>
</Provider>,
document.getElementById('root')
);
I'd appreciate some pointers on this.
UPDATE:
A behavior I'm observing is that after loading a ponent with a param, when I hit the second ponent with a param, I hit the render()
method first and right after the first createElement
, the code jumps to ReactInstrumentation.debugTool.onBeginLifeCycleTimer
-- see screen shot below.
UPDATE 2:
At this point, I'm convinced the issue is not caused by react-router
because I hard-coded the ID I needed into the ponent so that I don't depend on react-router
or anything else to get it.
I also removed /:id
parameter from the routes for my TaskProfile
and ProjectProfile
ponents.
I still get the same behavior. So, something is preventing ponentDidMount()
method from being called. I also tried ponentDidUpdate()
to see if it was being called and it is NOT. I placed ponentWillUnmount()
in both ponents and as I navigate elsewhere in the app, in both ponents ponentWillUnmount()
gets called so one could argue that both ponents are unmounting OK.
So the bottom line is that something in the render()
method is preventing ponentDidMount()
or any other lifecycle method from being called but this is only happening in this particular circumstance.
I'd appreciate any suggestions at this point!!!
Just a heads up: this is a really strange problem. I'll do my best to clearly explain the issue. This is happening in my ReactJs
app using React Router
.
I have some ponents that require some type of parameter ing from the URL. I also have ponents that do NOT depend on a parameter.
If I go to ponents that do not require any parameters, there are no problems whatsoever.
So, I go to my Home
, then Account List
, neither of which require any parameters, I can go back and forth as many times as I like, there's no problem.
However, if I go to a ponent that uses a parameter, then try to go another ponent that uses a parameter, I then get an error. The error indicates that the ponent react-router
is supposed to load is NOT mounted properly which throws an error telling me that the data the child ponent needs is missing. The errors I'm getting are pretty simple ones. They simply say something like:
this.props.something is required but undefined
Here's my routes in App.jsx
:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { Route, Switch, withRouter } from 'react-router-dom';
// Import ponents here
// Just showing one for brevity
import Home from '../ponents/home/Home';
import * as appActions from '../actions/app-actions';
class App extends Component {
constructor(props) {
super(props);
}
render() {
return(
<div>
<Switch>
<Route exact path="/member" ponent={Home} />
<Route exact path="/member/accounts" ponent={Accounts} />
<Route exact path="/member/projects" ponent={ProjectsList} />
<Route path="/member/projects/profile/:id" ponent={ProjectProfile} />|
<Route exact path="/member/tasks" ponent={TasksList} />
<Route path="/member/tasks/profile/:id" ponent={TaskProfile} />
</Switch>
</div>
);
}
}
function mapStateToProps(state) {
return {
member: state.member.memberData
}
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(appActions, dispatch)
};
}
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(App));
As you can see from the routes, both ProjectProfile
and TaskProfile
ponents require ID's. Furthermore, both ProjectProfile
and TaskProfile
ponents are rather simple parent ponents that do two things:
- Make an API call to load data in the
ponentDidMount()
event - They also load the correct child ponent based on user's screen resolution
Here's my ProjectProfile
ponent and the TaskProfile
is pretty much identical to this.
import React, { Component } from 'react'
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
// Actions
import * as projectProfileActions from '../../../actions/project-profile-actions';
// Components
import Desktop from './ProjectProfileDesktop';
import Mobile from './ProjectProfileMobile';
class ProjectProfile extends Component {
constructor(props) {
super(props);
};
ponentDidMount() {
const id = this.props.match.params.id;
this.props.actions.getData(id);
}
render() {
return (
<div className="height-100 width-100">
<div className="height-100 width-100 row row-clean">
{this.props.ui.isDesktop || this.props.ui.isTablet ? <Desktop />
: this.props.ui.isMobile ? <Mobile />
: null}
</div>
</div>
);
}
}
function mapStateToProps(state) {
return {
ui: state.app.window
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(projectProfileActions, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(ProjectProfile);
The strangest part about this is that if I keep going back and forth between ProjectProfile
and any other ponent and stay away from TaskProfile
, there are no problems. I can even hit ProjectProfile
with different ID's and everything works fine. It all fails only when I go to another ponent with parameter. I can do the same with TaskProfile
. I can keep hitting TaskProfile
with different ID's OR go back and forth between TaskProfile
and any ponent without a parameter and everything works fine.
Only if I go to ProjectProfile
first, then try to go to TaskProfile
or vice versa, the error occurs.
The error is simply telling me that the data ProjectProfileDesktop
requires is not there.
Further inspection shows me that after loading a ponent with a parameter, if I go to another ponent with a parameter, I'm just NOT hitting the ponentDidMount()
method in the second ponent. Looks like something in the render()
method is causing an issue and preventing it from going to ponentDidMount()
. I'm not sure what the issue is because I'm not getting any errors. I put a debugger
at the top of the render()
method. Though I'm not seeing an exact error, the flow shows me that something is definitely going wrong.
Please also notice that I'm using withRouter
. I've seen some issues involving withRouter
but again, I couldn't find anything concrete so far.
I also want to mention that I also have Stripe provider wrapping my App
ponent. Not sure if this is playing a role in this problem as well. Here's what the render()
looks like in my index.js
:
render(
<Provider store={store}>
<BrowserRouter history={browserHistory}>
<StripeProvider apiKey="my_key">
<App />
</StripeProvider>
</BrowserRouter>
</Provider>,
document.getElementById('root')
);
I'd appreciate some pointers on this.
UPDATE:
A behavior I'm observing is that after loading a ponent with a param, when I hit the second ponent with a param, I hit the render()
method first and right after the first createElement
, the code jumps to ReactInstrumentation.debugTool.onBeginLifeCycleTimer
-- see screen shot below.
UPDATE 2:
At this point, I'm convinced the issue is not caused by react-router
because I hard-coded the ID I needed into the ponent so that I don't depend on react-router
or anything else to get it.
I also removed /:id
parameter from the routes for my TaskProfile
and ProjectProfile
ponents.
I still get the same behavior. So, something is preventing ponentDidMount()
method from being called. I also tried ponentDidUpdate()
to see if it was being called and it is NOT. I placed ponentWillUnmount()
in both ponents and as I navigate elsewhere in the app, in both ponents ponentWillUnmount()
gets called so one could argue that both ponents are unmounting OK.
So the bottom line is that something in the render()
method is preventing ponentDidMount()
or any other lifecycle method from being called but this is only happening in this particular circumstance.
I'd appreciate any suggestions at this point!!!
Share Improve this question edited Jun 22, 2018 at 10:03 Joshua 3,2063 gold badges26 silver badges41 bronze badges asked Jun 13, 2018 at 0:47 SamSam 30.6k76 gold badges252 silver badges464 bronze badges 3- did you check if the ponent is unmounting? I think it's something with the routes and it never unmounts. – andrewgi Commented Jun 15, 2018 at 1:28
-
@andrewgi I'm hitting the
debugger
I placed inponentWillUnmount()
in both the parent and the child ponents so looks like it unmounts OK. – Sam Commented Jun 15, 2018 at 3:04 - Can you create a stackoverflow./help/mcve on codesandbox.io/s/new? – Roy Wang Commented Jun 16, 2018 at 0:09
2 Answers
Reset to default 8It's been more than a week I've been banging my head against the wall on this issue and it's finally solved. Admittedly, lots of concepts that I thought I understood became even clearer in the process.
The error was due a logical error on my part that handled "isLoading" animation logic -- yes, as simple and stupid as that!!!
What I really wanted to share here are the lessons I've learned for everyone who may experience a similar behavior in the future.
- You should hit
ponentDidMount()
every time you load your ponent. Even if you go back and forth to load the same ponent. But you hitponentDidMount()
ONLY IF your ponent was unmounted in the first place. If the ponent does not get unmounted, you will NOT hitponentDidMount()
in your subsequent uses of the ponent. So look at your logic carefully to see if your ponent gets unmounted if you navigate away from it. I think the easiest way to see if this is happening is by addingponentWillUnmount()
method with adebugger
in it. If you're hitting this method when you navigate away from your ponent, that means it is unmounting. - Did lots of research during this process to see where the best place to make API calls to fetch data and it's definitely clear that
ponentDidMount()
is still the best place to do it. Having said that you may need additional logic in yourponentDidMount()
to make sure you don't make any unnecessary API calls because your data may still be in your store. - During my research, some suggested that it's better to use
ponentWillReceiveProps()
method to make my API call to fetch data. This is bad advice! As a matter of fact,ponentWillReceiveProps()
,ponentWillUpdate()
andponentWillMount()
methods are being deprecated so we should not use them for two reasons: future patibility and following best practices. Here's more info on why they're being deprecated: https://reactjs/blog/2018/03/27/update-on-async-rendering.html - Maybe the most important lesson is this: the cause of the problem may be much simpler than you think -- it usually is -- and the logic in your code may be the cause of the issue -- it usually is!
Hope these lessons will help others find their solutions quickly and painlessly!
ComponentDidMount it's called just one time, when the ponent is mounted. As you are using a prop to then make an api call, I would use ponentWillReceiveProps and check props and then make an api call.
If you want yo check that the ponentDidMount is used by your flow, use a console.log to check it.
Regards!