Bagaimana saya bisa menampilkan dialog modal di Redux yang melakukan tindakan asinkron?

240

Saya sedang membangun aplikasi yang perlu menampilkan dialog konfirmasi dalam beberapa situasi.

Katakanlah saya ingin menghapus sesuatu, maka saya akan mengirimkan tindakan seperti deleteSomething(id)beberapa peredam akan menangkap peristiwa itu dan akan mengisi peredam dialog untuk menunjukkannya.

Keraguan saya muncul ketika dialog ini dikirimkan.

  • Bagaimana komponen ini mengirimkan tindakan yang tepat sesuai dengan tindakan pertama yang dikirim?
  • Haruskah pembuat tindakan menangani logika ini?
  • Bisakah kita menambahkan tindakan di dalam peredam?

edit:

untuk membuatnya lebih jelas:

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

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

Jadi saya mencoba menggunakan kembali komponen dialog. Menampilkan / menyembunyikan dialog itu bukan masalah karena ini dapat dengan mudah dilakukan di peredam. Yang saya coba jelaskan adalah bagaimana mengirimkan tindakan dari sisi kanan sesuai dengan tindakan yang memulai aliran di sisi kiri.

carlesba
sumber
1
Saya pikir dalam kasus Anda keadaan dialog (sembunyikan / tampilkan) adalah lokal. Saya akan memilih untuk menggunakan status reaksi untuk mengelola dialog yang menampilkan / menyembunyikan. Dengan cara ini, pertanyaan tentang "tindakan yang tepat sesuai dengan tindakan pertama" akan hilang.
Ming

Jawaban:

516

Pendekatan yang saya sarankan agak bertele-tele, tetapi saya menemukan skala untuk aplikasi yang kompleks. Saat Anda ingin menunjukkan modal, jalankan tindakan yang menggambarkan modal mana yang ingin Anda lihat:

Mengirimkan Tindakan untuk Menampilkan Modal

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

(String bisa menjadi konstanta tentu saja; Saya menggunakan string sebaris untuk kesederhanaan.)

Menulis Reducer untuk Mengelola Modal State

Kemudian pastikan Anda memiliki peredam yang hanya menerima nilai-nilai ini:

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

Bagus! Sekarang, ketika Anda mengirim tindakan, state.modalakan diperbarui untuk memasukkan informasi tentang jendela modal yang saat ini terlihat.

Menulis Komponen Modal Root

Pada akar hierarki komponen Anda, tambahkan <ModalRoot>komponen yang terhubung ke toko Redux. Ini akan mendengarkan state.modaldan menampilkan komponen modal yang sesuai, meneruskan alat peraga dari 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)

Apa yang sudah kita lakukan di sini? ModalRootmembaca arus modalTypedan modalPropsdari state.modalmana itu terhubung, dan membuat komponen yang sesuai seperti DeletePostModalatau ConfirmLogoutModal. Setiap modal adalah komponen!

Menulis Komponen Modal Tertentu

Tidak ada aturan umum di sini. Mereka hanya komponen Bereaksi yang dapat mengirim tindakan, membaca sesuatu dari keadaan toko, dan kebetulan menjadi modals .

Misalnya, DeletePostModalmungkin terlihat seperti:

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)

The DeletePostModalterhubung ke toko sehingga dapat menampilkan judul posting dan karya-karya seperti komponen yang terhubung: dapat mengirimkan tindakan, termasuk hideModaljika diperlukan untuk menyembunyikan diri.

Mengekstraksi Komponen Presentasi

Akan aneh untuk menyalin-menempel logika tata letak yang sama untuk setiap modal "spesifik". Tetapi Anda memiliki komponen, bukan? Jadi, Anda dapat mengekstraksi komponen presentasi <Modal> yang tidak tahu apa yang dilakukan modals tertentu, tetapi menangani tampilannya.

Kemudian, modals spesifik seperti DeletePostModaldapat menggunakannya untuk rendering:

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)

Terserah kepada Anda untuk datang dengan seperangkat alat peraga yang <Modal>dapat menerima dalam aplikasi Anda, tetapi saya akan membayangkan bahwa Anda mungkin memiliki beberapa jenis modals (misalnya modal info, modal konfirmasi, dll), dan beberapa gaya untuk mereka.

Aksesibilitas dan Menyembunyikan di Klik Luar atau Tombol Escape

Bagian penting terakhir tentang modals adalah bahwa umumnya kita ingin menyembunyikannya ketika pengguna mengklik di luar atau menekan Escape.

Alih-alih memberi Anda saran untuk menerapkan ini, saya sarankan Anda tidak menerapkannya sendiri. Sulit untuk mendapatkan yang benar mengingat aksesibilitas.

Sebagai gantinya, saya akan menyarankan Anda untuk menggunakan komponen modal off-the-shelf yang dapat diakses seperti react-modal. Ini benar-benar dapat dikustomisasi, Anda dapat memasukkan apa pun yang Anda inginkan di dalamnya, tetapi menangani aksesibilitas dengan benar sehingga orang buta masih dapat menggunakan modal Anda.

Anda bahkan dapat membungkus react-modalsendiri <Modal>yang menerima alat peraga khusus untuk aplikasi Anda dan menghasilkan tombol anak atau konten lainnya. Itu semua hanya komponen!

Pendekatan Lain

Ada lebih dari satu cara untuk melakukannya.

Beberapa orang tidak suka verbositas dari pendekatan ini dan lebih suka memiliki <Modal>komponen yang dapat mereka render tepat di dalam komponen mereka dengan teknik yang disebut "portal". Portal memungkinkan Anda membuat komponen di dalam Anda sementara sebenarnya itu akan membuat di tempat yang telah ditentukan di DOM, yang sangat nyaman untuk modals.

Sebenarnya react-modalsaya ditautkan sebelumnya sudah melakukan itu secara internal sehingga secara teknis Anda bahkan tidak perlu membuatnya dari atas. Saya masih merasa senang untuk memisahkan modal yang ingin saya tunjukkan dari komponen yang menunjukkannya, tetapi Anda juga dapat menggunakan react-modallangsung dari komponen Anda, dan melewatkan sebagian besar dari apa yang saya tulis di atas.

Saya mendorong Anda untuk mempertimbangkan kedua pendekatan, bereksperimen dengan mereka, dan memilih apa yang menurut Anda paling cocok untuk aplikasi Anda dan untuk tim Anda.

Dan Abramov
sumber
35
Satu hal yang saya sarankan adalah peredam memiliki daftar modals yang dapat didorong dan muncul. Kedengarannya konyol, saya secara konsisten mengalami situasi di mana desainer / jenis produk ingin saya membuka modal dari modal, dan menyenangkan untuk memungkinkan pengguna untuk "kembali".
Kyle
9
Ya, tentu saja, ini adalah hal yang mudah dibuat Redux karena Anda bisa mengubah status Anda menjadi array. Secara pribadi saya telah bekerja dengan desainer yang, sebaliknya, ingin modalnya eksklusif, jadi pendekatan yang saya tuliskan memecahkan sarang yang tidak disengaja. Tapi ya, Anda bisa mendapatkan keduanya.
Dan Abramov
4
Dalam pengalaman saya, saya akan mengatakan: jika modal terkait dengan komponen lokal (seperti modal konfirmasi konfirmasi terkait dengan tombol hapus), lebih mudah menggunakan portal, jika tidak gunakan tindakan redux. Setuju dengan @Kyle satu harus dapat membuka modal dari modal. Ini juga berfungsi secara default dengan portal karena mereka ditambahkan untuk mendokumentasikan tubuh sehingga portal saling menumpuk satu sama lain (sampai Anda mengacaukan semuanya dengan z-index: p)
Sebastien Lorber
4
@DanAbramov, solusi Anda bagus, tapi saya punya masalah kecil. Tidak ada yang serius. Saya menggunakan Material-ui dalam proyek, ketika menutup modal itu hanya mematikannya, alih-alih "memainkan" animasi fade-away. Mungkin perlu melakukan semacam penundaan? Atau simpan setiap modal di sana sebagai daftar di dalam ModalRoot? Saran?
gcerar
7
Kadang-kadang saya ingin memanggil fungsi-fungsi tertentu setelah modal ditutup (misalnya memanggil fungsi-fungsi dengan nilai-nilai bidang input di dalam modal). Saya akan melewati fungsi-fungsi ini sebagai modalPropstindakan. Ini melanggar aturan menjaga negara serializable. Bagaimana saya bisa mengatasi masalah ini?
chmanie
98

Pembaruan : Bereaksi 16.0 portal yang diperkenalkan melalui ReactDOM.createPortal tautan

Pembaruan : React versi berikutnya (Fiber: mungkin 16 atau 17) akan menyertakan metode untuk membuat portal: ReactDOM.unstable_createPortal() tautan


Gunakan portal

Dan Abramov menjawab bagian pertama baik-baik saja, tetapi melibatkan banyak boilerplate. Seperti katanya, Anda juga bisa menggunakan portal. Saya akan sedikit memperluas ide itu.

Keuntungan dari portal adalah popup dan tombolnya tetap sangat dekat dengan React tree, dengan komunikasi orangtua / anak yang sangat sederhana menggunakan alat peraga: Anda dapat dengan mudah menangani tindakan async dengan portal, atau membiarkan orangtua menyesuaikan portal.

Apa itu portal?

Portal memungkinkan Anda untuk merender langsung di dalam document.bodyelemen yang bersarang di pohon Bereaksi Anda.

Idenya adalah bahwa misalnya Anda merender ke dalam pohon React berikut:

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

Dan Anda dapatkan sebagai output:

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

The inside-portalsimpul telah diterjemahkan dalam <body>, bukannya normal, tempat sangat-bersarang nya.

Kapan harus menggunakan portal

Portal sangat membantu untuk menampilkan elemen yang harus di atas komponen Bereaksi Anda yang ada: popup, dropdown, saran, hotspot

Mengapa menggunakan portal

Tidak ada masalah z-indeks lagi : portal memungkinkan Anda untuk membuat <body>. Jika Anda ingin menampilkan popup atau dropdown, ini ide yang sangat bagus jika Anda tidak ingin harus berjuang melawan masalah indeks-z. Elemen portal dapat ditambahkan lakukan document.bodydalam urutan pemasangan, yang berarti bahwa kecuali Anda bermain z-index, perilaku default adalah menumpuk portal di atas satu sama lain, dalam urutan pemasangan. Dalam praktiknya, itu berarti Anda dapat dengan aman membuka sembulan dari dalam sembulan lain, dan pastikan bahwa sembulan kedua akan ditampilkan di atas yang pertama, tanpa harus memikirkan z-index.

Dalam praktek

Paling sederhana: gunakan status Bereaksi lokal: jika Anda berpikir, untuk popup konfirmasi penghapusan sederhana, tidak perlu memiliki Redux boilerplate, maka Anda dapat menggunakan portal dan sangat menyederhanakan kode Anda. Untuk kasus penggunaan seperti itu, di mana interaksinya sangat lokal dan sebenarnya cukup detail implementasi, apakah Anda benar-benar peduli dengan hot-reload, perjalanan-waktu, logging tindakan dan semua manfaat yang Redux berikan kepada Anda? Secara pribadi, saya tidak dan menggunakan negara bagian dalam kasus ini. Kode menjadi sesederhana:

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

Sederhana: Anda masih dapat menggunakan status Redux : jika Anda benar-benar ingin, Anda masih dapat menggunakan connectuntuk memilih apakah DeleteConfirmationPopupakan ditampilkan atau tidak. Karena portal tetap bersarang di pohon Bereaksi Anda, sangat mudah untuk menyesuaikan perilaku portal ini karena orang tua Anda dapat meneruskan alat peraga ke portal. Jika Anda tidak menggunakan portal, biasanya Anda harus membuat munculan di bagian atas pohon Bereaksiz-indexalasan, dan biasanya harus memikirkan hal-hal seperti "bagaimana saya menyesuaikan DeleteConfirmationPopup generik yang saya buat sesuai dengan use case". Dan biasanya Anda akan menemukan solusi yang cukup rumit untuk masalah ini, seperti mengirim tindakan yang berisi tindakan konfirmasi / pembatalan bersarang, kunci bundel terjemahan, atau lebih buruk lagi, fungsi render (atau sesuatu yang tidak dapat di-unserializable). Anda tidak harus melakukan itu dengan portal, dan hanya dapat melewati alat peraga biasa, karena DeleteConfirmationPopuphanya anak-anakDeleteButton

Kesimpulan

Portal sangat berguna untuk menyederhanakan kode Anda. Saya tidak bisa melakukannya tanpa mereka lagi.

Perhatikan bahwa implementasi portal juga dapat membantu Anda dengan fitur berguna lainnya seperti:

  • Aksesibilitas
  • Tambahkan pintasan untuk menutup portal
  • Menangani klik luar (tutup portal atau tidak)
  • Menangani klik tautan (tutup portal atau tidak)
  • Bereaksi Konteks tersedia di pohon portal

react-portal atau react-modal bagus untuk popup, modals, dan overlay yang harus layar penuh, umumnya dipusatkan di tengah layar.

reaksi-tether tidak diketahui oleh sebagian besar pengembang Bereaksi, namun itu salah satu alat paling berguna yang bisa Anda temukan di sana. Tether memungkinkan Anda untuk membuat portal, tetapi akan memposisikan portal secara otomatis, relatif terhadap target yang diberikan. Ini sempurna untuk tooltips, dropdown, hotspot, helpbox ... Jika Anda pernah memiliki masalah dengan posisi absolute/ relativedan z-index, atau dropdown Anda keluar dari viewport Anda, Tether akan menyelesaikan semua itu untuk Anda.

Anda dapat, misalnya, dengan mudah menerapkan hotspot onboarding, yang diperluas ke tooltip setelah diklik:

Hotspot onboarding

Kode produksi nyata di sini. Tidak bisa lebih sederhana :)

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

Sunting : baru saja ditemukan gateway reaksi yang memungkinkan untuk merender portal ke dalam simpul pilihan Anda (belum tentu badan)

Sunting : sepertinya react-popper dapat menjadi alternatif yang layak untuk bereaksi-tether. PopperJS adalah pustaka yang hanya menghitung posisi yang tepat untuk suatu elemen, tanpa menyentuh DOM secara langsung, membiarkan pengguna memilih di mana dan kapan ia ingin meletakkan simpul DOM, sementara Tether menambahkan langsung ke tubuh.

Sunting : ada juga react-slot-fill yang menarik dan dapat membantu menyelesaikan masalah serupa dengan memungkinkan untuk merender elemen ke slot elemen yang dipesan yang Anda letakkan di mana pun Anda inginkan di pohon Anda

Sebastien Lorber
sumber
Dalam cuplikan contoh Anda, popup konfirmasi tidak akan ditutup jika Anda mengonfirmasi tindakan (berlawanan dengan ketika Anda mengklik Batal)
dKab
Akan sangat membantu untuk memasukkan impor Portal Anda dalam snipet kode. Perpustakaan apa yang <Portal>berasal? Saya menduga itu bereaksi-portal, tetapi akan menyenangkan untuk mengetahui dengan pasti.
batu
1
@skypecakes harap pertimbangkan implementasi saya sebagai pseudo-code. Saya tidak mengujinya terhadap perpustakaan beton. Saya hanya mencoba mengajarkan konsep di sini bukan implementasi konkret. Saya terbiasa bereaksi-portal dan kode di atas harus berfungsi dengan baik, tetapi harus bekerja dengan hampir semua lib yang serupa.
Sebastien Lorber
gateway reaksi luar biasa! Ini mendukung rendering sisi server :)
cyrilluce
Saya cukup pemula sehingga akan sangat senang untuk beberapa penjelasan tentang pendekatan ini. Bahkan jika Anda benar-benar merender modal di tempat lain, dalam pendekatan ini Anda harus memeriksa setiap tombol hapus jika Anda harus merender instance spesifik modal. Dalam pendekatan redux saya hanya memiliki satu contoh dari modal yang ditunjukkan atau tidak. Bukankah itu masalah kinerja?
Amit Neuhaus
9

Banyak solusi yang baik dan komentar berharga oleh para ahli terkenal dari komunitas JS tentang topik ini dapat ditemukan di sini. Ini bisa menjadi indikator bahwa itu bukan masalah sepele seperti yang terlihat. Saya pikir inilah mengapa ini bisa menjadi sumber keraguan dan ketidakpastian tentang masalah ini.

Masalah mendasar di sini adalah bahwa dalam Bereaksi Anda hanya diizinkan untuk memasang komponen ke induknya, yang tidak selalu merupakan perilaku yang diinginkan. Tetapi bagaimana cara mengatasi masalah ini?

Saya mengusulkan solusi, ditujukan untuk memperbaiki masalah ini. Definisi masalah yang lebih terperinci, src dan contoh-contohnya dapat ditemukan di sini: https://github.com/fckt/react-layer-stack#rationale

Alasan

react/ react-domDilengkapi dengan 2 asumsi / ide dasar:

  • setiap UI bersifat hierarkis secara alami. Inilah mengapa kami memiliki ide untuk componentssaling membungkus
  • react-dom me-mount komponen anak (secara fisik) ke simpul DOM induknya secara default

Masalahnya adalah bahwa terkadang properti kedua bukan yang Anda inginkan dalam kasus Anda. Terkadang Anda ingin memasang komponen Anda ke simpul DOM fisik yang berbeda dan memegang koneksi logis antara orang tua dan anak pada saat yang sama.

Contoh kanonik adalah komponen Tooltip-like: di beberapa titik proses pengembangan Anda dapat menemukan bahwa Anda perlu menambahkan beberapa deskripsi untuk UI element: Anda akan merender dalam layer tetap dan harus tahu koordinatnya (yang merupakan UI elementcoord atau mouse coords) dan pada saat yang sama ia membutuhkan informasi apakah perlu ditampilkan sekarang atau tidak, isinya dan beberapa konteks dari komponen induk. Contoh ini menunjukkan bahwa terkadang hierarki logis tidak cocok dengan hierarki DOM fisik.

Lihatlah https://github.com/fckt/react-layer-stack/blob/master/README.md#real-world-usage-example untuk melihat contoh konkret yang merupakan jawaban untuk pertanyaan Anda:

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

Menurut pendapat saya implementasi minimum yang telanjang memiliki dua persyaratan. Status yang melacak apakah modal terbuka atau tidak, dan portal untuk membuat modal di luar pohon reaksi standar.

Komponen ModalContainer di bawah ini mengimplementasikan persyaratan tersebut bersama dengan fungsi render yang sesuai untuk modal dan pemicu, yang bertanggung jawab untuk mengeksekusi callback untuk membuka 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;

Dan inilah kasus penggunaan sederhana ...

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;

Saya menggunakan fungsi render, karena saya ingin mengisolasi manajemen negara dan logika boilerplate dari implementasi modal yang diberikan dan komponen pemicu. Ini memungkinkan komponen yang dirender menjadi apa pun yang Anda inginkan. Dalam kasus Anda, saya kira komponen modal bisa menjadi komponen yang terhubung yang menerima fungsi panggilan balik yang mengirimkan tindakan asinkron.

Jika Anda perlu mengirim alat peraga dinamis ke komponen modal dari komponen pemicu, yang mudah-mudahan tidak terjadi terlalu sering, saya sarankan membungkus ModalContainer dengan komponen wadah yang mengelola alat peraga dinamis di negaranya sendiri dan meningkatkan metode render asli seperti begitu.

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

Bungkus modal ke dalam wadah yang terhubung dan lakukan operasi async di sini. Dengan cara ini Anda dapat menjangkau pengiriman untuk memicu tindakan dan onClose prop juga. Untuk mencapai dispatchdari alat peraga, jangan tidak lulus mapDispatchToPropsfungsi untuk 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);

Aplikasi tempat modal diberikan dan status visibilitasnya diatur:

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

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

  ...

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

}
gazdagergo
sumber