Jak mogę wyświetlić modalne okno dialogowe w Redux, które wykonuje akcje asynchroniczne?

240

Tworzę aplikację, która w niektórych sytuacjach musi wyświetlać okno dialogowe potwierdzenia.

Powiedzmy, że chcę coś usunąć, a następnie wyślę akcję taką, deleteSomething(id)aby jakiś reduktor przechwycił to zdarzenie i wypełnił okno dialogowe redukcji, aby je pokazać.

Moje wątpliwości pojawiają się po przesłaniu tego okna dialogowego.

  • W jaki sposób ten komponent może wywołać prawidłowe działanie zgodnie z pierwszym wysłanym działaniem?
  • Czy twórca akcji powinien obsługiwać tę logikę?
  • Czy możemy dodać działania wewnątrz reduktora?

edytować:

aby było jaśniej:

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

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

Więc próbuję ponownie użyć komponentu okna dialogowego. Wyświetlanie / ukrywanie okna dialogowego nie jest problemem, ponieważ można to łatwo zrobić w reduktorze. To, co próbuję określić, to sposób wysłania akcji z prawej strony zgodnie z akcją rozpoczynającą przepływ z lewej strony.

carlesba
źródło
1
Myślę, że w twoim przypadku stan dialogu (ukryj / pokaż) jest lokalny. Chciałbym użyć stanu reagowania do zarządzania wyświetlaniem / ukrywaniem okna dialogowego. W ten sposób zniknie pytanie „właściwe działanie zgodnie z pierwszym działaniem”.
Ming

Odpowiedzi:

516

Podejście, które sugeruję, jest nieco szczegółowe, ale udało mi się dość dobrze skalować w złożone aplikacje. Jeśli chcesz pokazać modal, uruchom akcję opisującą, który modal chcesz zobaczyć:

Wywołanie akcji w celu wyświetlenia modalu

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

(Ciągi mogą być oczywiście stałymi; używam ciągów wbudowanych dla uproszczenia.)

Pisanie reduktora w celu zarządzania stanem modalnym

Następnie upewnij się, że masz reduktor, który akceptuje tylko te wartości:

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

Wspaniały! Teraz, gdy wydasz akcję, state.modalzaktualizuje się, aby uwzględnić informacje o aktualnie widocznym oknie modalnym.

Pisanie głównego elementu modalnego

W katalogu głównym hierarchii komponentów dodaj <ModalRoot>komponent, który jest podłączony do sklepu Redux. Będzie nasłuchiwał state.modali wyświetlał odpowiedni komponent modalny, przekazując rekwizyty z 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)

Co my tu zrobiliśmy? ModalRootodczytuje prąd modalTypei modalPropsod state.modalktórego jest podłączony, i renderuje odpowiedni komponent, taki jak DeletePostModallub ConfirmLogoutModal. Każdy modal jest elementem!

Pisanie określonych składników modalnych

Nie ma tu ogólnych zasad. Są tylko komponentami React, które mogą wywoływać akcje, czytać coś ze stanu sklepu i po prostu być modalne .

Na przykład, DeletePostModal może wyglądać następująco:

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)

DeletePostModalJest podłączony do sklepu, więc może wyświetlić tytuł posta i działa jak dowolnego podłączonego urządzenia: może wysyłką działań, w tym hideModalprzypadku konieczne jest, aby ukryć się.

Wyodrębnianie elementu prezentacji

Byłoby niewygodne kopiowanie i wklejanie tej samej logiki układu dla każdego „określonego” modalu. Ale masz komponenty, prawda? Możesz więc wyodrębnić prezentację <Modal> komponent , który nie wie, co robią poszczególne modały, ale obsługuje ich wygląd.

Następnie określone moduły, takie jak DeletePostModalmożna użyć do renderowania:

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)

To do ciebie należy wymyślenie zestawu rekwizytów <Modal> można zaakceptować w twojej aplikacji, ale wyobrażam sobie, że możesz mieć kilka rodzajów modów (np. Modal informacji, modal potwierdzenia itp.) I kilka stylów dla nich.

Dostępność i ukrywanie na Click Outside lub Escape Key

Ostatnią ważną częścią modali jest to, że generalnie chcemy je ukryć, gdy użytkownik kliknie na zewnątrz lub naciśnie klawisz Escape.

Zamiast udzielać porad na temat wdrażania tego, sugeruję, abyś sam tego nie wdrażał. Trudno jest dobrze zrozumieć, biorąc pod uwagę dostępność.

Zamiast tego sugerowałbym, abyś użył dostępnego, modalnego komponentu, takiego jak react-modal. Jest całkowicie konfigurowalny, możesz umieścić w nim wszystko, co chcesz, ale poprawnie obsługuje dostępność, dzięki czemu niewidomi mogą nadal korzystać z twojego modalu.

Możesz nawet owinąć react-modalwłasny<Modal> który akceptuje rekwizyty specyficzne dla twoich aplikacji i generuje przyciski potomne lub inne treści. To wszystko tylko komponenty!

Inne podejścia

Jest na to więcej niż jeden sposób.

Niektórzy ludzie nie lubią gadatliwości tego podejścia i wolą mieć <Modal>komponent, który mogą renderować bezpośrednio w swoich komponentach za pomocą techniki zwanej „portalami”. Portale pozwalają ci renderować komponent wewnątrz twojego, gdy faktycznie będzie renderowany w określonym miejscu w DOM, co jest bardzo wygodne w przypadku modałów.

W rzeczywistości react-modal, o czym już wcześniej wspomniałem, robi to wewnętrznie, więc technicznie nie musisz nawet renderować tego z góry. Nadal uważam, że miło jest oddzielić modal, który chcę pokazać, od pokazującego go komponentu, ale możesz także używać react-modalbezpośrednio z komponentów i pomijać większość tego, co napisałem powyżej.

Zachęcam do rozważenia obu podejść, eksperymentowania z nimi i wybrania tego, co według ciebie będzie najlepsze dla Twojej aplikacji i zespołu.

Dan Abramov
źródło
35
Jedną rzeczą, którą zasugerowałem, jest to, aby reduktor utrzymywał listę modów, które można wypychać i otwierać. Choć to głupio brzmi, konsekwentnie spotykam się z sytuacjami, w których projektanci / typy produktów chcą, żebym otworzył modal z modalu, i miło pozwolić użytkownikom „wrócić”.
Kyle
9
Tak, zdecydowanie jest to coś, co Redux ułatwia budować, ponieważ możesz po prostu zmienić swój stan na tablicę. Osobiście współpracowałem z projektantami, którzy przeciwnie, chcieli, aby modały były ekskluzywne, więc podejście, które napisałem, rozwiązuje przypadkowe zagnieżdżenie. Ale tak, możesz to zrobić na dwa sposoby.
Dan Abramov
4
Z mojego doświadczenia powiedziałbym: jeśli modal jest powiązany z lokalnym komponentem (tak jak modal potwierdzenia usunięcia jest powiązany z przyciskiem usuwania), łatwiej jest użyć portalu, w przeciwnym razie użyj akcji redux. Zgadzam się z @Kyle, że powinno być możliwe otwarcie modalu z modalu. Działa również domyślnie z portalami, ponieważ są one dodawane w celu udokumentowania treści, więc portale ładnie się na sobie układają (dopóki nie zepsujesz wszystkiego za pomocą indeksu Z: p)
Sebastien Lorber
4
@ DanAbramov, twoje rozwiązanie jest świetne, ale mam niewielki problem. Nic poważnego. Korzystam z Material-ui w projekcie, kiedy zamykam modal, po prostu go wyłączam, zamiast „odtwarzać” animację zanikania. Prawdopodobnie potrzebujesz jakiegoś opóźnienia? Czy przechowywać każdy modal jako listę wewnątrz ModalRoot? Propozycje?
gcerar
7
Czasami chcę wywoływać niektóre funkcje po zamknięciu modalu (na przykład wywołać funkcje z wartościami pola wejściowego wewnątrz modalu). Przekazałbym te funkcje co modalPropsdo akcji. Jest to jednak sprzeczne z zasadą utrzymywania stanu szeregowalnego. Jak mogę rozwiązać ten problem?
chmanie
98

Aktualizacja : React 16.0 wprowadził portale poprzez ReactDOM.createPortal link

Aktualizacja : kolejne wersje React (Fibre: prawdopodobnie 16 lub 17) będą zawierać metodę tworzenia portali: ReactDOM.unstable_createPortal() link


Użyj portali

Pierwsza część odpowiedzi Dana Abramova jest w porządku, ale wiąże się z dużą ilością płyty kotłowej. Jak powiedział, możesz także korzystać z portali. Rozwinę trochę ten pomysł.

Zaletą portalu jest to, że okienko wyskakujące i przycisk pozostają bardzo blisko drzewa React, z bardzo prostą komunikacją nadrzędny / podrzędny za pomocą rekwizytów: możesz łatwo obsługiwać akcje asynchroniczne z portalami lub pozwolić rodzicowi dostosować portal.

Co to jest portal?

Portal pozwala na renderowanie bezpośrednio w document.bodyelemencie głęboko zagnieżdżonym w drzewie React.

Chodzi o to, że na przykład renderujesz w ciele następujące drzewo React:

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

I dostajesz jako wynik:

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

inside-portalWęzeł został przekłada się wewnątrz <body>, a nie od jego normalnej głęboko zagnieżdżonej miejscu.

Kiedy korzystać z portalu

Portal jest szczególnie pomocny do wyświetlania elementów, które powinny się znajdować na istniejących komponentach React: wyskakujące okienka, listy rozwijane, sugestie, punkty aktywne

Dlaczego warto korzystać z portalu?

Nie ma już problemów z indeksowaniem Z : portal pozwala na renderowanie <body>. Jeśli chcesz wyświetlić wyskakujące okienko lub menu rozwijane, jest to naprawdę fajny pomysł, jeśli nie chcesz walczyć z problemami z indeksem Z. Elementy portalu są dodawane document.bodyw kolejności montowania, co oznacza, że ​​o ile nie będziesz się nimi bawić z-index, domyślnym zachowaniem będzie układanie portali jeden na drugim, w kolejności montowania. W praktyce oznacza to, że możesz bezpiecznie otworzyć wyskakujące okienko z innego wyskakującego okienka i upewnij się, że drugie wyskakujące okienko wyświetli się nad pierwszym, nawet o tym nie myśląc z-index.

W praktyce

Najprościej: użyj lokalnego stanu React: jeśli uważasz, że w przypadku prostego wyskakującego okienka potwierdzenia usunięcia nie warto mieć płyty Redux, możesz skorzystać z portalu i znacznie upraszcza to kod. W przypadku takiego przypadku użycia, w którym interakcja jest bardzo lokalna i jest w rzeczywistości dość szczegółowa, czy naprawdę zależy ci na ponownym ładowaniu, podróżowaniu w czasie, rejestrowaniu akcji i wszystkich korzyściach, jakie przynosi Ci Redux? Osobiście nie używam w tym przypadku stanu lokalnego. Kod staje się tak prosty, jak:

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

Proste: nadal możesz używać stanu Redux : jeśli naprawdę chcesz, możesz nadal używać, connectaby wybrać, czy ma DeleteConfirmationPopupbyć pokazywany, czy nie. Ponieważ portal pozostaje głęboko zagnieżdżony w drzewie React, bardzo łatwo jest dostosować zachowanie tego portalu, ponieważ rodzic może przekazywać rekwizyty do portalu. Jeśli nie korzystasz z portali, zwykle musisz wyświetlać wyskakujące okienka na górze drzewa Reactz-index powody, i zwykle musisz pomyśleć o takich rzeczach, jak „jak dostosować ogólny rodzaj DeleteConfirmationPopup, który zbudowałem zgodnie z przypadkiem użycia „. I zwykle znajdziesz dość hackerskie rozwiązania tego problemu, takie jak wysłanie akcji zawierającej zagnieżdżone akcje potwierdzenia / anulowania, klucz pakietu tłumaczeń lub, co gorsza, funkcja renderowania (lub coś innego, czego nie można zdiagnozować). Nie musisz tego robić z portalami i możesz po prostu przekazywać regularne rekwizyty, ponieważ DeleteConfirmationPopupjest to tylko dzieckoDeleteButton

Wniosek

Portale są bardzo przydatne w celu uproszczenia kodu. Nie poradzę sobie bez nich.

Pamiętaj, że implementacje portalu mogą również pomóc w innych przydatnych funkcjach, takich jak:

  • Dostępność
  • Skróty Espace, aby zamknąć portal
  • Obsługa kliknięcia zewnętrznego (zamknij portal lub nie)
  • Obsługuj kliknięcie linku (zamknij portal lub nie)
  • React Kontekst udostępniony w drzewie portalu

Reaktywuj portal lub Reaktywuj modalnie są przydatne w przypadku wyskakujących okienek, modów i nakładek, które powinny być pełnoekranowe, zwykle wyśrodkowane na środku ekranu.

Reakt-tether jest nieznany większości programistów React, ale jest to jedno z najbardziej przydatnych narzędzi, jakie można tam znaleźć. Tether pozwala tworzyć portale, ale automatycznie pozycjonuje portal względem danego celu. Jest to idealne rozwiązanie dla podpowiedzi, menu rozwijanych, hotspotów, skrzynek pomocy ... Jeśli kiedykolwiek miałeś problem z pozycją absolute/ relativei z-index, lub gdy twoje menu rozwijane wykraczało poza obszar wyświetlania, Tether rozwiąże to wszystko za Ciebie.

Możesz na przykład łatwo wdrożyć wbudowane hotspoty, które po kliknięciu zmieniają się w etykietkę:

Wbudowany punkt dostępu

Prawdziwy kod produkcyjny tutaj. Nie może być prostsze :)

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

Edycja : właśnie odkryłem bramkę reagującą, która pozwala renderować portale w wybranym węźle (niekoniecznie treści)

Edycja : wygląda na to, że reagujący popper może być przyzwoitą alternatywą dla tetheringu. PopperJS to biblioteka, która oblicza tylko odpowiednią pozycję dla elementu, nie dotykając bezpośrednio DOM, pozwalając użytkownikowi wybrać, gdzie i kiedy chce umieścić węzeł DOM, podczas gdy Tether dołącza się bezpośrednio do ciała.

Edycja : istnieje również możliwość wypełnienia szczeliny, która jest interesująca i może pomóc w rozwiązaniu podobnych problemów, umożliwiając renderowanie elementu w zarezerwowanym gnieździe elementu, który umieszczasz w dowolnym miejscu w drzewie

Sebastien Lorber
źródło
W przykładowym fragmencie wyskakujące okienko potwierdzenia nie zostanie zamknięte, jeśli potwierdzisz akcję (w przeciwieństwie do kliknięcia przycisku Anuluj)
dKab
Pomocne byłoby włączenie importu portalu do fragmentu kodu. Z jakiej biblioteki <Portal>pochodzi? Zgaduję, że to portal reagujący, ale dobrze byłoby wiedzieć na pewno.
kamień
1
@skypecakes proszę traktować moje implementacje jako pseudo-kod. Nie przetestowałem tego na żadnej konkretnej bibliotece. Po prostu próbuję nauczyć tutaj tej koncepcji, a nie konkretnej implementacji. Jestem przyzwyczajony do reagowania na portal i powyższy kod powinien dobrze z nim działać, ale powinien działać poprawnie z prawie każdym podobnym lib.
Sebastien Lorber
Reaguj bramka jest niesamowita! Obsługuje renderowanie po stronie serwera :)
cyrilluce
Jestem całkiem początkującym, więc będę bardzo szczęśliwy z powodu wyjaśnienia tego podejścia. Nawet jeśli naprawdę renderujesz modal w innym miejscu, w tym podejściu będziesz musiał sprawdzić na każdym przycisku usuwania, czy powinieneś renderować konkretną instancję modalu. W podejściu redux mam tylko jedną instancję modalu, która jest pokazana lub nie. Czy to nie dotyczy wydajności?
Amit Neuhaus,
9

Wiele dobrych rozwiązań i cennych komentarzy znanych ekspertów ze społeczności JS na ten temat można znaleźć tutaj. Może to wskazywać, że nie jest to tak trywialny problem, jak mogłoby się wydawać. Myślę, że właśnie dlatego może być źródłem wątpliwości i niepewności w tej kwestii.

Podstawowym problemem jest to, że w React możesz montować tylko komponenty do jego elementów nadrzędnych, co nie zawsze jest pożądanym zachowaniem. Ale jak rozwiązać ten problem?

Proponuję rozwiązanie, rozwiązane w celu rozwiązania tego problemu. Bardziej szczegółową definicję problemu, src i przykłady można znaleźć tutaj: https://github.com/fckt/react-layer-stack#rationale

Racjonalne uzasadnienie

react/ react-domma 2 podstawowe założenia / pomysły:

  • każdy interfejs jest naturalnie hierarchiczny. Właśnie dlatego mamy pomysł, componentsktóre się owijają
  • react-dom domyślnie montuje (fizycznie) komponent potomny w nadrzędnym węźle DOM

Problem polega na tym, że czasami druga właściwość nie jest tym, czego chcesz w twoim przypadku. Czasami chcesz zamontować komponent w innym fizycznym węźle DOM i jednocześnie utrzymywać logiczne połączenie między rodzicem a dzieckiem.

Przykładem kanonicznym jest komponent podobny do podpowiedzi: w pewnym momencie procesu programowania może być konieczne dodanie opisu UI element: będzie renderowany w warstwie stałej i powinien znać jego współrzędne (które są UI elementwspółrzędnymi myszy lub współrzędnych myszy) i w tym samym czasie potrzebuje informacji, czy należy ją teraz pokazać, czy nie, jej zawartości i kontekstu ze składników nadrzędnych. Ten przykład pokazuje, że czasami logiczna hierarchia nie jest zgodna z fizyczną hierarchią DOM.

Spójrz na https://github.com/fckt/react-layer-stack/blob/master/README.md#real-world-usage-example, aby zobaczyć konkretny przykład, który jest odpowiedzią na twoje pytanie:

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
źródło
2

Moim zdaniem absolutna minimalna implementacja ma dwa wymagania. Stan, który śledzi, czy modal jest otwarty, czy nie, oraz portal do renderowania modalu poza standardowym drzewem reakcji.

Składnik ModalContainer poniżej implementuje te wymagania wraz z odpowiednimi funkcjami renderowania dla modalu i wyzwalacza, który jest odpowiedzialny za wykonanie wywołania zwrotnego w celu otwarcia modalu.

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;

A oto prosty przypadek użycia ...

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;

Korzystam z funkcji renderowania, ponieważ chcę odizolować zarządzanie stanem i logikę podstawową od implementacji renderowanego modułu modalnego i wyzwalacza. Dzięki temu renderowane komponenty mogą być takie, jakie chcesz. W twoim przypadku przypuszczam, że komponent modalny może być połączonym komponentem, który odbiera funkcję zwrotną, która wywołuje akcję asynchroniczną.

Jeśli chcesz wysłać dynamiczne rekwizyty do komponentu modalnego z komponentu wyzwalającego, co, mam nadzieję, nie zdarza się zbyt często, polecam owijanie ModalContainer komponentem kontenerowym, który zarządza rekwizytami dynamicznymi we własnym stanie i ulepszenie oryginalnych metod renderowania, takich jak więc.

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
źródło
0

Zawiń modal w podłączony kontener i wykonaj tutaj operację asynchroniczną. W ten sposób możesz dotrzeć zarówno do wysyłki, aby wywołać akcje, jak i do prop onClose. Aby dosięgnąć dispatchrekwizytów, nie przekazuj mapDispatchToPropsfunkcji do 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);

Aplikacja, w której moduł jest renderowany i ustawiony jest jego stan widoczności:

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

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

  ...

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

}
gazdagergo
źródło