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 components that require some type of parameter coming from the URL. I also have components that do NOT depend on a parameter.
If I go to components 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 component that uses a parameter, then try to go another component that uses a parameter, I then get an error. The error indicates that the component react-router
is supposed to load is NOT mounted properly which throws an error telling me that the data the child component 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 components here
// Just showing one for brevity
import Home from '../components/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" component={Home} />
<Route exact path="/member/accounts" component={Accounts} />
<Route exact path="/member/projects" component={ProjectsList} />
<Route path="/member/projects/profile/:id" component={ProjectProfile} />|
<Route exact path="/member/tasks" component={TasksList} />
<Route path="/member/tasks/profile/:id" component={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
components require ID's. Furthermore, both ProjectProfile
and TaskProfile
components are rather simple parent components that do two things:
componentDidMount()
eventHere's my ProjectProfile
component 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);
};
componentDidMount() {
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 component 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 component 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 component 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 component with a parameter, if I go to another component with a parameter, I'm just NOT hitting the componentDidMount()
method in the second component. Looks like something in the render()
method is causing an issue and preventing it from going to componentDidMount()
. 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
component. 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 component with a param, when I hit the second component 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 component 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
components.
I still get the same behavior. So, something is preventing componentDidMount()
method from being called. I also tried componentDidUpdate()
to see if it was being called and it is NOT. I placed componentWillUnmount()
in both components and as I navigate elsewhere in the app, in both components componentWillUnmount()
gets called so one could argue that both components are unmounting OK.
So the bottom line is that something in the render()
method is preventing componentDidMount()
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!!!
Although you have a console log statement in your componentDidMount function it may not be executing because that function never gets run. If the error occurs before componentDidMount gets called such as in your render function, you won't see the console log.
How many times componentDidMount is called? React components call componentDidMount only once by default. You can run the component multiple times if you delete the component or change the props or state.
Render JavaScript with Initial RenderThe componentDidMount() method will be triggered as soon as the component is mounted or inserted into the DOM. The basic syntax to use componentDidMount() is looks like this. This method used widely by developers because it loads immediately once the component is mounted.
componentDidMount() method As the name suggests, after all the elements of the page is rendered correctly, this method is called. After the markup is set on the page, this technique called by React itself to either fetch the data from An External API or perform some unique operations which need the JSX elements.
It'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.
componentDidMount()
every time you load your component. Even if you go back and forth to load the same component. But you hit componentDidMount()
ONLY IF your component was unmounted in the first place. If the component does not get unmounted, you will NOT hit componentDidMount()
in your subsequent uses of the component. So look at your logic carefully to see if your component gets unmounted if you navigate away from it. I think the easiest way to see if this is happening is by adding componentWillUnmount()
method with a debugger
in it. If you're hitting this method when you navigate away from your component, that means it is unmounting.componentDidMount()
is still the best place to do it. Having said that you may need additional logic in your componentDidMount()
to make sure you don't make any unnecessary API calls because your data may still be in your store.componentWillReceiveProps()
method to make my API call to fetch data. This is bad advice! As a matter of fact, componentWillReceiveProps()
, componentWillUpdate()
and componentWillMount()
methods are being deprecated so we should not use them for two reasons: future compatibility and following best practices. Here's more info on why they're being deprecated: https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html
Hope these lessons will help others find their solutions quickly and painlessly!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With