Wie kann ich in Redux einen modalen Dialog anzeigen, der asynchrone Aktionen ausführt?

240

Ich erstelle eine App, die in bestimmten Situationen einen Bestätigungsdialog anzeigen muss.

Angenommen, ich möchte etwas entfernen, dann werde ich eine Aktion wie diese deleteSomething(id)auslösen, damit ein Reduzierer dieses Ereignis abfängt und den Dialogreduzierer ausfüllt, um es anzuzeigen.

Mein Zweifel kommt, wenn dieser Dialog eingereicht wird.

  • Wie kann diese Komponente die richtige Aktion gemäß der ersten ausgelösten Aktion auslösen?
  • Sollte der Aktionsersteller mit dieser Logik umgehen?
  • Können wir Aktionen innerhalb des Reduzierers hinzufügen?

bearbeiten:

um es klarer zu machen:

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

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

Ich versuche also, die Dialogkomponente wiederzuverwenden. Das Ein- und Ausblenden des Dialogfelds ist nicht das Problem, da dies im Reduzierer leicht möglich ist. Ich versuche anzugeben, wie die Aktion von der rechten Seite entsprechend der Aktion versendet wird, die den Fluss auf der linken Seite startet.

carlesba
quelle
1
Ich denke in Ihrem Fall ist der Status des Dialogs (Verstecken / Anzeigen) lokal. Ich würde den Reaktionsstatus verwenden, um das Ein- und Ausblenden von Dialogen zu verwalten. Auf diese Weise wird die Frage der "richtigen Handlung gemäß der ersten Handlung" weg sein.
Ming

Antworten:

516

Der Ansatz, den ich vorschlage, ist etwas ausführlich, aber ich fand, dass er sich ziemlich gut in komplexe Apps skalieren lässt. Wenn Sie ein Modal anzeigen möchten, lösen Sie eine Aktion aus, die beschreibt, welches Modal Sie sehen möchten :

Auslösen einer Aktion zum Anzeigen des Modals

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

(Strings können natürlich Konstanten sein; ich verwende der Einfachheit halber Inline-Strings.)

Schreiben eines Reduzierers zum Verwalten des Modalstatus

Stellen Sie dann sicher, dass Sie einen Reduzierer haben, der nur diese Werte akzeptiert:

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 */
})

Toll! Wenn Sie jetzt eine Aktion auslösen, state.modalwird diese aktualisiert und enthält nun Informationen zum aktuell sichtbaren modalen Fenster.

Schreiben der Root-Modalkomponente

Fügen Sie im Stammverzeichnis Ihrer Komponentenhierarchie eine <ModalRoot>Komponente hinzu, die mit dem Redux-Speicher verbunden ist. Es wird zuhörenstate.modal eine geeignete modale Komponente und anzeigen und die Requisiten von der weiterleiten 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)

Was haben wir hier gemacht? ModalRootliest den aktuellen modalTypeundmodalProps von state.modaldem aus er verbunden ist, und rendert eine entsprechende Komponente wie DeletePostModaloder ConfirmLogoutModal. Jedes Modal ist eine Komponente!

Schreiben spezifischer modaler Komponenten

Hier gibt es keine allgemeinen Regeln. Sie sind nur React-Komponenten, die Aktionen auslösen, etwas aus dem Speicherstatus lesen können, auslösen und zufällig Modalitäten sein können .

Zum Beispiel DeletePostModalkönnte es so aussehen:

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)

Das DeletePostModal ist mit dem Geschäft verbunden, sodass es den Post-Titel anzeigen kann und wie jede verbundene Komponente funktioniert: Es kann Aktionen auslösen, auch hideModalwenn es notwendig ist, sich selbst zu verstecken.

Extrahieren einer Präsentationskomponente

Es wäre umständlich, für jedes „spezifische“ Modal dieselbe Layoutlogik zu kopieren und einzufügen. Aber Sie haben Komponenten, richtig? So können Sie eine Präsentation extrahieren <Modal> , die nicht weiß, was bestimmte Modalitäten tun, aber wie sie aussehen.

Dann können bestimmte Modalitäten, wie DeletePostModalsie zum Rendern verwendet werden können:

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)

Es liegt an Ihnen, eine Reihe von Requisiten zu entwickeln, <Modal>die in Ihrer Anwendung akzeptiert werden können, aber ich würde mir vorstellen, dass Sie verschiedene Arten von Modalitäten (z. B. Info-Modal, Bestätigungsmodal usw.) und verschiedene Stile für diese haben.

Zugänglichkeit und Ausblenden bei Klicken außerhalb oder Escape-Taste

Der letzte wichtige Teil bei Modals ist, dass wir sie im Allgemeinen ausblenden möchten, wenn der Benutzer nach draußen klickt oder Escape drückt.

Anstatt Ihnen Ratschläge zur Implementierung zu geben, schlage ich vor, dass Sie es einfach nicht selbst implementieren. In Anbetracht der Zugänglichkeit ist es schwierig, das Richtige zu finden.

Stattdessen würde ich Ihnen empfehlen, eine zugängliche modale Standardkomponente wie z react-modal. Es ist vollständig anpassbar, Sie können alles hineinstecken, was Sie wollen, aber es handhabt die Zugänglichkeit korrekt, so dass Blinde Ihr Modal weiterhin verwenden können.

Sie können sogar react-modalIhre eigenen <Modal>einbinden, die für Ihre Anwendungen spezifische Requisiten akzeptieren und untergeordnete Schaltflächen oder andere Inhalte generieren. Es sind alles nur Komponenten!

Andere Ansätze

Es gibt mehr als einen Weg, dies zu tun.

Einige Leute mögen die Ausführlichkeit dieses Ansatzes nicht und bevorzugen eine <Modal>Komponente, die sie mit einer Technik namens „Portale“ direkt in ihren Komponenten rendern können . Mit Portalen können Sie eine Komponente in Ihrem rendern, während sie tatsächlich an einer vorbestimmten Stelle im DOM gerendert wird, was für Modale sehr praktisch ist.

Tatsächlich habe react-modalich das schon früher intern gemacht, so dass Sie es technisch nicht einmal von oben rendern müssen. Ich finde es immer noch schön, das Modal, das ich zeigen möchte, von der Komponente zu entkoppeln, die es zeigt, aber Sie können es auch react-modaldirekt von Ihren Komponenten verwenden und das meiste, was ich oben geschrieben habe, überspringen.

Ich ermutige Sie, beide Ansätze zu berücksichtigen, mit ihnen zu experimentieren und auszuwählen, was für Ihre App und Ihr Team am besten geeignet ist.

Dan Abramov
quelle
35
Eine Sache, die ich vorschlagen würde, ist, dass der Reduzierer eine Liste von Modalitäten führt, die gedrückt und geknallt werden können. So albern es auch klingt, ich bin immer wieder auf Situationen gestoßen, in denen Designer / Produkttypen möchten, dass ich ein Modal von einem Modal öffne, und es ist schön, Benutzern zu erlauben, "zurück" zu gehen.
Kyle
9
Ja, definitiv, das ist die Art von Dingen, die Redux einfach zu erstellen macht, weil Sie einfach Ihren Status ändern können, um ein Array zu sein. Persönlich habe ich mit Designern zusammengearbeitet, die im Gegenteil wollten, dass Modalitäten exklusiv sind. Der Ansatz, den ich geschrieben habe, löst also versehentliches Verschachteln. Aber ja, Sie können es in beide Richtungen haben.
Dan Abramov
4
Nach meiner Erfahrung würde ich sagen: Wenn sich Modal auf eine lokale Komponente bezieht (wie ein Löschbestätigungsmodal auf die Schaltfläche Löschen), ist es einfacher, ein Portal zu verwenden, andernfalls Redux-Aktionen. Stimmen Sie mit @Kyle überein, sollte man in der Lage sein, ein Modal von einem Modal zu öffnen. Es funktioniert auch standardmäßig mit Portalen, da sie hinzugefügt werden, um den Körper zu dokumentieren, sodass Portale gut aufeinander gestapelt sind (bis Sie alles mit dem Z-Index durcheinander bringen: p)
Sebastien Lorber
4
@ DanAbramov, deine Lösung ist großartig, aber ich habe ein kleines Problem. Nichts Ernstes. Ich verwende Material-ui im ​​Projekt. Wenn ich Modal schließe, schalte es einfach aus, anstatt verblassende Animationen "abzuspielen". Müssen Sie wahrscheinlich eine Verzögerung machen? Oder behalten Sie jedes Modal als Liste in ModalRoot? Vorschläge?
Gcerar
7
Manchmal möchte ich bestimmte Funktionen nach dem Schließen des Modals aufrufen (z. B. die Funktionen mit den Eingabefeldwerten innerhalb des Modals aufrufen). Ich würde diese Funktionen in Bezug modalPropsauf die Aktion übergeben. Dies verstößt jedoch gegen die Regel, den Status serialisierbar zu halten. Wie kann ich dieses Problem lösen?
Chmanie
98

Update : Reagiere 16.0 eingeführte Portale über ReactDOM.createPortal Link

Update : Die nächsten Versionen von React (Fibre: wahrscheinlich 16 oder 17) enthalten eine Methode zum Erstellen von Portalen: ReactDOM.unstable_createPortal() Link


Verwenden Sie Portale

Dan Abramov Antwort erster Teil ist in Ordnung, beinhaltet aber viel Boilerplate. Wie gesagt, Sie können auch Portale verwenden. Ich werde diese Idee etwas näher erläutern.

Der Vorteil eines Portals besteht darin, dass das Popup und die Schaltfläche mit der sehr einfachen Eltern-Kind-Kommunikation mithilfe von Requisiten sehr nahe am Reaktionsbaum bleiben: Sie können asynchrone Aktionen mit Portalen problemlos ausführen oder das Portal vom Elternteil anpassen lassen.

Was ist ein Portal?

Über ein Portal können Sie direkt in document.bodyeinem Element rendern , das tief in Ihrem Reaktionsbaum verschachtelt ist.

Die Idee ist, dass Sie beispielsweise den folgenden Reaktionsbaum in body rendern:

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

Und Sie erhalten als Ausgabe:

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

Der inside-portalKnoten wurde <body>anstelle seiner normalen, tief verschachtelten Position im Inneren übersetzt .

Wann man ein Portal benutzt

Ein Portal ist besonders hilfreich, um Elemente anzuzeigen, die über Ihre vorhandenen React-Komponenten hinausgehen sollen: Popups, Dropdowns, Vorschläge, Hotspots

Warum ein Portal benutzen?

Keine Z-Index-Probleme mehr : Über ein Portal können Sie rendern <body>. Wenn Sie ein Popup oder Dropdown anzeigen möchten, ist dies eine wirklich gute Idee, wenn Sie nicht gegen Z-Index-Probleme kämpfen müssen. Die hinzugefügten Portalelemente werden document.bodyin der Mount-Reihenfolge hinzugefügt. Wenn Sie also nicht damit spielen z-index, werden standardmäßig Portale in der Mount-Reihenfolge übereinander gestapelt. In der Praxis bedeutet dies, dass Sie ein Popup sicher aus einem anderen Popup heraus öffnen und sicherstellen können, dass das 2. Popup über dem ersten angezeigt wird, ohne darüber nachdenken zu müssen z-index.

In der Praxis

Am einfachsten: Verwenden Sie den lokalen Reaktionsstatus: Wenn Sie der Meinung sind, dass es für ein einfaches Popup zur Löschbestätigung nicht sinnvoll ist, das Redux-Boilerplate zu haben, können Sie ein Portal verwenden, das Ihren Code erheblich vereinfacht. Interessieren Sie sich in einem solchen Anwendungsfall, in dem die Interaktion sehr lokal ist und tatsächlich ein ziemliches Implementierungsdetail darstellt, wirklich für Hot-Reloading, Zeitreisen, Aktionsprotokollierung und alle Vorteile, die Redux Ihnen bietet? Persönlich tue ich das nicht und benutze in diesem Fall den lokalen Staat. Der Code wird so einfach wie:

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

Einfach: Sie können weiterhin den Redux-Status verwenden : Wenn Sie dies wirklich möchten, können Sie weiterhin connectauswählen, ob das DeleteConfirmationPopupangezeigt wird oder nicht. Da das Portal tief in Ihrem Reaktionsbaum verschachtelt bleibt, ist es sehr einfach, das Verhalten dieses Portals anzupassen, da Ihre Eltern Requisiten an das Portal übergeben können. Wenn Sie keine Portale verwenden, müssen Sie Ihre Popups normalerweise oben in Ihrem Reaktionsbaum für rendernz-indexGründe und müssen in der Regel über Dinge wie "Wie passe ich das generische DeleteConfirmationPopup, das ich entsprechend dem Anwendungsfall erstellt habe" nachdenken. Und normalerweise finden Sie ziemlich hackige Lösungen für dieses Problem, z. B. das Versenden einer Aktion, die verschachtelte Bestätigungs- / Abbruchaktionen, einen Übersetzungspaketschlüssel oder noch schlimmer eine Renderfunktion (oder etwas anderes unserialisierbares) enthält. Sie müssen das nicht mit Portalen machen und können einfach normale Requisiten weitergeben, da dies DeleteConfirmationPopupnur ein Kind der istDeleteButton

Fazit

Portale sind sehr nützlich, um Ihren Code zu vereinfachen. Ich konnte nicht mehr ohne sie auskommen.

Beachten Sie, dass Portalimplementierungen Ihnen auch bei anderen nützlichen Funktionen helfen können, wie z.

  • Barrierefreiheit
  • Espace-Verknüpfungen zum Schließen des Portals
  • Klick von außen behandeln (Portal schließen oder nicht)
  • Linkklick behandeln (Portal schließen oder nicht)
  • Reaktionskontext im Portalbaum verfügbar gemacht

React -Portal oder React -Modal eignen sich gut für Popups, Modals und Overlays, die im Vollbildmodus angezeigt werden sollen und im Allgemeinen in der Mitte des Bildschirms zentriert sind.

React -Tether ist den meisten React-Entwicklern unbekannt, aber es ist eines der nützlichsten Tools, die Sie dort finden können. Mit Tether können Sie Portale erstellen, das Portal wird jedoch automatisch relativ zu einem bestimmten Ziel positioniert. Dies ist perfekt für Tooltips, Dropdowns, Hotspots, Helpboxes ... Wenn Sie jemals ein Problem mit Position absolute/ relativeund z-indexhatten oder Ihr Dropdown außerhalb Ihres Ansichtsfensters liegt, wird Tether all das für Sie lösen.

Sie können beispielsweise problemlos Onboarding-Hotspots implementieren, die nach dem Klicken zu einem Tooltip erweitert werden:

Onboarding-Hotspot

Echter Produktionscode hier. Einfacher geht es nicht :)

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

Bearbeiten : Gerade entdecktes React-Gateway, mit dem Portale in den Knoten Ihrer Wahl gerendert werden können (nicht unbedingt Body)

Bearbeiten : Es scheint, dass React-Popper eine anständige Alternative zu React-Tether sein kann. PopperJS ist eine Bibliothek, die nur eine geeignete Position für ein Element berechnet, ohne das DOM direkt zu berühren, sodass der Benutzer auswählen kann, wo und wann er den DOM-Knoten platzieren möchte, während Tether direkt an den Körper angehängt wird.

Bearbeiten : Es gibt auch React-Slot-Fill, was interessant ist und zur Lösung ähnlicher Probleme beitragen kann, indem ein Element in einen reservierten Element-Slot gerendert wird, den Sie an einer beliebigen Stelle in Ihrem Baum platzieren

Sebastien Lorber
quelle
In Ihrem Beispiel-Snippet wird das Bestätigungs-Popup nicht geschlossen, wenn Sie die Aktion bestätigen (im Gegensatz zum Klicken auf Abbrechen)
dKab
Es wäre hilfreich, Ihren Portalimport in das Code-Snippet aufzunehmen. Aus welcher Bibliothek kommt <Portal>? Ich vermute, es ist ein Reaktionsportal, aber es wäre schön, es sicher zu wissen.
Stein
1
@skypecakes Bitte betrachte meine Implementierungen als Pseudocode. Ich habe es nicht gegen eine konkrete Bibliothek getestet. Ich versuche nur, dem Konzept hier keine konkrete Umsetzung beizubringen. Ich bin es gewohnt, auf das Portal zu reagieren, und der obige Code sollte gut damit funktionieren, aber mit fast jeder ähnlichen Bibliothek sollte es gut funktionieren.
Sebastien Lorber
React-Gateway ist großartig! Es unterstützt serverseitiges Rendern :)
Cyrilluce
Ich bin ein ziemlich Anfänger und freue mich sehr über eine Erklärung zu diesem Ansatz. Selbst wenn Sie das Modal wirklich an einer anderen Stelle rendern, müssen Sie bei diesem Ansatz bei jeder Löschtaste überprüfen, ob Sie die spezifische Instanz des Modals rendern möchten. Im Redux-Ansatz habe ich nur eine Instanz des Modals, die angezeigt wird oder nicht. Ist es nicht ein Leistungsproblem?
Amit Neuhaus
9

Viele gute Lösungen und wertvolle Kommentare bekannter Experten aus der JS-Community zu diesem Thema finden Sie hier. Es könnte ein Indikator dafür sein, dass es nicht so trivial ist, wie es scheint. Ich denke, dies könnte der Grund für Zweifel und Unsicherheit in dieser Frage sein.

Grundlegendes Problem hierbei ist, dass Sie in React nur Komponenten an deren übergeordneten Elementen mounten dürfen, was nicht immer das gewünschte Verhalten ist. Aber wie kann man dieses Problem angehen?

Ich schlage die Lösung vor, um dieses Problem zu beheben. Ausführlichere Problemdefinitionen, src und Beispiele finden Sie hier: https://github.com/fckt/react-layer-stack#rationale

Begründung

react/ react-domkommt mit 2 Grundannahmen / Ideen:

  • Jede Benutzeroberfläche ist natürlich hierarchisch. Deshalb haben wir die Idee, componentswelche sich gegenseitig einwickeln
  • react-dom Mounten (physisch) die untergeordnete Komponente standardmäßig an ihrem übergeordneten DOM-Knoten

Das Problem ist, dass manchmal die zweite Eigenschaft nicht das ist, was Sie in Ihrem Fall wollen. Manchmal möchten Sie Ihre Komponente in einen anderen physischen DOM-Knoten einbinden und gleichzeitig eine logische Verbindung zwischen Eltern und Kind herstellen.

Ein kanonisches Beispiel ist eine Tooltip-ähnliche Komponente: Zu einem bestimmten Zeitpunkt des Entwicklungsprozesses müssen Sie möglicherweise eine Beschreibung für Ihre hinzufügen UI element: Sie wird in einer festen Ebene gerendert und sollte ihre Koordinaten (die Koordinaten- UI elementoder Mauskoordinaten) und bei kennen Gleichzeitig benötigt es Informationen darüber, ob es jetzt angezeigt werden muss oder nicht, seinen Inhalt und einen Kontext aus übergeordneten Komponenten. Dieses Beispiel zeigt, dass die logische Hierarchie manchmal nicht mit der physischen DOM-Hierarchie übereinstimmt.

Schauen Sie sich https://github.com/fckt/react-layer-stack/blob/master/README.md#real-world-usage-example an, um das konkrete Beispiel zu sehen, das die Antwort auf Ihre Frage ist:

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
quelle
2

Meiner Meinung nach hat die Mindestimplementierung zwei Anforderungen. Ein Status, der verfolgt, ob das Modal geöffnet ist oder nicht, und ein Portal, um das Modal außerhalb des Standardreaktionsbaums zu rendern.

Die folgende ModalContainer-Komponente implementiert diese Anforderungen zusammen mit den entsprechenden Renderfunktionen für das Modal und den Trigger, die für die Ausführung des Rückrufs zum Öffnen des Modals verantwortlich sind.

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;

Und hier ist ein einfacher Anwendungsfall ...

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;

Ich verwende Renderfunktionen, weil ich die Statusverwaltung und die Boilerplate-Logik von der Implementierung der gerenderten Modal- und Trigger-Komponente isolieren möchte. Auf diese Weise können die gerenderten Komponenten so sein, wie Sie es möchten. In Ihrem Fall könnte die modale Komponente eine verbundene Komponente sein, die eine Rückruffunktion empfängt, die eine asynchrone Aktion auslöst.

Wenn Sie dynamische Requisiten von der Trigger-Komponente an die Modalkomponente senden müssen, was hoffentlich nicht allzu oft vorkommt, empfehle ich, den ModalContainer mit einer Containerkomponente zu versehen, die die dynamischen Requisiten in ihrem eigenen Zustand verwaltet und die ursprünglichen Rendermethoden wie z so.

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
quelle
0

Wickeln Sie das Modal in einen verbundenen Container und führen Sie hier die asynchrone Operation aus. Auf diese Weise können Sie sowohl den Versand zum Auslösen von Aktionen als auch die onClose-Requisite erreichen. Erreichen dispatchvon Requisiten, nicht nicht passieren mapDispatchToPropsFunktion 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);

Die App, in der das Modal gerendert und sein Sichtbarkeitsstatus festgelegt wird:

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

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

  ...

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

}
Gazdagergo
quelle