Come posso visualizzare una finestra di dialogo modale in Redux che esegue azioni asincrone?


240

Sto creando un'app che deve mostrare una finestra di dialogo di conferma in alcune situazioni.

Diciamo che voglio rimuovere qualcosa, quindi invierò un'azione in deleteSomething(id)modo tale che un riduttore catturi quell'evento e riempia il riduttore di dialogo per mostrarlo.

Il mio dubbio arriva quando si presenta questa finestra di dialogo.

  • In che modo questo componente può inviare l'azione corretta in base alla prima azione inviata?
  • Il creatore di azioni dovrebbe gestire questa logica?
  • Possiamo aggiungere azioni all'interno del riduttore?

modificare:

per chiarire:

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

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

Quindi sto cercando di riutilizzare il componente della finestra di dialogo. Mostrare / nascondere la finestra di dialogo non è un problema in quanto ciò può essere fatto facilmente nel riduttore. Quello che sto cercando di specificare è come inviare l'azione dal lato destro in base all'azione che avvia il flusso sul lato sinistro.


1
Penso che nel tuo caso lo stato del dialogo (nascondi / mostra) sia locale. Vorrei scegliere di utilizzare lo stato di reazione per gestire la visualizzazione / nascondere la finestra di dialogo. In questo modo, la questione dell '"azione corretta secondo la prima azione" sparirà.
Ming,

Risposte:


516

L'approccio che suggerisco è un po 'prolisso, ma l'ho trovato per adattarsi abbastanza bene ad app complesse. Quando vuoi mostrare un modale, lancia un'azione che descriva quale modale ti piacerebbe vedere:

Invio di un'azione per mostrare il modale

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

(Le stringhe possono essere costanti ovviamente; sto usando stringhe in linea per semplicità.)

Scrivere un riduttore per gestire lo stato modale

Quindi assicurati di avere un riduttore che accetta solo questi valori:

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

Grande! Ora, quando si invia un'azione, state.modalverrà aggiornato per includere le informazioni sulla finestra modale attualmente visibile.

Scrittura del componente modale root

Alla radice della gerarchia dei componenti, aggiungi un <ModalRoot>componente collegato all'archivio Redux. Ascolterà state.modale visualizzerà un componente modale appropriato, inoltrando gli oggetti di scena dal 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)

Cosa abbiamo fatto qui? ModalRootlegge la corrente modalTypee modalPropsda state.modalcui è connessa e rende un componente corrispondente come DeletePostModaloConfirmLogoutModal . Ogni modale è un componente!

Scrittura di componenti modali specifici

Non ci sono regole generali qui. Sono solo componenti di React che possono inviare azioni, leggere qualcosa dallo stato del punto vendita e semplicemente essere modali .

Ad esempio, DeletePostModalpotrebbe apparire come:

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)

Il DeletePostModalè collegato al negozio in modo da poter visualizzare il titolo del post e funziona come qualsiasi componente collegato: si può inviare azioni, tra cui hideModalquando è necessario nascondersi.

Estrazione di un componente di presentazione

Sarebbe scomodo copiare e incollare la stessa logica di layout per ogni modale "specifico". Ma hai dei componenti, giusto? Quindi puoi estrarre una presentazione <Modal> componente di che non sa cosa fanno i particolari modali, ma gestisce il loro aspetto.

Quindi, modali specifici come DeletePostModalpossono usarlo per il 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)

Sta a te <Modal>inventare una serie di oggetti di scena che possono accettare nella tua applicazione, ma immagino che potresti avere diversi tipi di modali (ad es. Informazioni modali, conferma modali, ecc.) E diversi stili per loro.

Accessibilità e nascondersi quando fai clic su Chiave esterna o Esc

L'ultima parte importante delle modali è che generalmente vogliamo nasconderle quando l'utente fa clic all'esterno o preme Esc.

Invece di darti consigli su come implementarlo, ti suggerisco di non implementarlo da solo. È difficile capire bene l'accessibilità.

Invece, ti suggerirei di utilizzare un componente modale standard accessibile come react-modal. È completamente personalizzabile, puoi inserire tutto ciò che vuoi al suo interno, ma gestisce l'accessibilità in modo che i non vedenti possano ancora usare il tuo modale.

Puoi anche includere il react-modaltuo <Modal>che accetta oggetti di scena specifici per le tue applicazioni e genera pulsanti figlio o altri contenuti. Sono solo componenti!

Altri approcci

C'è più di un modo per farlo.

Ad alcune persone non piace la verbosità di questo approccio e preferiscono avere un <Modal>componente che possono renderizzare direttamente all'interno dei loro componenti con una tecnica chiamata "portali". I portali ti consentono di eseguire il rendering di un componente all'interno del tuo mentre in realtà verrà eseguito il rendering in un punto predeterminato nel DOM, il che è molto conveniente per i modali.

In effetti, che react-modalho già collegato precedentemente, lo fa internamente, quindi tecnicamente non è nemmeno necessario renderlo dall'alto. Trovo ancora bello disaccoppiare il modale che voglio mostrare dal componente mostrandolo, ma puoi anche usare react-modaldirettamente dai tuoi componenti e saltare la maggior parte di ciò che ho scritto sopra.

Ti incoraggio a prendere in considerazione entrambi gli approcci, a sperimentarli e a scegliere ciò che ritieni più adatto alla tua app e al tuo team.


35
Una cosa che suggerirei è che il riduttore mantenga un elenco di modali che possono essere spinti e spuntati. Per quanto sembri sciocco, mi sono imbattuto costantemente in situazioni in cui designer / tipi di prodotti vogliono che io apra un modale da un modale ed è bello consentire agli utenti di "tornare indietro".
Kyle,

9
Sì, sicuramente, questo è il genere di cose che Redux rende facile da costruire perché puoi semplicemente cambiare il tuo stato in un array. Personalmente ho lavorato con designer che, al contrario, volevano che i modali fossero esclusivi, quindi l'approccio che ho scritto risolve la nidificazione accidentale. Ma sì, puoi averlo in entrambi i modi.
Dan Abramov,

4
Nella mia esperienza direi: se modale è correlato a un componente locale (come una conferma di cancellazione modale è correlato al pulsante Elimina), è più semplice utilizzare un portale, altrimenti utilizzare azioni di redux. D'accordo con @Kyle si dovrebbe essere in grado di aprire un modale da un modale. Funziona anche di default con i portali perché vengono aggiunti per documentare il corpo in modo che i portali si impilino l'uno sull'altro (fino a quando non si incasina tutto con l'indice z: p)
Sebastien Lorber,

4
@DanAbramov, la tua soluzione è fantastica, ma ho un piccolo problema. Nulla di serio. Uso Material-ui nel progetto, quando si chiude modale lo spegne, invece di "riprodurre" l'animazione di dissolvenza. Probabilmente devi fare una sorta di ritardo? O mantenere tutti i modali lì come un elenco all'interno di ModalRoot? Suggerimenti?
Gcerar,

7
A volte desidero chiamare determinate funzioni dopo la chiusura modale (ad esempio chiamare le funzioni con i valori del campo di input all'interno del modale). Passerei queste funzioni modalPropsall'azione. Ciò viola la regola di mantenere lo stato serializzabile però. Come posso superare questo problema?
Chmanie,

98

Aggiornamento : React 16.0 ha introdotto i portali tramite ReactDOM.createPortal link

Aggiornamento : le prossime versioni di React (Fibre: probabilmente 16 o 17) includeranno un metodo per creare portali: ReactDOM.unstable_createPortal() link


Usa portali

Dan Abramov risponde che la prima parte va bene, ma comporta molta piastra di cottura. Come ha detto, puoi anche usare i portali. Espanderò un po 'quell'idea.

Il vantaggio di un portale è che il popup e il pulsante rimangono molto vicini nell'albero di React, con una comunicazione genitore / figlio molto semplice tramite i puntelli: puoi facilmente gestire le azioni asincrone con i portali o lasciare che il genitore personalizzi il portale.

Cos'è un portale?

Un portale ti consente di renderizzare direttamente all'interno di document.bodyun elemento che è profondamente annidato nel tuo albero di React.

L'idea è che, ad esempio, rendi nel corpo il seguente albero di React:

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

E ottieni come output:

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

Il inside-portalnodo è stato tradotto all'interno <body>, al posto del suo normale posto profondamente annidato.

Quando utilizzare un portale

Un portale è particolarmente utile per visualizzare elementi che dovrebbero andare in cima ai componenti React esistenti: popup, menu a discesa, suggerimenti, hotspot

Perché usare un portale

Nessun problema con z-index : un portale ti permette di renderizzare <body>. Se vuoi visualizzare un popup o un menu a discesa, questa è davvero una bella idea se non vuoi combattere contro i problemi di z-index. Gli elementi del portale vengono aggiunti document.bodyin ordine di montaggio, il che significa che, a meno che non si giochi z-index, il comportamento predefinito sarà quello di impilare i portali uno sopra l'altro, in ordine di montaggio. In pratica, significa che puoi aprire in sicurezza un popup dall'interno di un altro popup ed essere sicuro che il secondo popup verrà visualizzato in cima al primo, senza nemmeno pensarci z-index.

In pratica

Più semplice: usa lo stato React locale: se pensi, per un semplice popup di conferma dell'eliminazione, non vale la pena avere la caldaia Redux, allora puoi usare un portale e semplifica notevolmente il tuo codice. Per un tale caso d'uso, in cui l'interazione è molto locale ed è in realtà un dettaglio di implementazione, ti interessa davvero la ricarica a caldo, i viaggi nel tempo, la registrazione delle azioni e tutti i vantaggi offerti da Redux? Personalmente, non lo faccio e uso lo stato locale in questo caso. Il codice diventa semplice come:

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

Semplice: puoi ancora usare lo stato Redux : se vuoi davvero, puoi ancora usare connectper scegliere se DeleteConfirmationPopupmostrare o meno. Dato che il portale rimane profondamente annidato nella tua struttura di React, è molto semplice personalizzare il comportamento di questo portale perché i tuoi genitori possono passare oggetti di scena al portale. Se non usi portali, di solito devi renderizzare i tuoi popup nella parte superiore dell'albero React perz-indexmotivi e di solito devono pensare a cose come "come posso personalizzare il DeleteConfirmationPopup generico che ho costruito in base al caso d'uso". E di solito troverai soluzioni piuttosto confuse a questo problema, come l'invio di un'azione che contiene azioni di conferma / annullamento nidificate, una chiave di bundle di traduzione o, peggio ancora, una funzione di rendering (o qualcos'altro non serializzabile). Non devi farlo con i portali e puoi semplicemente passare oggetti di scena regolari, poiché DeleteConfirmationPopupè solo un figlio diDeleteButton

Conclusione

I portali sono molto utili per semplificare il tuo codice. Non potrei più farne a meno.

Si noti che le implementazioni del portale possono anche aiutarti con altre utili funzioni come:

  • Accessibilità
  • Scorciatoie di spazio per chiudere il portale
  • Gestisci clic esterno (chiudi il portale o no)
  • Gestisci clic sul collegamento (chiudi il portale o no)
  • React Context reso disponibile nella struttura del portale

reagire-portale o reagire-modale sono utili per popup, modali e overlay che dovrebbero essere a schermo intero, generalmente centrati al centro dello schermo.

Reagire -tether è sconosciuto alla maggior parte degli sviluppatori di React, ma è uno degli strumenti più utili che puoi trovare lì. Tether ti consente di creare portali, ma posizionerà automaticamente il portale, rispetto a un determinato target. Questo è perfetto per tooltip, menu a discesa, hotspot, caselle di aiuto ... Se hai mai avuto problemi con la posizione absolute/ relativee z-index, o il tuo menu a discesa non rientra nella visualizzazione, Tether risolverà tutto ciò per te.

Ad esempio, è possibile implementare facilmente hotspot onboard, che si espande in una descrizione comandi dopo aver fatto clic:

Hotspot integrato

Codice di produzione reale qui. Non può essere più semplice :)

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

Modifica : appena scoperto reag-gateway che consente di eseguire il rendering dei portali nel nodo di tua scelta (non necessariamente body)

Modifica : sembra che il reattivo possa essere un'alternativa decente al reagente. PopperJS è una libreria che calcola solo una posizione appropriata per un elemento, senza toccare direttamente il DOM, consentendo all'utente di scegliere dove e quando desidera posizionare il nodo DOM, mentre Tether accoda direttamente al corpo.

Modifica : c'è anche reattivo-slot-riempimento che è interessante e può aiutare a risolvere problemi simili consentendo di rendere un elemento in uno slot di elemento riservato che metti dove vuoi nel tuo albero


Nel tuo frammento di esempio il popup di conferma non si chiuderà se confermi l'azione (al contrario di quando fai clic su Annulla)
dKab

Sarebbe utile includere l'importazione del portale nello snippet di codice. Da quale biblioteca <Portal>proviene? Immagino sia il portale di reazione, ma sarebbe bello saperlo con certezza.
pietra

1
@skypecakes considera le mie implementazioni come pseudo-codice. Non l'ho provato su nessuna libreria concreta. Cerco solo di insegnare il concetto qui non un'implementazione concreta. Sono abituato a reagire-portale e il codice sopra dovrebbe funzionare bene con esso, ma dovrebbe funzionare bene con quasi ogni lib simile.
Sebastien Lorber,

React-Gateway è fantastico! Supporta il rendering lato server :)
Cyrilluce,

Sono abbastanza principiante, quindi sarò molto felice per qualche spiegazione su questo approccio. Anche se esegui il rendering del modale in un altro posto, in questo approccio dovrai controllare ogni pulsante di eliminazione se dovresti eseguire il rendering dell'istanza specifica del modale. Nell'approccio redux ho solo un'istanza del modale che viene mostrata o meno. Non è una preoccupazione per le prestazioni?
Amit Neuhaus,

9

Molte buone soluzioni e preziosi commenti di esperti noti della comunità JS sull'argomento possono essere trovati qui. Potrebbe essere un indicatore del fatto che non è quel problema banale come potrebbe sembrare. Penso che questo sia il motivo per cui potrebbe essere fonte di dubbi e incertezze sulla questione.

Il problema fondamentale qui è che in React ti è permesso solo montare componenti sul suo genitore, che non è sempre il comportamento desiderato. Ma come affrontare questo problema?

Propongo la soluzione, indirizzata a risolvere questo problema. Definizione del problema, src ed esempi più dettagliati sono disponibili qui: https://github.com/fckt/react-layer-stack#rationale

Fondamento logico

react/ react-domviene fornito con 2 ipotesi / idee di base:

  • ogni interfaccia utente è gerarchica naturalmente. Questo è il motivo per cui abbiamo l'idea di componentsquale avvolgerci l'un l'altro
  • react-dom monta (fisicamente) il componente figlio sul nodo DOM padre per impostazione predefinita

Il problema è che a volte la seconda proprietà non è quella che desideri nel tuo caso. A volte si desidera montare il componente in un nodo DOM fisico diverso e mantenere contemporaneamente una connessione logica tra padre e figlio.

L'esempio canonico è il componente simile a Tooltip: ad un certo punto del processo di sviluppo potresti scoprire che devi aggiungere una descrizione per il tuo UI element: verrà visualizzato in un livello fisso e dovrebbe conoscere le sue coordinate (che sono quel UI elementcoord o coord del mouse) e at allo stesso tempo ha bisogno di informazioni se deve essere mostrato in questo momento o meno, il suo contenuto e un certo contesto dai componenti principali. Questo esempio mostra che a volte la gerarchia logica non corrisponde alla gerarchia DOM fisica.

Dai un'occhiata a https://github.com/fckt/react-layer-stack/blob/master/README.md#real-world-usage-example per vedere l'esempio concreto che è la risposta alla tua domanda:

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>)
// ...

2

Secondo me l'implementazione minima nuda ha due requisiti. Uno stato che tiene traccia dell'apertura o meno del modale e un portale per rendere il modale esterno all'albero di reazione standard.

Il componente ModalContainer di seguito implementa tali requisiti insieme alle corrispondenti funzioni di rendering per il modale e il trigger, che è responsabile dell'esecuzione del callback per aprire il modale.

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;

Ed ecco un semplice caso d'uso ...

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;

Uso le funzioni di rendering, perché desidero isolare la gestione dello stato e la logica boilerplate dall'implementazione del componente modale e trigger renderizzato. Ciò consente ai componenti renderizzati di essere qualunque cosa tu voglia che siano. Nel tuo caso, suppongo che il componente modale potrebbe essere un componente collegato che riceve una funzione di callback che invia un'azione asincrona.

Se devi inviare oggetti di scena dinamici al componente modale dal componente trigger, che si spera non accada troppo spesso, ti consiglio di avvolgere ModalContainer con un componente contenitore che gestisce gli oggetti di scena dinamici nel suo stato e migliora i metodi di rendering originali come così.

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;

0

Avvolgi il modale in un contenitore collegato ed esegui l'operazione asincrona qui. In questo modo è possibile raggiungere sia l'invio per attivare azioni sia il puntello onClose. Per raggiungere dispatchdagli oggetti di scena, non passare la mapDispatchToPropsfunzione a 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'app in cui viene visualizzato il modale e viene impostato il suo stato di visibilità:

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

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

  ...

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

}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.