The reaction context is not transmitted when using the component as a support

I use action-bootstrap ModalTrigger to show a heavy modulo-modal (based on the modal reaction-bootstrap), which means sending it as a props:

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

The parent component that creates the trigger has fields / values ​​passed through the details, and the parent component of this component also passed it as details to the top-level component that actually stores the data. Both are mostly pipes, which are the classic childContext script, except that it does not work. Here is a simplified version of what I 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 "Context", without displaying the actual context.

I believe this is because the alert sent to ModalTrigger has already been made / mounted in some way, but I'm not sure why. As far as I understand, the owner of MyModal is the Content component, which means that the context should be in order, but this is not so.

Additional info: I already tried passing {...this.props} and context={this.context} to MyModal without any success. Also, possibly relevant, ModalTrigger uses cloneElement to make sure that the modal onRequestHide prop points to the trigger release function.

So what am I missing here?: /

+6
source share
2 answers

React.cloneElement will change the owner of the element when the ref parameter is overridden, which means that the context will not be passed to the previous owner. However, this is not like ModalTrigger .

Note that an owner-based approach will not work in React 0.14, as the context will be passed from parent to child, not from owner to owner. ModalTrigger maps its modal node prop to another DOM branch (see OverlayMixin ). Thus, your modal component is not a descendant or descendant of your Content component and the child context from Content will not be passed.

As for solving your problem, you can always create a component whose sole purpose is to pass the 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 turned out that the bootstrap reaction already includes a very similar approach to the context of forwarding through ModalTrigger.withContext . This allows you to create a ModalTrigger component class that redirects its context to its modal node prop, regardless of its position in the VDOM tree.

 // MyModalTrigger.js module.exports = ModalTrigger.withContext({ foo: React.PropTypes.String }); 
+10
source

There is a much better way to convey context to your portal components, which turn their children into another container outside of the React tree.

Using "renderSubtreeIntoContainer" rather than "render" will also convey the context in the subtree.

It can be used like this:

 import React, {PropTypes} from 'react'; import { unstable_renderSubtreeIntoContainer as renderSubtreeIntoContainer, unmountComponentAtNode } from 'react-dom'; export default class extends React.Component { static displayName = 'ReactPortal'; static propTypes = { isRendered: PropTypes.bool, children: PropTypes.node, portalContainer: PropTypes.node }; static defaultProps = { isRendered: true }; state = { mountNode: null }; componentDidMount() { if (this.props.isRendered) { this._renderPortal(); } } componentDidUpdate(prevProps) { if (prevProps.isRendered && !this.props.isRendered || (prevProps.portalContainer !== this.props.portalContainer && prevProps.isRendered)) { this._unrenderPortal(); } if (this.props.isRendered) { this._renderPortal(); } } componentWillUnmount() { this._unrenderPortal(); } _getMountNode = () => { if (!this.state.mountNode) { const portalContainer = this.props.portalContainer || document.body; const mountNode = document.createElement('div'); portalContainer.appendChild(mountNode); this.setState({ mountNode }); return mountNode; } return this.state.mountNode; }; _renderPortal = () => { const mountNode = this._getMountNode(); renderSubtreeIntoContainer( this, ( <div> {this.props.children} </div> ), mountNode, ); }; _unrenderPortal = () => { if (this.state.mountNode) { unmountComponentAtNode(this.state.mountNode); this.state.mountNode.parentElement.removeChild(this.state.mountNode); this.setState({ mountNode: null }); } }; render() { return null; } }; 

This is an example of a portal that I use in my Casalova production application , which correctly displays context in its children.

Note. This API is undocumented and is likely to change in the future. At the moment, this is the right way to visualize the context in the components of the portal.

+3
source

Source: https://habr.com/ru/post/987645/


All Articles