Comment puis-je afficher une boîte de dialogue modale dans Redux qui effectue des actions asynchrones?

240

Je crée une application qui doit afficher une boîte de dialogue de confirmation dans certaines situations.

Disons que je veux supprimer quelque chose, alors je vais envoyer une action comme deleteSomething(id)ça, donc un réducteur attrapera cet événement et remplira le réducteur de dialogue afin de le montrer.

Mon doute vient quand cette boîte de dialogue se soumet.

  • Comment ce composant peut-il envoyer l'action appropriée en fonction de la première action envoyée?
  • Le créateur d'action doit-il gérer cette logique?
  • Pouvons-nous ajouter des actions à l'intérieur du réducteur?

Éditer:

pour le rendre plus clair:

deleteThingA(id) => show dialog with Questions => deleteThingARemotely(id)

createThingB(id) => Show dialog with Questions => createThingBRemotely(id)

J'essaie donc de réutiliser le composant de dialogue. Afficher / masquer la boîte de dialogue n'est pas le problème car cela peut être facilement fait dans le réducteur. Ce que j'essaie de spécifier, c'est comment répartir l'action du côté droit en fonction de l'action qui démarre le flux du côté gauche.

carlesba
la source
1
Je pense que dans votre cas, l'état du dialogue (masquer / afficher) est local. Je choisirais d'utiliser l'état de réaction pour gérer l'affichage / le masquage des dialogues. De cette façon, la question de "l'action appropriée selon la première action" disparaîtra.
Ming

Réponses:

516

L'approche que je suggère est un peu verbeuse, mais je l'ai trouvée assez bien adaptée aux applications complexes. Lorsque vous voulez montrer un modal, le feu d' une action décrivant ce qui vous modal souhaitez voir:

Envoi d'une action pour afficher le modal

this.props.dispatch({
  type: 'SHOW_MODAL',
  modalType: 'DELETE_POST',
  modalProps: {
    postId: 42
  }
})

(Les chaînes peuvent être des constantes bien sûr; j'utilise des chaînes en ligne pour plus de simplicité.)

Écrire un réducteur pour gérer l'état modal

Assurez-vous ensuite que vous disposez d'un réducteur qui accepte simplement ces valeurs:

const initialState = {
  modalType: null,
  modalProps: {}
}

function modal(state = initialState, action) {
  switch (action.type) {
    case 'SHOW_MODAL':
      return {
        modalType: action.modalType,
        modalProps: action.modalProps
      }
    case 'HIDE_MODAL':
      return initialState
    default:
      return state
  }
}

/* .... */

const rootReducer = combineReducers({
  modal,
  /* other reducers */
})

Génial! Désormais, lorsque vous envoyez une action, state.modalsera mise à jour pour inclure les informations sur la fenêtre modale actuellement visible.

Écriture du composant racine modal

À la racine de la hiérarchie de vos composants, ajoutez un <ModalRoot>composant connecté au magasin Redux. Il écoutera state.modalet affichera un composant modal approprié, transmettant les accessoires à partir du state.modal.modalProps.

// These are regular React components we will write soon
import DeletePostModal from './DeletePostModal'
import ConfirmLogoutModal from './ConfirmLogoutModal'

const MODAL_COMPONENTS = {
  'DELETE_POST': DeletePostModal,
  'CONFIRM_LOGOUT': ConfirmLogoutModal,
  /* other modals */
}

const ModalRoot = ({ modalType, modalProps }) => {
  if (!modalType) {
    return <span /> // after React v15 you can return null here
  }

  const SpecificModal = MODAL_COMPONENTS[modalType]
  return <SpecificModal {...modalProps} />
}

export default connect(
  state => state.modal
)(ModalRoot)

Qu'avons-nous fait ici? ModalRootlit le courant modalTypeet à modalPropspartir state.modalde laquelle il est relié, et rend un tel composant comme correspondant DeletePostModalou ConfirmLogoutModal. Chaque modal est un composant!

Écriture de composants modaux spécifiques

Il n'y a pas de règles générales ici. Ce ne sont que des composants React qui peuvent envoyer des actions, lire quelque chose à partir de l'état du magasin et être des modaux .

Par exemple, DeletePostModalpourrait ressembler à:

import { deletePost, hideModal } from '../actions'

const DeletePostModal = ({ post, dispatch }) => (
  <div>
    <p>Delete post {post.name}?</p>
    <button onClick={() => {
      dispatch(deletePost(post.id)).then(() => {
        dispatch(hideModal())
      })
    }}>
      Yes
    </button>
    <button onClick={() => dispatch(hideModal())}>
      Nope
    </button>
  </div>
)

export default connect(
  (state, ownProps) => ({
    post: state.postsById[ownProps.postId]
  })
)(DeletePostModal)

Le DeletePostModalest connecté au magasin afin qu'il puisse afficher le titre du message et fonctionne comme n'importe quel composant connecté: il peut envoyer des actions, y compris hideModallorsqu'il est nécessaire de se cacher.

Extraire un composant de présentation

Il serait gênant de copier-coller la même logique de disposition pour chaque modal «spécifique». Mais vous avez des composants, non? Vous pouvez donc extraire un composant de présentation <Modal> qui ne sait pas ce que font les modaux particuliers, mais gère leur apparence.

Ensuite, des modaux spécifiques tels que DeletePostModalpeuvent l'utiliser pour le rendu:

import { deletePost, hideModal } from '../actions'
import Modal from './Modal'

const DeletePostModal = ({ post, dispatch }) => (
  <Modal
    dangerText={`Delete post ${post.name}?`}
    onDangerClick={() =>
      dispatch(deletePost(post.id)).then(() => {
        dispatch(hideModal())
      })
    })
  />
)

export default connect(
  (state, ownProps) => ({
    post: state.postsById[ownProps.postId]
  })
)(DeletePostModal)

C'est à vous de trouver un ensemble d'accessoires qui <Modal>peuvent accepter dans votre application, mais j'imagine que vous pourriez avoir plusieurs types de modaux (par exemple, modal info, modal de confirmation, etc.), et plusieurs styles pour eux.

Accessibilité et masquage du clic à l'extérieur ou de la touche d'échappement

La dernière partie importante concernant les modaux est que, généralement, nous voulons les masquer lorsque l'utilisateur clique à l'extérieur ou appuie sur Échap.

Au lieu de vous donner des conseils sur la mise en œuvre de cela, je vous suggère de ne pas le mettre en œuvre vous-même. Il est difficile de bien faire compte tenu de l'accessibilité.

Au lieu de cela, je vous suggère d'utiliser un composant modal disponible sur le marché tel que react-modal. Il est entièrement personnalisable, vous pouvez y mettre tout ce que vous voulez, mais il gère correctement l'accessibilité afin que les aveugles puissent toujours utiliser votre modal.

Vous pouvez même envelopper react-modalle vôtre <Modal>qui accepte les accessoires spécifiques à vos applications et génère des boutons enfants ou d'autres contenus. Ce ne sont que des composants!

Autres approches

Il y a plus d'une façon de le faire.

Certaines personnes n'aiment pas la verbosité de cette approche et préfèrent avoir un <Modal>composant qu'elles peuvent rendre directement à l'intérieur de leurs composants avec une technique appelée «portails». Les portails vous permettent de restituer un composant à l'intérieur du vôtre alors qu'en fait il sera rendu à un endroit prédéterminé dans le DOM, ce qui est très pratique pour les modaux.

En fait, react-modalje l'ai déjà fait précédemment en interne, donc techniquement, vous n'avez même pas besoin de le rendre par le haut. Je trouve toujours agréable de découpler le modal que je veux montrer du composant qui le montre, mais vous pouvez également l'utiliser react-modaldirectement à partir de vos composants et ignorer la plupart de ce que j'ai écrit ci-dessus.

Je vous encourage à considérer les deux approches, à les expérimenter et à choisir ce qui vous convient le mieux pour votre application et pour votre équipe.

Dan Abramov
la source
35
Une chose que je suggérerais serait que le réducteur maintienne une liste de modaux qui peuvent être poussés et sautés. Aussi stupide que cela puisse paraître, j'ai toujours rencontré des situations où les concepteurs / types de produits veulent que j'ouvre un modal à partir d'un modal, et il est agréable de permettre aux utilisateurs de "revenir en arrière".
Kyle
9
Oui, certainement, c'est le genre de chose que Redux rend facile à construire parce que vous pouvez simplement changer votre état pour être un tableau. Personnellement, j'ai travaillé avec des designers qui, au contraire, voulaient que les modaux soient exclusifs, donc l'approche que j'ai écrite résout l'imbrication accidentelle. Mais oui, vous pouvez l'avoir dans les deux sens.
Dan Abramov
4
D'après mon expérience, je dirais: si le modal est lié à un composant local (comme un modal de confirmation de suppression est lié au bouton Supprimer), il est plus simple d'utiliser un portail, sinon utilisez des actions redux. D'accord avec @Kyle, on devrait pouvoir ouvrir un modal à partir d'un modal. Il fonctionne également par défaut avec les portails car ils sont ajoutés afin de documenter le corps afin que les portails se superposent bien (jusqu'à ce que vous gâchiez tout avec z-index: p)
Sebastien Lorber
4
@DanAbramov, votre solution est excellente, mais j'ai un problème mineur. Rien de sérieux. J'utilise Material-ui dans le projet, lors de la fermeture du modal, il suffit de le désactiver, au lieu de "jouer" une animation de fondu. Vous avez probablement besoin de faire une sorte de retard? Ou conserver chaque modal comme une liste à l'intérieur de ModalRoot? Suggestions?
gcerar
7
Parfois, je veux appeler certaines fonctions après la fermeture du modal (par exemple, appeler les fonctions avec les valeurs du champ de saisie à l'intérieur du modal). Je passerais ces fonctions quant modalPropsà l'action. Cela viole cependant la règle de maintien de l'état sérialisable. Comment puis-je surmonter ce problème?
chmanie
98

Mise à jour : React 16.0 a introduit des portails via un ReactDOM.createPortal lien

Mise à jour : les prochaines versions de React (Fibre: probablement 16 ou 17) incluront une méthode pour créer des portails: ReactDOM.unstable_createPortal() lien


Utiliser des portails

Dan Abramov répond que la première partie est très bien, mais implique beaucoup de passe-partout. Comme il l'a dit, vous pouvez également utiliser des portails. Je vais développer un peu cette idée.

L'avantage d'un portail est que la fenêtre contextuelle et le bouton restent très proches de l'arborescence React, avec une communication parent / enfant très simple à l'aide d'accessoires: vous pouvez facilement gérer des actions asynchrones avec des portails, ou laisser le parent personnaliser le portail.

Qu'est-ce qu'un portail?

Un portail vous permet d'effectuer document.bodyun rendu directement à l'intérieur d' un élément profondément imbriqué dans votre arbre React.

L'idée est que, par exemple, vous restituez dans le corps l'arborescence React suivante:

<div className="layout">
  <div className="outside-portal">
    <Portal>
      <div className="inside-portal">
        PortalContent
      </div>
    </Portal>
  </div>
</div>

Et vous obtenez en sortie:

<body>
  <div class="layout">
    <div class="outside-portal">
    </div>
  </div>
  <div class="inside-portal">
    PortalContent
  </div>
</body>

Le inside-portalnœud a été traduit à l'intérieur <body>, au lieu de son emplacement normal et profondément imbriqué.

Quand utiliser un portail

Un portail est particulièrement utile pour afficher les éléments qui devraient aller au-dessus de vos composants React existants: popups, listes déroulantes, suggestions, hotspots

Pourquoi utiliser un portail

Plus de problème de z-index : un portail vous permet de faire un rendu <body>. Si vous souhaitez afficher une popup ou une liste déroulante, c'est une très bonne idée si vous ne voulez pas avoir à vous battre contre des problèmes d'index z. Les éléments du portail sont ajoutés document.bodydans l'ordre de montage, ce qui signifie qu'à moins de jouer avec z-index, le comportement par défaut sera d'empiler les portails les uns sur les autres, dans l'ordre de montage. En pratique, cela signifie que vous pouvez ouvrir une fenêtre contextuelle en toute sécurité à l'intérieur d'une autre fenêtre contextuelle et être sûr que la deuxième fenêtre contextuelle sera affichée en haut de la première, sans même avoir à y penser z-index.

En pratique

Le plus simple: utilisez l'état React local: si vous pensez que pour une simple fenêtre de confirmation de suppression, cela ne vaut pas la peine d'avoir le passe-partout Redux, alors vous pouvez utiliser un portail et cela simplifie considérablement votre code. Pour un tel cas d'utilisation, où l'interaction est très locale et est en fait un détail d'implémentation, vous souciez-vous vraiment du rechargement à chaud, du voyage dans le temps, de la journalisation des actions et de tous les avantages que Redux vous apporte? Personnellement, je n'utilise pas l'état local dans ce cas. Le code devient aussi simple que:

class DeleteButton extends React.Component {
  static propTypes = {
    onDelete: PropTypes.func.isRequired,
  };

  state = { confirmationPopup: false };

  open = () => {
    this.setState({ confirmationPopup: true });
  };

  close = () => {
    this.setState({ confirmationPopup: false });
  };

  render() {
    return (
      <div className="delete-button">
        <div onClick={() => this.open()}>Delete</div>
        {this.state.confirmationPopup && (
          <Portal>
            <DeleteConfirmationPopup
              onCancel={() => this.close()}
              onConfirm={() => {
                this.close();
                this.props.onDelete();
              }}
            />
          </Portal>
        )}
      </div>
    );
  }
}

Simple: vous pouvez toujours utiliser l'état Redux : si vous le souhaitez vraiment, vous pouvez toujours utiliser connectpour choisir d' DeleteConfirmationPopupafficher ou non le. Comme le portail reste profondément imbriqué dans votre arborescence React, il est très simple de personnaliser le comportement de ce portail car votre parent peut transmettre des accessoires au portail. Si vous n'utilisez pas de portails, vous devez généralement afficher vos fenêtres contextuelles en haut de votre arborescence React pourz-indexraisons, et doivent généralement penser à des choses comme "comment personnaliser le DeleteConfirmationPopup générique que j'ai construit en fonction du cas d'utilisation". Et généralement, vous trouverez des solutions assez hacky à ce problème, comme l'envoi d'une action qui contient des actions de confirmation / annulation imbriquées, une clé de bundle de traduction, ou pire encore, une fonction de rendu (ou autre chose non sérialisable). Vous n'êtes pas obligé de le faire avec des portails, et vous pouvez simplement passer des accessoires réguliers, car ce DeleteConfirmationPopupn'est qu'un enfant duDeleteButton

Conclusion

Les portails sont très utiles pour simplifier votre code. Je ne pouvais plus m'en passer.

Notez que les implémentations de portail peuvent également vous aider avec d'autres fonctionnalités utiles telles que:

  • Accessibilité
  • Raccourcis d'espace pour fermer le portail
  • Gérer le clic extérieur (fermer le portail ou non)
  • Gérer le clic du lien (fermer le portail ou non)
  • React Context mis à disposition dans l'arborescence du portail

react-portal ou react-modal conviennent aux fenêtres contextuelles, aux modaux et aux superpositions qui doivent être en plein écran, généralement centrées au milieu de l'écran.

react-tether est inconnu de la plupart des développeurs React, mais c'est l'un des outils les plus utiles que vous puissiez trouver. Tether vous permet de créer des portails, mais positionnera automatiquement le portail par rapport à une cible donnée. C'est parfait pour les info-bulles, les listes déroulantes, les points chauds, les boîtes d'aide ... Si vous avez déjà eu un problème avec la position absolute/ relativeet z-index, ou votre liste déroulante en dehors de votre fenêtre, Tether résoudra tout cela pour vous.

Vous pouvez, par exemple, implémenter facilement des hotspots d'intégration, qui se transforment en info-bulle une fois cliqué:

Hotspot d'intégration

Un vrai code de production ici. Rien de plus simple :)

<MenuHotspots.contacts>
  <ContactButton/>
</MenuHotspots.contacts>

Edit : vient de découvrir la passerelle de réaction qui permet de rendre les portails dans le nœud de votre choix (pas forcément le corps)

Edit : il semble que react-popper puisse être une alternative décente à react-tether. PopperJS est une bibliothèque qui calcule uniquement une position appropriée pour un élément, sans toucher directement au DOM, permettant à l'utilisateur de choisir où et quand il veut placer le nœud DOM, tandis que Tether s'ajoute directement au corps.

Edit : il y a aussi react-slot-fill qui est intéressant et peut aider à résoudre des problèmes similaires en permettant de rendre un élément dans un emplacement d'élément réservé que vous placez où vous le souhaitez dans votre arborescence

Sébastien Lorber
la source
Dans votre exemple d'extrait, la fenêtre de confirmation ne se fermera pas si vous confirmez l'action (contrairement à lorsque vous cliquez sur Annuler)
dKab
Il serait utile d'inclure votre importation Portal dans l'extrait de code. De quelle bibliothèque <Portal>vient-il? Je suppose que c'est react-portal, mais ce serait bien de savoir avec certitude.
pierre
1
@skypecakes veuillez considérer mes implémentations comme un pseudo-code. Je ne l'ai testé contre aucune bibliothèque concrète. J'essaie juste d'enseigner ici le concept et non une mise en œuvre concrète. J'ai l'habitude de react-portal et le code ci-dessus devrait fonctionner correctement avec, mais cela devrait fonctionner correctement avec presque toutes les bibliothèques similaires.
Sebastien Lorber
react-gateway est génial! Il prend en charge le rendu côté serveur :)
cyrilluce
Je suis assez débutant, je serai donc très heureux d'avoir des explications sur cette approche. Même si vous effectuez vraiment le rendu du modal à un autre endroit, dans cette approche, vous devrez vérifier sur chaque bouton de suppression si vous devez rendre l'instance spécifique du modal. Dans l'approche redux, je n'ai qu'une seule instance du modal qui est montrée ou non. N'est-ce pas un souci de performance?
Amit Neuhaus
9

Beaucoup de bonnes solutions et de précieux commentaires par des experts connus de la communauté JS sur le sujet peuvent être trouvés ici. Cela pourrait être un indicateur que ce n'est pas un problème aussi banal que cela puisse paraître. Je pense que c'est pourquoi cela pourrait être la source de doutes et d'incertitudes sur la question.

Le problème fondamental ici est que dans React, vous n'êtes autorisé à monter le composant que sur son parent, ce qui n'est pas toujours le comportement souhaité. Mais comment résoudre ce problème?

Je propose la solution, adressée pour résoudre ce problème. Une définition plus détaillée du problème, src et des exemples peuvent être trouvés ici: https://github.com/fckt/react-layer-stack#rationale

Raisonnement

react/ react-domvient avec 2 hypothèses / idées de base:

  • chaque interface utilisateur est naturellement hiérarchique. C'est pourquoi nous avons l'idée de nous componentsenvelopper
  • react-dom monte le composant enfant (physiquement) sur son nœud DOM parent par défaut

Le problème est que parfois la deuxième propriété n'est pas ce que vous voulez dans votre cas. Parfois, vous souhaitez monter votre composant dans un nœud DOM physique différent et maintenir en même temps une connexion logique entre le parent et l'enfant.

Un exemple canonique est un composant de type info-bulle: à un moment donné du processus de développement, vous pourriez constater que vous devez ajouter une description pour votre UI element: il s'affichera en couche fixe et devrait connaître ses coordonnées (qui sont les coordonnées ou les coordonnées de la UI elementsouris) et à en même temps, il a besoin d'informations pour savoir s'il doit être affiché maintenant ou non, son contenu et un certain contexte des composants parents. Cet exemple montre que parfois la hiérarchie logique ne correspond pas à la hiérarchie DOM physique.

Jetez un œil à https://github.com/fckt/react-layer-stack/blob/master/README.md#real-world-usage-example pour voir l'exemple concret qui répond à votre question:

import { Layer, LayerContext } from 'react-layer-stack'
// ... for each `object` in array of `objects`
  const modalId = 'DeleteObjectConfirmation' + objects[rowIndex].id
  return (
    <Cell {...props}>
        // the layer definition. The content will show up in the LayerStackMountPoint when `show(modalId)` be fired in LayerContext
        <Layer use={[objects[rowIndex], rowIndex]} id={modalId}> {({
            hideMe, // alias for `hide(modalId)`
            index } // useful to know to set zIndex, for example
            , e) => // access to the arguments (click event data in this example)
          <Modal onClick={ hideMe } zIndex={(index + 1) * 1000}>
            <ConfirmationDialog
              title={ 'Delete' }
              message={ "You're about to delete to " + '"' + objects[rowIndex].name + '"' }
              confirmButton={ <Button type="primary">DELETE</Button> }
              onConfirm={ this.handleDeleteObject.bind(this, objects[rowIndex].name, hideMe) } // hide after confirmation
              close={ hideMe } />
          </Modal> }
        </Layer>

        // this is the toggle for Layer with `id === modalId` can be defined everywhere in the components tree
        <LayerContext id={ modalId }> {({showMe}) => // showMe is alias for `show(modalId)`
          <div style={styles.iconOverlay} onClick={ (e) => showMe(e) }> // additional arguments can be passed (like event)
            <Icon type="trash" />
          </div> }
        </LayerContext>
    </Cell>)
// ...
fckt
la source
2

À mon avis, la mise en œuvre minimale minimale a deux exigences. Un état qui garde la trace de l'ouverture ou non du modal, et un portail pour rendre le modal en dehors de l'arborescence de réaction standard.

Le composant ModalContainer ci-dessous implémente ces exigences ainsi que les fonctions de rendu correspondantes pour le modal et le déclencheur, qui est responsable de l'exécution du rappel pour ouvrir le modal.

import React from 'react';
import PropTypes from 'prop-types';
import Portal from 'react-portal';

class ModalContainer extends React.Component {
  state = {
    isOpen: false,
  };

  openModal = () => {
    this.setState(() => ({ isOpen: true }));
  }

  closeModal = () => {
    this.setState(() => ({ isOpen: false }));
  }

  renderModal() {
    return (
      this.props.renderModal({
        isOpen: this.state.isOpen,
        closeModal: this.closeModal,
      })
    );
  }

  renderTrigger() {
     return (
       this.props.renderTrigger({
         openModal: this.openModal
       })
     )
  }

  render() {
    return (
      <React.Fragment>
        <Portal>
          {this.renderModal()}
        </Portal>
        {this.renderTrigger()}
      </React.Fragment>
    );
  }
}

ModalContainer.propTypes = {
  renderModal: PropTypes.func.isRequired,
  renderTrigger: PropTypes.func.isRequired,
};

export default ModalContainer;

Et voici un cas d'utilisation simple ...

import React from 'react';
import Modal from 'react-modal';
import Fade from 'components/Animations/Fade';
import ModalContainer from 'components/ModalContainer';

const SimpleModal = ({ isOpen, closeModal }) => (
  <Fade visible={isOpen}> // example use case with animation components
    <Modal>
      <Button onClick={closeModal}>
        close modal
      </Button>
    </Modal>
  </Fade>
);

const SimpleModalButton = ({ openModal }) => (
  <button onClick={openModal}>
    open modal
  </button>
);

const SimpleButtonWithModal = () => (
   <ModalContainer
     renderModal={props => <SimpleModal {...props} />}
     renderTrigger={props => <SimpleModalButton {...props} />}
   />
);

export default SimpleButtonWithModal;

J'utilise des fonctions de rendu, car je souhaite isoler la gestion de l'état et la logique standard de l'implémentation du composant modal et déclencheur rendu. Cela permet aux composants rendus d'être ce que vous voulez qu'ils soient. Dans votre cas, je suppose que le composant modal pourrait être un composant connecté qui reçoit une fonction de rappel qui envoie une action asynchrone.

Si vous devez envoyer des accessoires dynamiques au composant modal à partir du composant déclencheur, ce qui, espérons-le, ne se produit pas trop souvent, je recommande d'envelopper le ModalContainer avec un composant conteneur qui gère les accessoires dynamiques dans son propre état et améliore les méthodes de rendu d'origine comme alors.

import React from 'react'
import partialRight from 'lodash/partialRight';
import ModalContainer from 'components/ModalContainer';

class ErrorModalContainer extends React.Component {
  state = { message: '' }

  onError = (message, callback) => {
    this.setState(
      () => ({ message }),
      () => callback && callback()
    );
  }

  renderModal = (props) => (
    this.props.renderModal({
       ...props,
       message: this.state.message,
    })
  )

  renderTrigger = (props) => (
    this.props.renderTrigger({
      openModal: partialRight(this.onError, props.openModal)
    })
  )

  render() {
    return (
      <ModalContainer
        renderModal={this.renderModal}
        renderTrigger={this.renderTrigger}
      />
    )
  }
}

ErrorModalContainer.propTypes = (
  ModalContainer.propTypes
);

export default ErrorModalContainer;
kskkido
la source
0

Enveloppez le modal dans un conteneur connecté et effectuez l'opération asynchrone ici. De cette façon, vous pouvez atteindre à la fois l'envoi pour déclencher des actions et l'accessoire onClose. Pour accéder dispatchaux accessoires, ne passez pas lamapDispatchToProps fonction à connect.

class ModalContainer extends React.Component {
  handleDelete = () => {
    const { dispatch, onClose } = this.props;
    dispatch({type: 'DELETE_POST'});

    someAsyncOperation().then(() => {
      dispatch({type: 'DELETE_POST_SUCCESS'});
      onClose();
    })
  }

  render() {
    const { onClose } = this.props;
    return <Modal onClose={onClose} onSubmit={this.handleDelete} />
  }
}

export default connect(/* no map dispatch to props here! */)(ModalContainer);

L'application où le modal est rendu et son état de visibilité est défini:

class App extends React.Component {
  state = {
    isModalOpen: false
  }

  handleModalClose = () => this.setState({ isModalOpen: false });

  ...

  render(){
    return (
      ...
      <ModalContainer onClose={this.handleModalClose} />  
      ...
    )
  }

}
gazdagergo
la source