Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React context doesn't transfer when using a component as prop

I'm using react-bootstrap's ModalTrigger to show a field-heavy modal (based on react-bootstrap's Modal), which means sending it a bunch of props:

<ModalTrigger modal={<MyModal field1={value1} field2={value2} (more fields...)/>}>
    Click here to open
</ModalTrigger>

The parent component that creates the trigger has the fields/values passed in via props, and the parent component of that component has it passed as props as well, by the top-level component that actually holds the data. Both are basically pipes, which is a classic childContext scenario, except that it doesn't work. Here's a simplified version of what I've tried:

var MyModal = React.createClass({
    contextTypes : {foo : React.PropTypes.string},
    render : function() {
        return (
            <Modal {...this.props} title="MyTitle">
                <div className="modal-body">
                    The context is {this.context.foo}
                </div>
            </Modal>
        );
    }
});

var Content = React.createClass({
    childContextTypes : {foo: React.PropTypes.string},
    getChildContext : function() {return {foo : "bar"}},
    render : function() {
        return (
            <ModalTrigger modal={<MyModal/>}>
                <span>Show modal</span>
            </ModalTrigger>
        )
    }
});

The modal pops up with "The context is", without showing the actual context.

I believe this is happening because the prop sent to ModalTrigger is already rendered/mounted somehow, but I'm not sure why. To the best of my understanding, the owner of MyModal is the Content component, which means that the context should be ok, but this is not the case.

Some more information: I already tried passing {...this.props} and context={this.context} to MyModal with no success. Also, maybe relevant, ModalTrigger uses cloneElement to make sure the modal's onRequestHide prop points to the trigger's hide function.

So what am I missing here? :/

like image 869
Guy Benron Avatar asked May 21 '15 08:05

Guy Benron


1 Answers

React.cloneElement will change the owner of the element when the ref prop is overriden, which means context will not be passed from the previous owner. However, this does not seem to be the case with ModalTrigger.

Note that the owner-based approach won't work altogether in React 0.14, since context will be passed from parent to child and not from owner to ownee anymore. ModalTrigger renders its modal node prop in another branch of the DOM (See OverlayMixin). Thus, your Modal component is not a child nor a descendant of your Content component and won't be passed child context from Content.

As for solving your problem, you can always create a component whose sole purpose is to pass context to its children.

var PassContext = React.createClass({
  childContextTypes: {
    foo: React.PropTypes.string
  },

  getChildContext: function() {
    return this.props.context;
  },

  render: function() {
    return <MyModal />;
  },
});

To use it:

<ModalTrigger modal={<PassContext context={this.getChildContext()}/>}>

As Matt Smith hinted, it turns out that react-bootstrap already includes a very similar approach to forwarding context via ModalTrigger.withContext. This allows you to create a ModalTrigger component class which will forward its context to its modal node prop, no matter its position in the VDOM tree.

// MyModalTrigger.js
module.exports = ModalTrigger.withContext({
  foo: React.PropTypes.String
});
like image 154
Alexandre Kirszenberg Avatar answered Nov 15 '22 00:11

Alexandre Kirszenberg