Do you want to request a feature or report a bug?
Bug
What is the current behavior?
I have a component and I'm using the life cycle componentWillReceiveProps():
import { connect } from 'react-redux';
import { Component } from 'react';
class MyApp extends Component {
constructor(props) {
super(props);
this.state = {
currentTab: 0,
};
}
UNSAFE_componentWillReceiveProps(nextProps) {
if (this.state.currentTab !== nextProps.tabNumber) {
this.setState({ currentTab: nextProps.tabNumber });
}
}
}
connect(state => ({
tabNumber: state.common.editMyAppTabs.tabNumber,
}))(MyApp);
I tried to change the componetWillReceiveProps by getDerivedStateFromProps :
import { connect } from 'react-redux';
import { Component } from 'react';
class MyApp extends Component {
constructor(props) {
super(props);
this.state = {
currentTab: 0,
};
}
static getDerivedStateFromProps(props, state) {
if (state.currentTab !== props.tabNumber) {
return { currentTab: props.tabNumber };
}
return null;
}
}
connect(state => ({
tabNumber: state.common.editMyAppTabs.tabNumber,
}))(MyApp);
I got this error:
Did not properly initialize state during construction. Expected state to be an object, but it was undefined.
The above error occurred in the <Translate(ReduxForm)> component:
in Translate(ReduxForm) (created by WithStyles(Translate(ReduxForm)))
in WithStyles(Translate(ReduxForm)) (created by Connect(WithStyles(Translate(ReduxForm))))
in Connect(WithStyles(Translate(ReduxForm))) (created by Route)
in Route (created by withRouter(Connect(WithStyles(Translate(ReduxForm)))))
in withRouter(Connect(WithStyles(Translate(ReduxForm)))) (created by MyAppWrapper)
in MyAppWrapper (created by Route)
in Route (created by ProtectedRoute)
in ProtectedRoute (created by Main)
in Switch (created by Main)
in main (created by Main)
in Main (created by AppRoutes)
in div (created by AppRoutes)
in AppRoutes (created by App)
in Router (created by BrowserRouter)
in BrowserRouter (created by App)
in Provider (created by App)
in I18nextProvider (created by App)
in MuiThemeProvider (created by App)
in App
and the state = null
What is the expected behavior?
No warning and error problem. Does this due to the connect function ?
Which versions of React, and which browser / OS are affected by this issue? Did this work in previous versions of React?
react: 16.4.0
OS: ubtunu 17.10 (64bit)
Can you try replacing your code by
static getDerivedStateFromProps(props, state) {
if (state.currentTab !== props.tabNumber) {
return { currentTab: props.tabNumber };
}
return state;
}
I think this should resolve the issue as it is expecting you return something which in your case is above.
@mrgurdeep thanks for the proposition but I got the same error.
@slim-hmidi This code works well for me:
class MyApp extends React.Component {
constructor(props) {
super(props);
this.state = {
currentTab: 0,
};
}
static getDerivedStateFromProps(props, state) {
if (state.currentTab !== props.tabNumber) {
return { currentTab: props.tabNumber };
}
return null;
}
render() {
return this.state.currentTab;
}
}
And I don't see how that error could happen. Can your try to create a simplified version of the app that still exhibits the problem?
I debug the component in my app and I realized that in the first load the getDerivedStateFromProps was called before the constructor. This point confused me, is not the constructor should be called first?
I debug the code and I didn't understand why the constructor was called after the gDSFP. I changed the gDSFP by the life cycle componentDidUpdate :
componentDidUpdate(prevProps, prevState) {
if (prevState.currentTab !== this.props.tabNumber) {
this.setState({ currentTab: this.props.tabNumber });
}
}
However I got an eslint-error mentioned that setState should not be included in componentDiDUpdate.
Based to eslint-plugin-react
I found :
Updating the state after a component update will trigger a second render() call and can lead to property/layout thrashing.
Any propositions for this?
@slim-hmidi from your stack trace it looks like you are using some higher order components. I would suggest looking into those components and seeing if they implement or use a buggy version of https://github.com/mridgway/hoist-non-react-statics (versions less than 2.5.0), or a similar pattern. There was a bug in that library that incorrectly hoisted getDerivedStateFromProps up to the parent HOC.
For example:
import hoist from 'hoist-non-react-statics';
const aHigherOrderComponent = Component => {
class Wrapped extends React.Component {
// does not initialize state
render() {
return <Component {...props} />
}
}
hoist(Wrapped, Component); // might "lift up" your components getDerivedStateFromProps method
return Wrapped;
}
I debug the component in my app and I realized that in the first load the
getDerivedStateFromPropswas called before the constructor.
This definitely should not be happening, unless something is hoisting lifecycle methods from your component to another component (e.g. copying your getDerivedStateFromProps onto a component that does not initialize state).
HOCs sometimes do this, as @hamlim mentioned. Specifically the hoist-non-react-statics library might be at fault since you're using react-redux.
It does look like the latest version of this library knows to not copy getDerivedStateFromProps. I suggest trying to either update react-redux or the hoist-non-react-statics dependency.
@slim-hmidi did you ever get this resolved?
@hamlim's solution worked for me. I searched through my package-lock and found my class decorator from react-css-modules had an old version of hoist-non-react-statics as a dependency.
(As well as updated my other dependencies like react-redux and react-i18next which used to have old versions).
Great. @slim-hmidi's I'll assume your issue was similar and that its been resolved. If not, please follow up with the a reproducing example.
Thanks!