Adam Grant
Adam Grant

Reputation: 13105

ReactJS: Control a child state from the child and the parent

I have a rather simple problem and I'm not sure how to solve it with React's one way data flow.

Say you have a link in the parent that shows a modal

In the modal, you have an "X" that closes it.

I know I can change the state of the modal from the parent via props

// In the parent
<Modal display={this.state.showModal} />

// In the modal
<div className={this.props.display ? "show" : "hide"}>
  <a className="close">&times;</a>
  ...
</div>

And I know how to close the modal, but not both. Not sure how to keep a state that is shared and controllable by both the parent and the child modal.

UPDATE

In trying to keep this as modular as possible, I think the React way would be to store the open/close logic in the modal variable.

var ParentThing = React.createClass({
  ...
  render (
    <Modal /> // How can I call this.open in the modal from here?
  )
});

var Modal = React.createClass({
  setInitialState: function() {
    return {
      display: false
    }
  },
  close: function() {
    this.setState({ display: false });
  },
  open: function() {
    this.setState({ display: true });
  },
  render: function() {
    return (
      <div className={this.state.display ? "show" : "hide"}>
        <a className="close" onClick={this.close}>&times;</a>
      </div>
    )
  }
});

I saw this method, but it seems to be a little more than I need to do here. Reactjs: how to modify child state or props from parent?

Upvotes: 13

Views: 7993

Answers (2)

Michelle Tilley
Michelle Tilley

Reputation: 159095

There are two ways to handle this kind of thing in React:

  1. Make the child "controlled," just like a form input with a value and onChange property, where the owner of the input controls the input.
  2. Make the child "uncontrolled," just like a form input without a value.

The second choice seems faster up front, but just like managing a collection of form inputs in React, the advantage to using fully controlled components becomes apparent as complexity builds and the need to fully describe your UI at any point and time increases. (See this excellent answer from FakeRainBrigand if you're curious exactly why controlled components is better than uncontrolled in most cases.)

However, just like form inputs, there's no reason your component can't be either controlled or uncontrolled. If the user passes a display and onClose property, like Austin Greco's answer, you have a controlled modal, and the parent fully decides when to show or hide the modal.

If the user doesn't, you can skip using the properties and instead delegate to internal state managed by public methods on the modal component:

var ParentThing = React.createClass({
  ...
  render: function() {
    return <Modal ref="modal" />;
  },

  handleSomeClick: function() {
    this.refs.modal.open();
  }
});

var Modal = React.createClass({
  setInitialState: function() {
    return {
      display: false
    }
  },
  close: function() {
    this.setState({ display: false });
  },
  open: function() {
    this.setState({ display: true });
  },
  render: function() {
    return (
      <div className={this.state.display ? "show" : "hide"}>
        <a className="close" onClick={this.close}>&times;</a>
      </div>
    )
  }
});

If you like the idea of a controlled Modal component, but don't want to do all the boilerplate typing, you could even go so far as to implement something like the valueLink property for the Modal to simplify this pattern.

var ParentThing = React.createClass({
  ...
  mixins: [React.addons.LinkedStateMixin],

  getInitialState: function() {
    return { showModal: false };
  },

  render: function() {
    return <Modal displayLink={this.linkState("showModal")} />;
  },

  handleSomeClick: function() {
    this.setState({showModal: true});
  }
});

var Modal = React.createClass({
  close: function() {
    this.props.displayLink.requestChange(false);
  },

  render: function() {
    return (
      <div className={this.props.displayLink.value? "show" : "hide"}>
        <a className="close" onClick={this.close}>&times;</a>
      </div>
    )
  }
});

(See my blog post on creating custom components that work with linkState/valueLink for more info.)

So now you get the benefit of using a fully parent-controlled Modal, but you've removed a portion of the boilerplate around creating a function that sets the value to false and passing it to the modal.

Upvotes: 16

Austin Greco
Austin Greco

Reputation: 33544

You could pass a callback as a prop to the child component:

// In the parent
<Modal display={this.state.showModal} onClose={this.closeModal} />

// In the modal
<div className={this.props.display ? "show" : "hide"}>
  <a className="close" onClick={this.props.onClose}>&times;</a>
  ...
</div>

Then when you click the close button on the child, it will call the function of the parent

Upvotes: 7

Related Questions