Bastien Goepfert
Bastien Goepfert

Reputation: 23

ReactJS: Mutating child state from parent via props

Disclaimer: I have looked at Reactjs: how to modify child state or props from parent? and do not believe the answer fits my question.

So I have a reusable, stateful conversation component that renders different DOM based on its state. I also have to be able to control which DOM is rendered from the parent.

TL;DR - How should I mutate a child components state from the parent, if at all, what other options are there?

The Child:

export default class Conversation extends Component {
  constructor(props) {
    super(props);
    
    this.state = {
      newConversation: props.showNewConversation
    };
  }
  
  render() {
   if (!this.state.newConversation) {
    return (
      <div>Current Conversation</div>
    );
   } return (
      <div>New Conversation</div>
    );
  }
}

Now I need to render this component in various places, but I need to render the appropriate DOM depending on the parent, i.e. from the navbar you can create a new conversation, and from the users page you can go directly to your conversation with them, so I can control when I call the child via it's prop.

Calling child and setting state via prop:

<Conversation
  showNewConversation={this.state.isConversationShown === true}
/>

This is currently working, but I've been told this is a very bad practice in React, my question is actually why this is considered bad practice, and what a good practice solution would look like.

Upvotes: 2

Views: 3603

Answers (1)

Dennis Shtatnov
Dennis Shtatnov

Reputation: 1363

In React, the general best practice is to make as many components 'state-less' if possible. Usually, state isn't necessary if you aren't loading async data or are accepting user input.

In this example the main issue happens as follows: If the parent renders

<Conversation newConversation={true} />

And later on rerenders it to

<Conversation newConversation={false} />

Then the child will not render as expected as React will 'update' the child conversation (re-render) but will not call the constructor again. Because you only transferred the props to the state in the constructor, the child will never change.

Rather most components should render only as a function of their properties. Your child can be adapted to the following:

export default class Conversation extends Component {
  constructor(props) {
    super(props);
  }

  render() {
   if (!this.props.newConversation) {
    return (
      <div>Current Conversation</div>
    );
   } return (
      <div>New Conversation</div>
    );
  }
}

Here, an update by the parent will allow the child to correctly re-render because you directly reference the props in the render() function of the child.

If you had more data about the conversation being rendered, then you should pass in all the data as a prop. i.e.

<Conversation conversationData={{name: 'abc', new: false}} />

Then the Conversation component can access the props directly in the render() method.

If you absolutely must change the state, then it should be in the form of a change in props by the parent:

export default class Conversation extends Component {
  constructor(props) {
    super(props);

    this.state = {
      newConversation: props.showNewConversation
    };
  }

  // Response to change
  componentWillReceiveProps(nextProps){
      this.setState({newConversation: this.props.showNewConversation});

  }

  render() {
   if (!this.state.newConversation) {
    return (
      <div>Current Conversation</div>
    );
   } return (
      <div>New Conversation</div>
    );
  }
}

Upvotes: 4

Related Questions