Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Change state when properties change and first mount on React - Missing function?

Tags:

I have come across a problem about states based on properties.

The scenario

I have a Component parent which creates passes a property to a child component. The Child component reacts according to the property received. In React the "only" proper way to change the state of a component is using the functions componentWillMount or componentDidMount and componentWillReceiveProps as far as I've seen (among others, but let's focus on these ones, because getInitialState is just executed once).

My problem/Question

If I receive a new property from the parent and I want to change the state, only the function componentWillReceiveProps will be executed and will allowed me to execute setState. Render does not allow to setStatus.

What if I want to set the state on the beginning and the time it receives a new property? So I have to set it on getInitialState or componentWillMount/componentDidMount. Then you have to change the state depending on the properties using componentWillReceiveProps.

This is a problem when your state highly depends from your properties, which is almost always. Which can become silly because you have to repeat the states you want to update according to the new property.

My solution

I have created a new method that it's called on componentWillMount and on componentWillReceiveProps. I have not found any method been called after a property has been updated before render and also the first time the Component is mounted. Then there would not be a need to do this silly workaround.

Anyway, here the question: is not there any better option to update the state when a new property is received or changed?

/*...*/ /**  * To be called before mounted and before updating props  * @param props  */ prepareComponentState: function (props) {     var usedProps = props || this.props;      //set data on state/template     var currentResponses = this.state.candidatesResponses.filter(function (elem) {         return elem.questionId === usedProps.currentQuestion.id;     });     this.setState({         currentResponses: currentResponses,         activeAnswer: null     }); }, componentWillMount: function () {     this.prepareComponentState(); }, componentWillReceiveProps: function (nextProps) {     this.prepareComponentState(nextProps); }, /*...*/ 

I feel a bit stupid, I guess I'm loosing something... I guess there is another solution to solve this.

And yeah, I already know about this: https://facebook.github.io/react/tips/props-in-getInitialState-as-anti-pattern.html

like image 698
bichotll Avatar asked Apr 01 '15 18:04

bichotll


People also ask

How do you set state after component mount?

componentDidMount() invokes immediately after a component mounts. You can call setState() immediately in componentDidMount() and triggers an extra rendering, but this happens before the browser updates the screen, calling render() twice.

Do not use setState in componentDidMount React no did mount set state?

Disallow usage of setState in componentDidMount (react/no-did-mount-set-state) 💼 This rule is enabled in the following configs: all . Updating the state after a component mount will trigger a second render() call and can lead to property/layout thrashing.

Is it OK to setState in componentDidMount?

According to the React Documentation it's perfectly OK to call setState() from within the componentDidMount() function. It will cause render() to be called twice, which is less efficient than only calling it once, but other than that it's perfectly fine.

How do I change the state in a function in React?

To make the state change, React gives us a setState function that allows us to update the value of the state. Calling setState automatically re-renders the entire component and all its child components. We don't need to manually re-render as seen previously using the renderContent function.


1 Answers

I've found that this pattern is usually not very necessary. In the general case (not always), I've found that setting state based on changed properties is a bit of an anti-pattern; instead, simply derive the necessary local state at render time.

render: function() {   var currentResponses = this.state.candidatesResponses.filter(function (elem) {     return elem.questionId === this.props.currentQuestion.id;   });    return ...; // use currentResponses instead of this.state.currentResponses } 

However, in some cases, it can make sense to cache this data (e.g. maybe calculating it is prohibitively expensive), or you just need to know when the props are set/changed for some other reason. In that case, I would use basically the pattern you've written in your question.

If you really don't like typing it out, you could formalize this new method as a mixin. For example:

var PropsSetOrChangeMixin = {   componentWillMount: function() {     this.onPropsSetOrChange(this.props);   },    componentWillReceiveProps: function(nextProps) {     this.onPropsSetOrChange(nextProps);   } };  React.createClass({   mixins: [PropsSetOrChangeMixin],    onPropsSetOrChange: function(props) {     var currentResponses = this.state.candidatesResponses.filter(function (elem) {         return elem.questionId === props.currentQuestion.id;     });      this.setState({       currentResponses: currentResponses,       activeAnswer: null     });   },    // ... }); 

Of course, if you're using class-based React components, you'd need to find some alternative solution (e.g. inheritance, or custom JS mixins) since they don't get React-style mixins right now.

(For what it's worth, I think the code is much clearer using the explicit methods; I'd probably write it like this:)

componentWillMount: function () {   this.prepareComponentState(this.props); },  componentWillReceiveProps: function (nextProps) {   this.prepareComponentState(nextProps); },  prepareComponentState: function (props) {   //set data on state/template   var currentResponses = this.state.candidatesResponses.filter(function (elem) {     return elem.questionId === props.currentQuestion.id;   });   this.setState({     currentResponses: currentResponses,     activeAnswer: null   }); }, 
like image 86
Michelle Tilley Avatar answered Sep 19 '22 13:09

Michelle Tilley