2015-05-21 10 views
6

Sto usando ModalTrigger di react-bootstrap per mostrare un modale field-heavy (basato su Modal react-bootstrap), che significa inviarlo un po 'di oggetti di scena:Contesto di reazione non trasferisce quando si usa un componente come prop

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

la componente principale che crea il grilletto ha i campi/valori passati in via oggetti di scena, e il componente principale di che componente ha passò come oggetti di scena così, dal componente di livello superiore che in realtà contiene i dati . Entrambi sono fondamentalmente pipe, che è un classico scenario childContext, tranne per il fatto che non funziona. Ecco una versione semplificata di quello che ho provato:

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> 
     ) 
    } 
}); 

Il modale si apre con "Il contesto è", senza mostrare il contesto reale.

Credo che stia succedendo perché l'oggetto inviato a ModalTrigger è già reso/montato in qualche modo, ma non sono sicuro del perché. Per quanto ne so, il proprietario di MyModal è il componente Contenuto, il che significa che il contesto dovrebbe essere ok, ma non è questo il caso.

Altre informazioni: Ho già provato a passare {...this.props} e context={this.context} a MyModal senza esito positivo. Inoltre, forse rilevante, ModalTrigger usa cloneElement per assicurarsi che i punti prop onRequestHide del mod alla funzione hide del trigger.

Quindi cosa mi manca qui? :/

risposta

10

React.cloneElement cambierà il proprietario dell'elemento quando il puntello ref viene sovrascritto, il che significa che il contesto non verrà passato dal proprietario precedente. Tuttavia, questo non sembra essere il caso di ModalTrigger.

Si noti che l'approccio basato sul proprietario non funzionerà del tutto in React 0.14, poiché il contesto verrà passato da padre a figlio e non da proprietario a proprietario più. ModalTrigger esegue il rendering del puntello del nodo modal in un altro ramo del DOM (vedere OverlayMixin). Pertanto, il componente Modal non è un figlio né un discendente del componente Content e non verrà passato il contesto figlio da Content.

Per risolvere il problema, è sempre possibile creare un componente il cui unico scopo è quello di passare il contesto ai propri figli.

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

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

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

usarlo:

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

Come accennato Matt Smith, si scopre che reagiscono-bootstrap comprende già un approccio molto simile al contesto inoltro via ModalTrigger.withContext. Ciò consente di creare una classe di componente ModalTrigger che inoltrerà il proprio contesto al puntello del nodo modal, indipendentemente dalla sua posizione nell'albero VDOM.

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

Poiché il contesto è una funzionalità non documentata di reazione, non abbiamo ancora documentato questa funzionalità. Per i dettagli, consulta https://github.com/react-bootstrap/react-bootstrap/pull/644. Jimmy ha fatto un ottimo lavoro per abilitare il tuo uso dei contesti. –

+0

Grazie! Non ero a conoscenza del comportamento di 'cloneElement' .. Puoi approfondire un po 'cosa intendi con" questo approccio non funzionerà "? –

+0

La mia precedente descrizione del comportamento di 'cloneElement' era sbagliata: il proprietario verrà conservato a meno che l'oggetto' ref' non venga sovrascritto, il che non sembra essere il caso di 'ModalTrigger'. Ho aggiornato la mia risposta con una correzione, oltre a una soluzione più chiara al problema che viene fornita con react-bootstrap. –

3

C'è un modo molto migliore di passare contesto ai vostri componenti di tipo "Portale" che rendono i loro figli in un contenitore diverso al di fuori l'albero reagire.

L'utilizzo di "renderSubtreeIntoContainer" anziché "render" passerà il contesto anche nella sottostruttura.

Può essere utilizzato in questo modo:

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; 
    } 
}; 

Questo è un esempio di un portale che uso nel mio production app Casalova che rendono contesto correttamente nei loro figli.

Nota: questa API non è documentata ed è probabile che cambi in futuro. Per ora, però, è il modo giusto per rendere il contesto nei componenti del portale.

+0

ehi - cosa intendi per "componenti del portale"? –

Problemi correlati