Skip to content

Legacy lifecycle use-case for componentWillReceiveProps #729

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
timroes opened this issue Mar 28, 2018 · 6 comments
Closed

Legacy lifecycle use-case for componentWillReceiveProps #729

timroes opened this issue Mar 28, 2018 · 6 comments

Comments

@timroes
Copy link

timroes commented Mar 28, 2018

In your update on async rendering blog post are a lot of examples on how to replace legacy lifecycle use-cases by the new methods. I am lacking an example for the following use-case: Change state depending on prop changes/diffs.

In contrast to the "Updating state based on props" which just looks into the new props and the previous state, I would like to see an example for a state change, that will require to look into the new and old properties - or an explanation why this is an anti-pattern.

A typical use-case I've seen often (and also used myself often) is, that I check within componentWillReceiveNewProps whether a specific property has changed from previous properties to new and if so change a state. I didn't save the previous property in the state (which I see how that would work with getDerivedStateFromProps, because it's never changed within the component or considered a "state" of the component).

A simple example of a tab container:

class TabContainer extends Component {

  state = {
    selectedTab: 0
  };

  componentWillReceiveNewProps(newProps) {
    if (newProps.tabs !== this.props.tabs) {
      // Reset selected tab if the tabs changed.
      // Though this could be a more sofisticated implementation, that keeps
      // the tab selected, if it still exists in the new tabs array, and only
      // resets to 0 if that tab doesn't exist anymore.
      this.setState({
        selectedTab: 0
      });
    }
  }

  /* Methods that change state.selectedTab when a specific tab is clicked. */

  renderTab = (tab, index) => {
    const active = this.state.selectedTab === index;
    return (/* tab depending on active state */);
  };

  render() {
    return (<div>
      <ul>
        { this.props.tabs.map(this.renderTab) }
      </ul>
      { /* selected tab content */ }
    </div>);
  }
}

TabContainer.propTypes = {
  tabs: PropTypes.array.isRequired,
};
@danburzo
Copy link
Contributor

I think this may be a duplicate of #721

@timroes
Copy link
Author

timroes commented Mar 28, 2018

Yeah that's a real duplicate. Will close this.

@gaearon
Copy link
Member

gaearon commented Mar 28, 2018

If you just need to perform a side effect in response to a prop change, you can do so from componentDidUpdate as I show here.

If you need to derive state from a comparison of previous and current props, please put the previous prop value in the state. Yes, it’s a bit more verbose, but it lets React hold onto less memory in the future in some cases (no need to keep the whole “previous props” object), and avoids the need to check prevProps for being null (since it wouldn’t exist on the initial render). I know if feels unusual compared to the existing code but that’s our recommendation.

class TabContainer extends Component {
  state = {
    selectedTab: 0,
    prevTabs: null
  };

  static getDerivedStateFromProps(props, state) {
    if (props.tabs !== state.prevTabs) {
      return {
        selectedTab: 0,
        prevTabs: props.tabs
      };
    }
    return null;
  }

  renderTab = (tab, index) => {
    const active = this.state.selectedTab === index;
    return (/* tab depending on active state */);
  };

  render() {
    return (<div>
      <ul>
        { this.props.tabs.map(this.renderTab) }
      </ul>
      { /* selected tab content */ }
    </div>);
  }
}

I know you’re saying prevTabs isn’t used for rendering, but it is applied during the render phase so in a technical way it is used for it. The blog post contains a similar example.

@danburzo
Copy link
Contributor

Ah, this also answers my dilemmas from #721 (comment)

I also see that both the blog post and your example make a point of naming props-in-state things like prevXXX or lastXXX, which I think is a nice touch.

@timroes
Copy link
Author

timroes commented Mar 28, 2018

@gaearon thanks for the clarification, that it's actually the recommended way to plainly move a property to state, if we need to compare on it. I think it would be a good addition to the blog post (maybe as a subparagraph of the actual props to state example). Also your example above lacks setting the prevTabs on the state, maybe you could fix this, since it seems those tickets get quiet some attention since the blog post.

@gaearon
Copy link
Member

gaearon commented Mar 28, 2018

Oops, fixed! Thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants