Как я могу отобразить модальный диалог в Redux, который выполняет асинхронные действия?

Я создаю приложение, которое в некоторых ситуациях должно отображать диалоговое окно подтверждения.

Скажем, я хочу что-то удалить, а затем отправлю действие типа deleteSomething(id), чтобы какой-нибудь редуктор перехватил это событие и заполнил диалоговое окно reducer, чтобы показать его.

Мое сомнение приходит, когда этот диалог подчиняется.

  • Как этот компонент может отправить правильное действие в соответствии с первым отправленным действием?
  • Должен ли создатель действия обрабатывать эту логику?
  • Можем ли мы добавить действия внутри редуктора?

редактировать:

чтобы было понятнее:

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

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

Поэтому я пытаюсь повторно использовать компонент диалога. Отображение / скрытие диалогового окна - это не проблема, так как это можно легко сделать в редукторе. Я пытаюсь указать, как отправить действие с правой стороны в соответствии с действием, которое запускает поток с левой стороны.


person carlesba    schedule 25.02.2016    source источник
comment
Я думаю, что в вашем случае состояние диалога (скрыть / показать) является локальным. Я бы предпочел использовать состояние реакции для управления отображением / скрытием диалогов. Таким образом отпадет вопрос о правильном действии согласно первому действию.   -  person Ming    schedule 05.09.2016


Ответы (5)


Обновление: в React 16.0 появились порталы через ReactDOM.createPortal ссылка

Обновление: следующие версии React (Fiber: вероятно, 16 или 17) будут включать метод создания порталов: ReactDOM.unstable_createPortal() ссылка


Используйте порталы

Дэн Абрамов ответил, что первая часть хороша, но включает в себя много шаблонов. По его словам, вы также можете использовать порталы. Я немного расширю эту идею.

Преимущество портала в том, что всплывающее окно и кнопка остаются очень близко к дереву React, с очень простой связью между родительскими и дочерними элементами с использованием свойств: вы можете легко обрабатывать асинхронные действия с порталами или позволить родителю настроить портал.

Что такое портал?

Портал позволяет отображать прямо внутри document.body элемент, глубоко вложенный в ваше дерево React.

Идея состоит в том, что, например, вы визуализируете в теле следующее дерево React:

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

И вы получите на выходе:

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

Узел inside-portal был переведен внутрь <body> вместо своего обычного, глубоко вложенного места.

Когда использовать портал

Портал особенно полезен для отображения элементов, которые должны располагаться поверх существующих компонентов React: всплывающих окон, раскрывающихся списков, предложений, горячих точек.

Зачем использовать портал

Больше нет проблем с z-индексом: портал позволяет выполнять рендеринг в <body>. Если вы хотите отображать всплывающее или раскрывающееся меню, это действительно хорошая идея, если вы не хотите бороться с проблемами z-index. Элементы портала добавляются document.body в порядке монтирования, что означает, что, если вы не играете с z-index, по умолчанию порталы будут располагаться друг над другом в порядке монтирования. На практике это означает, что вы можете безопасно открывать всплывающее окно из другого всплывающего окна и быть уверенным, что второе всплывающее окно будет отображаться поверх первого, даже не думая о z-index.

На практике

Самое простое: используйте локальное состояние React: если вы думаете, что для простого всплывающего окна подтверждения удаления не стоит иметь шаблон Redux, вы можете использовать портал, и это значительно упростит ваш код. Для такого случая использования, когда взаимодействие очень локально и на самом деле является довольно деталью реализации, действительно ли вы заботитесь о горячей перезагрузке, перемещении во времени, ведении журнала действий и всех преимуществах, которые дает Redux? Лично я этого не делаю и использую в этом случае локальное состояние. Код становится таким простым, как:

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

Просто: вы все еще можете использовать состояние Redux: если вы действительно хотите, вы все равно можете использовать connect, чтобы выбрать, будет ли DeleteConfirmationPopup отображаться или нет. Поскольку портал остается глубоко вложенным в ваше дерево React, очень просто настроить поведение этого портала, потому что ваш родитель может передавать реквизиты на портал. Если вы не используете порталы, вам обычно приходится отображать всплывающие окна в верхней части дерева React по z-index причинам и обычно нужно думать о таких вещах, как «как мне настроить общий DeleteConfirmationPopup, который я создал в соответствии с вариантом использования» . И обычно вы найдете довольно хакерские решения этой проблемы, такие как отправка действия, которое содержит вложенные действия подтверждения / отмены, ключ пакета перевода или, что еще хуже, функцию рендеринга (или что-то еще несериализуемое). Вам не обязательно делать это с порталами, вы можете просто передавать обычные реквизиты, поскольку DeleteConfirmationPopup - это всего лишь дочерний элемент DeleteButton

Заключение

Порталы очень полезны для упрощения вашего кода. Я больше не мог без них обходиться.

Обратите внимание, что реализации портала также могут помочь вам с другими полезными функциями, такими как:

  • Доступность
  • Ярлыки Espace для закрытия портала
  • Обработка внешнего щелчка (закрыть портал или нет)
  • Обработка щелчка по ссылке (закрыть портал или нет)
  • Контекст React доступен в дереве портала

response-portal или react-modal удобен для всплывающих окон, модальных окон и оверлеев, которые должны быть полноэкранными, как правило, по центру экрана.

react-tether неизвестен большинству разработчиков React, но это один из самых полезных инструментов, которые вы можете найти там. Tether позволяет создавать порталы, но автоматически позиционирует портал относительно заданной цели. Это идеально подходит для всплывающих подсказок, раскрывающихся списков, горячих точек, справочных ящиков ... Если у вас когда-либо были проблемы с позицией _18 _ / _ 19_ и z-index или раскрывающимся списком, выходящим за пределы области просмотра, Tether решит все это за вас.

Вы можете, например, легко реализовать интерактивные горячие точки, которые при нажатии расширяются до всплывающей подсказки:

Точка доступа

Настоящий производственный код здесь. Нет ничего проще :)

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

Изменить: только что обнаружил реактивный шлюз, который позволяет отображать порталы в узел по вашему выбору (не обязательно тело)

Изменить: кажется, что react-popper может быть достойной альтернативой для реагирования. -tether. PopperJS - это библиотека, которая только вычисляет подходящую позицию для элемента, не касаясь напрямую DOM, позволяя пользователю выбирать, где и когда он хочет разместить узел DOM, в то время как Tether присоединяется непосредственно к телу.

Изменить: есть также интересный react-slot-fill и может помочь решить аналогичные проблемы, позволяя отображать элемент в зарезервированный слот элемента, который вы помещаете в любое место в своем дереве.

person Sebastien Lorber    schedule 03.10.2016
comment
В вашем примере фрагмента всплывающее окно подтверждения не закроется, если вы подтвердите действие (в отличие от того, когда вы нажимаете Отмена) - person dKab; 29.12.2016
comment
Было бы полезно включить импорт вашего портала во фрагмент кода. Из какой библиотеки <Portal>? Я предполагаю, что это реактивный портал, но было бы неплохо узнать наверняка. - person stone; 15.03.2017
comment
@skypecakes, пожалуйста, рассматривайте мои реализации как псевдокод. Я не тестировал его с какой-либо конкретной библиотекой. Я просто пытаюсь преподать здесь концепцию, а не конкретную реализацию. Я привык к response-portal, и приведенный выше код должен нормально работать с ним, но он должен нормально работать практически с любой подобной библиотекой. - person Sebastien Lorber; 15.03.2017
comment
react-gateway потрясающий! Он поддерживает рендеринг на стороне сервера :) - person cyrilluce; 17.06.2017
comment
Я довольно новичок, поэтому буду очень рад некоторым объяснениям этого подхода. Даже если вы действительно визуализируете модальное окно в другом месте, при таком подходе вам придется проверять каждую кнопку удаления, если вы должны визуализировать конкретный экземпляр модального окна. В подходе redux у меня есть только один экземпляр модального окна, который отображается или нет. Разве это не проблема производительности? - person Amit Neuhaus; 11.06.2020

Подход, который я предлагаю, немного многословен, но я обнаружил, что он довольно хорошо масштабируется в сложных приложениях. Если вы хотите показать модальное окно, запустите действие, описывающее какой модальный файл вы хотите видеть:

Отправка действия для отображения модального окна

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

(Строки, конечно, могут быть константами; для простоты я использую встроенные строки.)

Написание редуктора для управления модальным состоянием

Затем убедитесь, что у вас есть редуктор, который принимает эти значения:

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

Большой! Теперь, когда вы отправляете действие, state.modal будет обновляться, чтобы включать информацию о текущем видимом модальном окне.

Написание корневого модального компонента

В корень иерархии компонентов добавьте <ModalRoot> компонент, подключенный к хранилищу Redux. Он будет слушать state.modal и отображать соответствующий модальный компонент, перенаправляя реквизиты из 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)

Что мы здесь сделали? ModalRoot считывает текущие modalType и modalProps из state.modal, к которому он подключен, и отображает соответствующий компонент, такой как DeletePostModal или ConfirmLogoutModal. Каждое модальное окно - это компонент!

Написание конкретных модальных компонентов

Здесь нет общих правил. Это просто компоненты React, которые могут отправлять действия, считывать что-то из состояния хранилища, и просто модальные.

Например, DeletePostModal может выглядеть так:

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)

DeletePostModal подключен к магазину, поэтому он может отображать заголовок сообщения и работает как любой подключенный компонент: он может отправлять действия, в том числе hideModal, когда необходимо скрыть себя.

Извлечение презентационного компонента

Было бы неудобно копировать и вставлять одну и ту же логику макета для каждого «конкретного» модального окна. Но ведь у вас ведь есть компоненты? Таким образом, вы можете извлечь презентационный <Modal> компонент, который не знает, что определенные модальные окна работают, но обрабатывают то, как они выглядят.

Затем определенные модальные окна, такие как DeletePostModal, могут использовать его для рендеринга:

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)

Вы должны придумать набор свойств, которые <Modal> может принимать в вашем приложении, но я могу предположить, что у вас может быть несколько видов модальных окон (например, модальное окно информации, модальное окно подтверждения и т. Д.) И несколько стилей для них.

Доступность и скрытие при нажатии снаружи или клавиши выхода

Последняя важная часть о модальных окнах заключается в том, что обычно мы хотим скрыть их, когда пользователь щелкает снаружи или нажимает Escape.

Вместо того, чтобы давать вам советы по реализации этого, я предлагаю вам просто не реализовывать это самостоятельно. Это сложно сделать, учитывая доступность.

Вместо этого я бы посоветовал вам использовать готовый доступный модальный компонент, такой как react-modal. Он полностью настраиваемый, вы можете поместить в него все, что захотите, но он правильно обрабатывает доступность, чтобы слепые люди по-прежнему могли использовать ваш модальный.

Вы даже можете заключить react-modal в свой собственный <Modal>, который принимает свойства, специфичные для ваших приложений, и генерирует дочерние кнопки или другой контент. Это всего лишь компоненты!

Другие подходы

Есть несколько способов сделать это.

Некоторым людям не нравится многословие этого подхода, и они предпочитают иметь <Modal> компонент, который они могут отображать прямо внутри своих компонентов с помощью техники, называемой «порталами». Порталы позволяют визуализировать компонент внутри вашего, в то время как на самом деле он будет отображаться в заранее определенном месте в DOM, что очень удобно для модальных окон.

Фактически, react-modal, на который я ссылался ранее, уже делает это внутренне, поэтому технически вам даже не нужно рендерить это сверху. Мне по-прежнему приятно отделить модальное окно, которое я хочу показать, от компонента, показывающего его, но вы также можете использовать react-modal прямо из ваших компонентов и пропустить большую часть того, что я написал выше.

Я призываю вас рассмотреть оба подхода, поэкспериментировать с ними и выбрать то, что, по вашему мнению, лучше всего подходит для вашего приложения и для вашей команды.

person Dan Abramov    schedule 26.02.2016
comment
Одна вещь, которую я предлагаю, - это чтобы редуктор поддерживал список модальных окон, которые можно нажимать и выталкивать. Как бы глупо это ни звучало, я постоянно сталкивался с ситуациями, когда дизайнеры / типы продуктов хотят, чтобы я открывал модальное окно из модального, и было бы приятно позволить пользователям вернуться. - person Kyle; 26.02.2016
comment
Да, определенно, Redux упрощает создание таких вещей, потому что вы можете просто изменить свое состояние на массив. Лично я работал с дизайнерами, которые, наоборот, хотели, чтобы модальные окна были эксклюзивными, поэтому описанный мной подход решает проблему случайного вложения. Но да, у вас могут быть оба варианта. - person Dan Abramov; 26.02.2016
comment
По своему опыту я бы сказал: если модальное окно связано с локальным компонентом (например, модальное окно подтверждения удаления связано с кнопкой удаления), проще использовать портал, иначе используйте действия redux. Согласитесь с @Kyle, нужно иметь возможность открывать модальное окно из модального. Он также работает по умолчанию с порталами, потому что они добавляются для документирования тела, поэтому порталы хорошо складываются друг на друга (пока вы не испортите все с z-index: p) - person Sebastien Lorber; 26.02.2016
comment
@DanAbramov, ваше решение отличное, но у меня небольшая проблема. Ничего особенного. Я использую Material-ui в проекте, при закрытии модального окна он просто отключает его, вместо того, чтобы воспроизводить исчезающую анимацию. Наверное, нужно сделать какую-то задержку? Или сохранить все модальные окна в виде списка внутри ModalRoot? Предложения? - person gcerar; 07.03.2016
comment
@ adam-sanderson: Видел ваше предложение по редактированию, но вы можете подумать о том, чтобы спросить отвечающего о его редактировании. - person sjsam; 24.04.2016
comment
@DanAbramov Я сделал что-то в этом роде, но я не уверен, что это способ React / Redux ... где был бы хороший форум, чтобы быстро обсудить это? - person The Bearded Llama; 22.07.2016
comment
Спасибо, что нашли время написать этот отличный ответ! +1 :) - person Philipp; 11.08.2016
comment
Иногда я хочу вызвать определенные функции после закрытия модального окна (например, вызвать функции со значениями поля ввода внутри модального окна). Я бы передал эти функции как modalProps в действие. Однако это нарушает правило сохранения сериализуемости состояния. Как я могу решить эту проблему? - person chmanie; 09.10.2016
comment
Есть ли способ передать функцию из родительского контейнера / компонента, которая может быть вызвана после закрытия модального окна? Я могу вызывать действия из модального окна при нажатии кнопки, но мне нужен обратный вызов при закрытии модального окна, что позволит мне выполнить функцию на родительском элементе. Родительские загрузки - ›нажатие кнопки отправляет модальное окно -› при подтверждении в модальном режиме отправляет действие, а затем отправляет hideModal - ›вызывается родительская функция в контейнере, и если подтверждение прошло успешно, страница перенаправляется. - person Brett Mathe; 17.10.2016
comment
ОБНОВЛЕНИЕ: похоже, что функции можно передавать в реквизитах. Итак, теперь мое модальное окно при подтверждении будет ложно проверять переданную функцию, а затем вызывает ее после успешного сохранения. - person Brett Mathe; 17.10.2016
comment
@gcerar У меня такая же проблема. Вы нашли какое-нибудь решение? - person Yoihito; 24.10.2016
comment
@Yoihito Я посмотрел источник MUI, и анимация выполняется примерно за 250 мс. Я сделал хакерский путь и представил действие redux с помощью setTimeout. Лучший способ сохранить эту работу - назначить идентификатор для каждого модального окна. Чтобы случайно не удалить слишком много или первое в массиве. - person gcerar; 24.10.2016
comment
Мои 50 центов до Other Approaches раздел: github.com/fckt/react-layer-stack - это комплексное решение для такого рода проблем и всего, что между ними. - person fckt; 07.11.2016
comment
Извините, не мог бы кто-нибудь объяснить, откуда берется dispatch в приведенном выше фрагменте кода deletePostModal? Почему это должно быть в объекте props, когда мы передаем туда только свойство post? - person dKab; 29.12.2016
comment
Я разработал подробное, измененное объяснение этого решения здесь: codersmind.com/scalable-modals -react-redux с демонстрационным репо. - person randomKek; 17.02.2017
comment
@DanAbramov, что делать, если действие в модальном окне должно закрыть текущий модальный и открыть другой? Отправлять два действия или одно действие, которое выполняет оба действия? (например, openOnlyModal () вместо closeModal () и openModal ()) - person Aurimas; 23.04.2017
comment
@DanAbramov Выглядит отлично. но что, если вам нужен общий модальный файл, который выполняет действие и отправляет его? например: пользователь внес некоторые изменения в некоторые входные данные, теперь пользователь хочет выполнить действие, которое может изменить состояние, но он потеряет внесенные им изменения. вы хотите открыть диалоговое окно подтверждения. Вы уверены, что не хотите сохранять… Теперь проблема в том, что действие является динамическим (кнопка обновления, переход по маршруту и ​​т. д.), если пользователь нажимает «да» 'тогда нам нужно отправить это действие, если он нажмет «отменить», нам нужно просто скрыть модальное окно. Так можно ли передавать действие в диалог? - person Sagiv b.g; 27.07.2017
comment
@DanAbramov Получал ли когда-нибудь ответ @dKab? Я также смущен тем, как мы внезапно передаем dispatch в DeletePostModal, но ModalRoot - это тот, кто передает ему реквизиты, нет? - person Azeli; 14.10.2017
comment
Один из лучших ответов в SO! (упс, просто обратите внимание на это Дэн Абрамов) - Аааа Неплохой Дэн! - person chenop; 13.11.2017
comment
@dKab функция connect () - это та функция, которая отправляет отправку в реквизиты - возможно, вы удалили соединение. - person chenop; 14.11.2017
comment
@ Sag1v Вы можете предоставить обратные вызовы (например, handleConfirm) как свойства modalProps, которые затем будут отправлять новые действия для обновления, маршрутизации и т. Д. - person colti; 01.12.2017
comment
Но я читал, что вы не должны хранить функции внутри магазина. Как я могу управлять обратными вызовами, например, onExited и т. Д.? - person Jan Ciołek; 19.12.2017
comment
Я застрял в той же проблеме. Если мне нужен общий диалог подтверждения, функция, запускающая диалог, должна иметь возможность определять, что происходит, когда он закрывается. Определение обработчика onClose в компоненте диалога делает это невозможным. И передавать обратный вызов в действии Redux неправильно, поскольку он не сериализуемый. Как люди это делают? - person JW.; 27.03.2018
comment
@ JanCiołek Я вижу вашу проблему. У меня нет легкого способа сделать это, но это возможно. Вы можете передать некоторую опору в modalProps, которая имеет некоторый уникальный uuid текущего отправленного действия, который затем сохраняется в чем-то вроде state.modals.open = [id], а затем имеет вашу модальную отправку действие, которое удалит этот id из хранилища redux при выходе {type: 'HIDE_MODAL', id} - или какой-либо другой действие, которое вы слушаете. Затем вы можете передать removedModals в качестве опоры в свой компонент и инициировать обратный вызов, когда модальный идентификатор совпадает с тем, который вы инициировали. - person oligofren; 22.10.2018
comment
@JW. В продолжение этого комментария, возможно, это просто намек на то, что компонент-инициатор не должен полагаться на знание того, что модальное окно завершено, вместо этого полагаясь на вычисление его состояния из других свойств в магазине. Во всяком случае, это кажется возможным. - person oligofren; 22.10.2018
comment
Вот почему я слежу за этими легендами (например, @Dan Ambrew) везде, где они есть. - person Manish Jangir; 27.10.2018
comment
Реквизиты, переданные в модальное окно, хранятся в Магазине. Можно ли передавать обратные вызовы в качестве реквизита или это плохая идея? - person user1283776; 12.04.2019

Здесь можно найти множество хороших решений и ценные комментарии известных экспертов из JS-сообщества по данной теме. Это может быть признаком того, что это не такая уж тривиальная проблема, как может показаться. Думаю, именно поэтому это может быть источником сомнений и неуверенности в этом вопросе.

Основная проблема здесь в том, что в React вам разрешено монтировать компонент только к его родительскому элементу, что не всегда является желаемым поведением. Но как решить эту проблему?

Предлагаю решение, направленное на устранение этой проблемы. Более подробное определение проблемы, src и примеры можно найти здесь: https://github.com/fckt/react-layer-stack#rationale

Обоснование

_1 _ / _ 2_ приходит с двумя основными предположениями / идеями:

  • каждый пользовательский интерфейс естественно иерархичен. Вот почему у нас есть идея components, которые обертывают друг друга
  • react-dom по умолчанию подключает (физически) дочерний компонент к его родительскому узлу DOM

Проблема в том, что иногда второе свойство не то, что вам нужно в вашем случае. Иногда вы хотите смонтировать свой компонент в другой физический узел DOM и одновременно поддерживать логическое соединение между родительским и дочерним элементами.

Каноническим примером является компонент, похожий на всплывающую подсказку: в какой-то момент процесса разработки вы можете обнаружить, что вам нужно добавить некоторое описание для вашего UI element: он будет отображаться в фиксированном слое и должен знать его координаты (которые представляют собой координаты UI element или координаты мыши) и в то же время ему нужна информация, нужно ли его показывать прямо сейчас или нет, его содержимое и некоторый контекст из родительских компонентов. Этот пример показывает, что иногда логическая иерархия не совпадает с физической иерархией DOM.

Взгляните на https://github.com/fckt/react-layer-stack/blob/master/README.md#real-world-usage-example, чтобы увидеть конкретный пример, отвечающий на ваш вопрос:

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>)
// ...
person fckt    schedule 07.11.2016

На мой взгляд, минимальная реализация требует двух требований. Состояние, которое отслеживает, открыто модальное окно или нет, и портал для рендеринга модального окна вне стандартного дерева реакции.

Компонент ModalContainer ниже реализует эти требования вместе с соответствующими функциями рендеринга для модального окна и триггера, который отвечает за выполнение обратного вызова для открытия модального окна.

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;

А вот простой вариант использования ...

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;

Я использую функции рендеринга, потому что хочу изолировать управление состоянием и шаблонную логику от реализации рендеринга модального и триггерного компонентов. Это позволяет визуализированным компонентам быть такими, какими вы хотите их видеть. В вашем случае я полагаю, что модальный компонент может быть подключенным компонентом, который получает функцию обратного вызова, которая отправляет асинхронное действие.

Если вам нужно отправить динамические реквизиты модальному компоненту из триггерного компонента, что, надеюсь, случается не слишком часто, я рекомендую обернуть ModalContainer контейнерным компонентом, который управляет динамическими реквизитами в своем собственном состоянии и улучшить исходные методы рендеринга, такие как так.

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;
person kskkido    schedule 28.11.2018

Оберните модальное окно в подключенный контейнер и выполните здесь асинхронную операцию. Таким образом, вы можете связаться как с диспетчером для запуска действий, так и с опорой onClose. Чтобы получить dispatch из реквизита, не передавайте функцию mapDispatchToProps в 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);

Приложение, в котором отображается модальное окно и устанавливается его состояние видимости:

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

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

  ...

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

}
person gazdagergo    schedule 24.01.2019