Как сделать модальное окно react

Обновлено: 04.05.2024

What’s the “react” way to trigger a modal when a button is clicked?

If you come from Angular, jQuery, or even just vanilla JS, your thought process for opening a modal dialog probably goes something like this:

  1. I need to open a modal.
  2. I’ll just call the modal function, which opens it up.
  3. Then the modal will wait for its “Close” button to be clicked.
  4. When “Close” is clicked I’ll call another function to close it.

But with React it’s like:

  1. I need to open a modal.
  2. What? How do I even

The root of the problem is this: How do you make something appear onscreen, in response to an event, in this new world where all you have is props and state? How can you make something happen?

A Modal

Let’s get right to it. I’ll just straight up give you a Modal component, and then we’ll walk through it:

This component is at least 50% inline styles by volume. I almost left them out, but I decided not to because having them really gives it the desired effect – the modal sits on top of a gray background that obscures everything behind it, and all you can do is click that Close button. If you try out this code you’ll see what I mean.

Modal in action

How It Works

The most important parts here are the first few lines, and the onClick handler.

This bit here is responsible for “showing” or “hiding” the modal:

Rather, it is either rendering the modal (when show is true) or nothing (when show is false).

Contrast this to jQuery where you might show and hide an element by toggling a CSS class, or maybe adding and removing it from the DOM.

The React way is different. There is no manual adding or removing of anything. Instead, it’s declarative. Pass show= to the Modal and it’s rendered. Pass show= and it isn’t.

So how, then, do you actually change that true/false value for show ? How could you do it in response to a button click? It is actually up to the parent component – the “user” of Modal . Here is such a component:

When the App component first renders, its isOpen state is false, so the Modal is not rendered.

Then when the user clicks the “Open the modal” button, it calls toggleModal which flips that flag to true.

The setState call triggers a re-render, and now Modal gets passed show= , so it appears.

Now what about closing it?

Notice we’re passing toggleModal as the onClose handler:

Look back at the code for Modal and notice how the button calls the onClose prop when it’s clicked:

So that’s what’s happening: when the “Close” button is clicked, it calls the onClose prop – which is, in fact, the toggleModal function in App . That function flips the isOpen flag, which triggers a re-render, and the modal disappears. It’s truly gone, too: try a right-click “Inspect Element” while the modal is closed and you will notice the modal is nowhere to be found in the DOM.

This might be a bit mind-bending at first, but just do it a few times and it’ll become second nature.

Get the Code

Download a working example project on GitHub.

Learning React can be a struggle — so many libraries and tools!
My advice? Ignore all of them :)
For a step-by-step approach, check out my Pure React workshop.

Pure React plant

Learn to think in React

  • 90+ screencast lessons
  • Full transcripts and closed captions
  • All the code from the lessons
  • Developer interviews

Dave Ceddia’s Pure React is a work of enormous clarity and depth. Hats off. I'm a React trainer in London and would thoroughly recommend this to all front end devs wanting to upskill or consolidate.

В этой статье мы рассмотрим, как создавать всплывающие и перекрывающие элементы на React, Angular 1.5 и Angular 2. Реализуем создание и показ модального окна на каждом из фреймворков. Весь код написан на typescript. Исходный код примеров доступен на github.

Что такое "всплывающие и перекрывающие" элементы? Это DOM элементы, которые показываются поверх основного содержимого документа.

Это различные всплывающие окна (в том числе модальные), выпадающие списки и менюшки, панельки для выбора даты и так далее.

Как правило, для таких элементов применяют абсолютное позиционирование в координатах окна (для модальных окон) при помощи position: fixed , или абсолютное позиционирование в координатах документа — для меню, выпадающих списков, которые должны располагаться возле своих "родительских" элементов, — при помощи position: absolute .

Простое размещение всплывающих элементов возле "родителей" и скрытие/отображение их не работают полностью. Причина — это родительские контейнеры с overflow , отличным от visible (и fixed ). Все, что выступает за границы контейнера, будет обрезано. Также такие элементы могут перекрываться элементами ниже по дереву, и z-index не всегда тут поможет, так как работает только в пределах одного контекста наложения.

По-хорошему, эту проблему элегантно мог бы решить Shadow DOM (и то, не факт), но пока он не готов. Могло бы помочь CSS свойство, запрещающее обрезку и перекрытие, либо позиционирование относительно документа (а не родителя), но его нет. Поэтому используем костыль — DOM элементы для всплывающих компонентов помещаем в body , ну или, на худой конец, поближе к нему, в специальный контейнер, у родителей которого заведомо нет "обрезающих" стилей.

Замечание: мы не будем рассматривать позиционирование элементов с разными координатами и родителями — это отдельная сложная тема, полная костылями, javascript-ом и обработкой неожиданных событий браузера.

Например, ни один из существующих способов не решает идеально проблему позиционирования, если нам нужно сделать компонент типа select или autocomplete с выпадающим списком возле элемента.

Использование position: fixed , по-видимому, позволяет избежать обрезки родительским контейнером, но вынуждает обрабатывать скроллинг документа и контейнера (проще всего тупо закрывать выпадающий список). Использование position: absolute и помещение элемента в body обрабатывает прокрутку документа правильно, но требует пересчета позиции при прокрутке контейнера.

Все способы требуют обработки события resize. В общем, нет тут хорошего решения.

Все примеры содержат одинаковую верстку, и состоят из поля ввода с текстом и кнопки. По нажатию на кнопку введенный текст появляется в "модальном" окошке с кнопкой "Закрыть".

Все примеры написаны на typescript. Для компиляции и бандлинга используется webpack. Чтобы запустить примеры, у вас должен быть установлен NodeJS.

Если это делать лень, можно просто открыть в браузере index.html из папки соответствующего примера.

Компоненты

В версии 1.5 Angular приобрел синтаксический сахар в виде метода component у модуля, который позволяет объявлять компоненты. Компоненты — это на самом деле директивы, но код их объявления ориентирован на создание кирпичиков предметной области приложения, тогда как директивы больше ориентированы (идеологически, технически все идентично) на низкоуровневую и императивную работу с DOM. Это нововведение простое, но прикольное, и позволяет объявлять компоненты способом, схожим с Angular 2. Никаких новых фич этот способ не привносит, но может кардинально повлиять на структуру приложения, особенно, если раньше вы пользовались

.
.

От себя я могу добавить, что я в восторге от этой возможности. Она позволяет создавать компоненты с четким контрактом и высоким потенциалом для повторного использования. Более того, с этой возможностью я отказался от вынесения HTML разметки компонента в отдельный файл, т.к. разметка получается маленькая и аккуратная — в ней используются вложенные компоненты — и не загромождает исходник компонента.

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

Два способа

Наверное, существует больше способов поместить компонент в произвольное место DOM. Я покажу два из них, один при помощи сервиса $compile , второй — при помощи директивы с transclude .

Второй способ — декларативный, он позволяет встроить всплывающий элемент в шаблон компонента,
но при показе помещать его в body . Подходит для компонентов типа дроп-дауна, позволяя реактивно управлять видимостью.

Способ 1: $compile

Сервис $compile позволяет преобразовать строку с Angular разметкой в DOM элемент и связать его со $scope .
Получившийся элемент может быть добавлен в произвольное место документа.

Все довольно просто.

Вот документация сервиса. По ссылке полное руководство по API директив, интересующая нас часть в самом конце — использование $compile как функции.

Получаем доступ к $compile

Объявление static $inject=["$compile"] эквивалентно следующему Javascript коду:

$compile работает в две фазы. На первой он преобразует строку в функцию-фабрику. На второй нужно вызвать полученную фабрику и передать ей $scope . Фабрика вернет DOM элементы, связанные с этим $scope.

Результатом компиляции будет фабрика — функция, которая позволит связать строковый шаблон с любым $scope . Таким образом, задавая шаблон, можно использовать любые поля и методы вашего скоупа. Например, вот как выглядит код открытия всплывающего окна:

Обратите внимание на несколько вещей.

Во-первых, шаблон содержит компонент .
Во-вторых, шаблон содержит обращение к полю text контроллера: text="$c.text" .
$c — это алиас контроллера, заданный при объявлении компонента.

PopupService.open также возвращает фабрику, позволяющую связать шаблон со $scope . Для того, чтобы связать динамический компонент со $scope нашего компонента, приходится передавать $scope в контроллер.

Вот как выглядит PopupService.open :

В нашей функции мы оборачиваем переданный шаблон в разметку модального окна. Потом компилируем шаблон, получая фабрику динамических компонентов. Потом вызываем полученную фабрику, передавая $scope , и получаем HTML элемент, который представляет собой полностью рабочий фрагмент Angular приложения, связанный с переданным $scope . Теперь его можно добавить в любое место документа.

Хотя наш метод PopupService.open тоже возвращает фабрику для связи с $scope , он делает дополнительную работу. Во-первых, когда фабрика вызывается, он не только создает элемент, но и добавляет его в body . Во-вторых, он создает функцию, которая позволит "закрыть" поп-ап окно, удалив его из документа. PopupService.open возвращает эту функцию для закрытия окна.

Что ж, вариант не так плох. Хотя само отображение окна императивное, тем не менее, содержимое окна все еще реактивно, и может быть декларативно связано с родительским $scope . Хотя для отображения контента приходится использовать строки, но если сам контент окна сделать в виде компонента, то связывать нужно будет только input и output свойства, а не весь контент. Метод позволяет поместить поп-ап элемент в любое место документа, даже если оно вне ng-app .

Способ 2: Директива с transclude

Второй способ позволяет задавать содержимое всплывающего элемента прямо возле его "родителя". При показе элемент будет на самом деле добавлен в body .

Здесь искомая директива — . . Все, что внутри нее, будет показано во всплывающем окне, и расположено в body .

Небольшой недостаток этого метода в том, что показывать и прятать окно необходимо при помощи директивы ng-if , которая физически будет убирать/добавлять содержимое в DOM дерево.

transclude

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

Это очень мощная возможность, на основе которой можно сделать много интересного. Мы же будем брать содержимое и помещать его в body .

Можно создавать сколько угодно клонов, переопределять им scope, добавлять в любое место документа, и так далее. Здорово.

Для директив, которые сами управляют содержимым (вызывают transclude function), необходимо реализовывать lifecycle методы для очистки содержимого. Эти методы реализуются в контроллере директивы. Удалять добавленное содержимое нужно в $onDestroy .

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

Итак, полный код:

Неплохо, всего 36 строк.

  • Полностью реактивное отображение и скрытие, реактивное содержимое
  • Удобно разносит "виртуальное" расположение в дереве компонентов и "физическое" расположение в DOM дереве.
  • Декларативно привязано к текущему scope.
  • В этом варианте реализации нужно использовать ng-if для управления отображением.

Новая версия Angular, отличающаяся от первого настолько, что, фактически, это новый продукт.
Мои впечатления от него двоякие.

С одной стороны, код компонентов несомненно чище и яснее. При написании бизнес-компонентов разделение кода и представления отличное, change tracking работает прекрасно, прощайте $watch и $apply , прекрасные средства для описания контракта компонента.

С другой стороны, не оставляет ощущение монструозности. 5 min quickstart выглядит издевательством. Множество дополнительных библиотек, многие из которых обязательны к использованию (как rxjs ). То, что я успеваю увидеть надпись Loading. при открытии документа с файловой системы, вселяет сомнения в его скорости. Размер бандла в 4.3MB против 1.3MB у Angular 1 и 700KB React (правда, это без оптимизаций, дефолтный бандлинг webpack-а). (Напоминаю, что webpack собирает (бандлит) весь код приложения и его зависимостей (из npm) в один большой javascript файл).

Минифицированный размер: Angular 1 — 156KB, Angular 2 — около 630KB, в зависимости от варианта, React — 150KB.

Angular 2 на момент написания еще RC. Код практически готов, багов вроде бы нет, основые вещи сделаны (ну кроме разве что переделки форм). Однако документация неполная, многие вещи приходится искать в комментариях к github issue
(как, например, динамическая загрузка компонентов, что, собственно, и подтолкнуло меня к написанию этой статьи).

Disclaimer

Тратить полтора часа на шаги, описанные в упомянутом 5 min quickstart, не хотелось, поэтому проект сконфигурирован не совсем, кхм, традиционно для Angular 2. SystemJS не используется, вместо этого бандлится webpack-ом. Причем Angular 2 не указывается как externals, а берется из npm пакета как есть. В результате получается гигантский бандл в 4.5MB весом. Поэтому не используйте эту конфигурацию в продакшене, если, конечно, не хотите, чтобы пользователи возненавидели ваш индикатор загрузки. Вторая странность, которая не знаю, чем вызвана, это отличающиеся названия модулей. Во всех примерах (в том числе в официальной документации) импорт Angular выглядит как import < >from "@angular/core" . В то же время, у меня так не заработало, а работает import <> from "angular2/core" .

Динамическая загрузка

К чести Angular 2, код динамической загрузки вызывает трудности только при поиске. Для динамической загрузки используется класс ComponentResolver в сочетании с ViewContainerRef.

ComponentResolver легко получить через dependency injection. ViewContainerRef , по-видимому, не может быть создан для произвольного DOM элемента, и может быть только получен для существующего Angular компонента. Это значит, что поместить динамически созданный элемент в произвольное место DOM дерева невозможно, по крайней мере, в релиз-кандидате.

Поэтому, наш механизм для показа поп-апов будет составным.

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

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

Хост-компонент

Я приведу класс целиком, он не очень большой, и потом пройдусь по тонким местам:

Что делает этот код? Он динамически создает компонент, используя его тип (тип компонента — это его функция-конструктор). Предварительно создается компонент-обертка ( OverlayComponent ), наш запрошенный компонент добавляется уже к нему. Также мы подписываемся на событие destroy , чтобы уничтожить обертку при уничтожении компонента.

Честно говоря, я это нагуглил, и как по мне, это вообще неинтуитивно. Это одна из особенностей второго Angular-а, которая мне очень сильно бросилась в глаза, — в документации очень сложно, или же вообще невозможно, найти решения для типовых задач низкоуровневой разработки директив. Документация для создания именно бизнес-компонентов нормальная, да и ничего там особо сложного нет. Однако для сценариев написания контролов, низкоуровневых компонентов, невозможно найти документации. Динамическое создание компонентов, взаимодействие с шаблоном из класса — эти области просто не документированы. Даже в описании @ViewChild ничего не сказано о втором параметре.

Что ж, надеюсь, к релизу задокументируют.

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

Посмотрим теперь, как этим пользоваться:

OverlayService указан в providers Root компонента, в нашем компоненте его регистрировать не нужно.

После создания экземпляра компонента можно получить к нему доступ через ComponentRef.instance .

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

Вывод

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

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

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

В Реакте стандартный способ отображения компонента в DOM дерево — это метод render , который возвращает виртуальный узел виртуального DOM. Однако, это совсем не значит, что этот способ единственный. Для вставки компонента в произвольное место из метода render возвращается null , и перехватываются lifecycle-методы componentDidMount , componentWillUnmount , componentDidUpdate . В componentDidMount и componentDidUpdate , используя ReactDOM.render , можно отрендерить содержимое в любое место. В componentWillUnmount содержимое, соответственно, уничтожается.

Все просто и понятно. Видно, что такой сценарий создателями Реакта продумывался.

Ладно, теперь посмотрим, как это использовать. Привожу, для краткости, только метод render :

Ifc это костылик, который рендерит содержимое, только если condition истинно. Это позволяет избавиться от монструозных IIFE, если нужно отрендерить кусок компонента по условию.

В остальном все просто: если компонент есть в виртуальном дереве — поп-ап окошко показывается, если нет — то прячется. При этом физически в DOM дереве оно находится в body .

Как видим, очень похоже на второй способ с Angular 1.5, с директивой.

Итоги

В принципе, поп-ап в Реакте можно сделать и императивным способом, похожим на способ Angular с $compile . Это может упростить некоторые сценарии и не создавать флаг в состоянии приложения для показа каждого алерта. Принцип тот же (используя ReactDOM.render ), но только не в методах жизненного цикла компонента, а в методе openPopup . Это, конечно же, нарушает реактивность, но сделает код понятнее, увеличив связность.

Недостатки приведенного способа — не будет работать в серверном рендеринге.

Подходы, изначально заложенные в Реакте — однонаправленные потоки данных, компоненты, четкий input/output контракт компонента — нашли свое отражение и в Angular: by design в Angular 2, и в обновлениях Angular 1.5. Это изменение без сомнения пошло на пользу первому Angular.

Что касается показа всплывающих элементов — это пример костылей, которые возникли из-за несовершенства CSS, но повлияли на всю экосистему веб-разработки. Это яркий пример текущей абстракции, а также баланса между "чистой архитектурой" и "реальной жизнью" веб разработки. Как видим, разработчики Angular 2 либо не задумывались об этом сценарии, либо реализовали его, но никому не сказали. В то же время, первый Angular и React достаточно гибкие (а разработчики Реакта видимо еще и продуманные), чтобы можно было реализовать рендеринг элемента в отличное от его расположения в дереве компонентов.


Начнем с создания следующей структуры папок в директории component :


Сначала мы собираемся изменить файл SuccessModal.js :

Этот код довольно прост. Мы используем компоненты react-bootstrap для модального внутри функционального компонента. Через объект props мы отправляем разные параметры нашему модальному окну и одно событие. Это событие закроет модальное окно после того, как мы нажмем кнопку.

Таким же образом изменим файл ErrorModal.js :

Далее, нам нужно изменить файл ModalStyles.css :

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

Создание конфигурации для элементов ввода

Внутри папки src мы собираемся создать папку Utility и внутри нее новый файл InputConfiguration.js . В этом файле мы собираемся сохранить все наши настройки конфигурации для элементов ввода (имя, адрес, дата рождения), которые мы собираемся использовать в формах добавления и редактирования.

Мы уже установили библиотеку моментов в предыдущем посте, но если вы еще этого не сделали, сделайте это (она нужна нам для элемента управления datepicker) с помощью следующей команды:

Теперь давайте изменим файл InputConfiguration.js :

Динамическое создание входных элементов

Первое, что нам нужно сделать, это установить библиотеку react-datepicker , потому что мы собираемся использовать ее для элемента управления датой рождения.


В папке src создадим папку UI . Внутри нее мы собираемся создать новую папку Input и внутри нее файлы Input.js и Input.css :


Файл Input.js будет функциональным компонентом, поэтому давайте изменим его соответствующим образом:

Под оператором if и над блоком return мы собираемся добавить код для заполнения свойства inputField :

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

Осталось изменить файл Input.css :

В этом классе мы переопределяем некоторые из собственных классов datePicker и добавляем один настраиваемый класс ( .datePickerControl ).

Вот и все, теперь мы можем перейти к компоненту CreateOwner .

Компонент CreateOwner

В папке containers , а затем внутри папки Owner создайте новую папку и назовите ее CreateOwner . Внутри создайте новый файл CreateOwner.js .

Приступим к редактированию этого файла:

В приведенном выше коде мы импортируем все необходимые компоненты react-bootstrap и функцию returnInputConfiguration из файла InputConfiguration.js . Этот компонент является компонентом класса или компонентом с отслеживанием состояния, и в хуке жизненного цикла componentWillMount мы обновляем наше локальное состояние с учетом всей конфигурации формы. Хук compnentWillMount сработает непосредственно перед установкой компонента.

Давайте добавим еще одну строку кода между блоками render и return :

Внутри папки Utility создайте новый файл FormUtility.js . Измените этот файл, добавив функцию преобразования объекта:

Теперь нам нужно импортировать эту функцию в файл CreateOwner.js :

У нас будет больше действий внутри файла FormUtility.js , поэтому мы импортируем все эти действия в компонент CreateOwner.js .

Мы заполнили formElementsArray , поэтому давайте воспользуемся им для отправки всех свойств компоненту Input .

Объект конфигурации элементов формы

Давайте добавим этот код в тег Well :

В этом коде мы перебираем все объекты (входные конфигурации) внутри formElementsArray и возвращаем компонент Input со всеми необходимыми свойствами и событиями, которые ему требуются. Существует функция handleChangeEvent , и мы собираемся создать эту функцию, чтобы включить проверку и двустороннюю привязку. Но об этом чуть позже.

Чтобы увидеть результат наших текущих действий, давайте изменим файл App.js , добавив другой маршрут к компоненту CreateOwner ниже маршрута OwnerDetails . Не следует забывать и об операторе импорта:


Если мы перейдем на страницу CreateOwner , мы увидим такой результат:

Мы создали элементы управления, но пока ничего не можем с ними сделать. Но как только мы реализуем функцию handleChangeEvent , мы сможем изменять и проверять наши входные данные.

Завершение компонента CreateOwner

Давайте добавим кнопки к нашему компоненту, чтобы завершить создание компонента.

Внутри тега Form и ниже тега
добавьте этот код:

Мы добавляем две кнопки, и кнопка «Создать» неактивна, пока форма недействительна.

Теперь наша форма выглядит так:


Все работает как надо.

Заключение

Прочитав этот пост, вы узнали:

  • Как использовать модальные элементы Bootstrap для создания повторно используемых компонентов модального окна.
  • Использовать простой объект конфигурации для создания формы и как передать его в массив объектов.
  • Как передать массив конфигурации с помощью простого кода JSX в представление формы.

Спасибо, что прочитали статью, и я надеюсь, что вы нашли в ней что-то полезное.

A guide to building a modal module for React with React-Router.

Modals are very useful for displaying one view on top of another.

However, they are more than an absolutely positioned element wrapping everything when it comes to implementation. Especially if you need dynamic URLs, page refreshes, or a simple scrolling interaction on a mobile device.

In this article, we’ll discuss the various aspects of modals and identify solutions to satisfy the requirements that come with creating dynamic URLs, page refreshes, and other features.

Before starting to shape the modal component, let’s start with some basics of the react-router package.

We’ll use four components from this package: BrowserRouter, Route, Link, and Switch.

Since this is not a react-router tutorial, I won’t be explaining what each of these components do.

However, if you’d like some info about react-router, you can check out this page.

Basic routing

First, go ahead and install react-router-dom through npm.

At the very top level of your application, use the component to wrap your app.

Inside , you’ll need to specify the routes so that you can render a specific view when one of them — or none of them — match.

We made a custom demo for .
No really. Click here to check it out .

Let’s assume we have three different components to render: , and . We’ll create a navigation menu, which will always be visible at the very top of the application.

The or components from react-router-dom are used for navigation purposes, while has the special feature of being applicable to a specific styling when the current URL matches.

Functionality-wise, you can use either one.

Below is the basic structure of the navigation menu, which changes the URL accordingly:

The next thing we’ll do is implement the mechanism that matches the URL and renders a specific component.

renders the first matching location specified by its children. When nothing is matched, the last is returned — usually as a 404 page.

Creating a modal component

So far, we’ve implemented the basic routing structure. Now we can create a modal component and work on displaying it as an overlay.

Although there are a variety of different methods for creating modal components, we’ll only be covering one.

A modal component has a wrapper element which spans the whole screen — width and height.

This area also acts as a clickedOutside detector. Then the actual modal element is positioned relative to that wrapper element.

An example of a modal router element.

Below is an example of a functional component using withRouter HOC (Higher order component) to access the router history and call the goBack() method to change the application URL when the modal is closed on click to .modal-wrapper .

onClick= e.stopPropagation()> is used to prevent propagation of the click event and trigger the onClick on .modal-wrapper , which would close the modal when the actual .modal element is activated.

Styling the .modal-wrapper is just as important. Below, you can find the basic styling used to make it span the whole screen and appear above the content.

Using -webkit-overflow-scrolling: touch enables elastic scroll on iOS devices.

Opening the modal view

The modal component we created should render on top of the existing view when a specific URL is matched, meaning that somehow we have to change the URL so the routing mechanism can decide what to render.

We know that renders the first matching location, but a modal overlay needs two components rendering at the same time.

This can be achieved by putting the modal out of and rendering it conditionally.

In this case, we should be able to detect if a modal is active or not.

The easiest way to do this is by passing a state variable along with a component.

In the same way we used the component to create the navigation menu, we’ll use it to trigger a modal view.

The usage shown below lets us define a state variable, which is then made available in the location prop, which we can access within any component using withRouter HOC.

Put this anywhere you want. Clicking the link will change the URL to /modal/1 .

There might be several modals with different names like modal/1 , modal/2 , and so on.

In this case, you’re not expected to define each intended to match the individual modal locations. In order to handle all of them under the /modal route, use the following syntax:

This gives you the flexibility of getting the value of the hardcoded :id parameter within the modal component through the match.params prop.

It also lets you do dynamic content renderings, depending on which modal is open.

Matching the modal location

This section is particularly important because it identifies the mechanism for displaying a modal on top of an existing view even though the location parameter changes when a modal is opened.

When we click the Open Modal link defined in the previous section, it will change the location path to /modal/1 , which matches nothing in .

So we have to define the following somewhere.

We want to display the component as an overlay.

However, putting it inside would match it and only render the component. As a result, there would be no overlay.

To resolve this problem, we need to define it both inside and outside of with extra conditions.

Below, you’ll see the modified version of the same snippet. There are several changes. Let’s list them quickly:

    There is a previousLocation variable defined in the constructor.

There is an isModal variable defined, which depends on some other values.

is using a location prop.

There are two exact path="/modal/:id" component= /> used both inside and outside , and the one outside is conditionally rendered.

When a modal is opened, we need to store the previous location object and pass this to instead of letting it use the current location object by default.

This basically tricks into thinking it’s still on the previous location — for example / — even though the location changes to /modal/1 after the modal is opened.

This can be achieved by setting the location prop on .

The following snippet replaces the previousLocation with the current location object when there is no open modal.

When you open a modal, it doesn’t modify the previousLocation .

As a result, we can pass it to to make it think we’re still on the same location, even though we changed the location by opening a modal.

We know that when a modal is opened, the state variable named modal in the location object will be set to true .

We can check if the state of the location object is defined and has the state variable of modal set to true .

However, these two checks alone do not suffice in the case of refreshing the page.

While the modal has to be closed on its own, location.state && location.state.modal still holds.

Checking whether this.previousLocation !== location , we can make sure that refreshing the page will not result in setting isModal to true .

When the modal route is visited directly, which is modal/1 in our example, then none of the checks are true .

Now we can use this boolean value to both render the outside of the , and to decide which location object to pass to location prop of .

Given that component has the necessary stylings, this results in two different views rendering on top of each other.

Rendering different modal views

So far we have implemented our modal in a way that ensures we don’t render an overlay when refreshing a page with an open modal, or when directly visiting a modal route.

Instead, we only render the matching inside .

In this case, the styling you want to apply is likely to be different, or you might want to show a different content.

This is pretty easy to achieve by passing the isModal variable as a prop on the component, as shown below.

Then, depending on the value of the prop, you can apply different stylings or return a completely different markup.

Preventing the scroll underneath the modal

When you open the modal on some browsers it may have the content below scrolling underneath the modal, which is not a desirable interaction.

Using overflow: hidden on body is the first attempt to block scrolling on the entire page.

However, although this method works fine on desktop, it fails on mobile Safari since it basically ignores overflow: hidden on body .

There are several different npm packages attempting to remedy this scroll locking issue virtually across all platforms.

I found the body-scroll-lock package quite useful.

From this package, you can import disableBodyScroll and enableBodyScroll functions, which accept a reference to the element for which you want scrolling to persist as an input.

When the modal is open we want to disable scrolling for the entire page, except for the modal itself.

Therefore, we need to call disableBodyScroll and enableBodyScroll functions when the modal component is mounted and unmounted, respectively.

To get a reference to the parent of the modal component, we can use the createRef API from React and pass it as a ref to the parent .

The code snippet below disables scrolling when the modal is open and enables it again when the modal component is about to be unmounted.

Using this.modalRef as the input for these imported functions prevents the content of the modal component from being scroll-locked.

Before using the disableBodyScroll function, we need a simple check.

This is because a modal component might get mounted if the page is refreshed when a modal is open, or when the modal route is visited directly.

In both cases, scrolling should not be disabled.

We have already passed the isModal variable as a prop to the component to render different views, so we can just use this prop to check if there is actually a modal.

Below is the modified version of the modal component:

Conclusion

You now have an understanding of how a modal view works, as well as a sense of some of the problems you may encounter while implementing your own integration.

For the fully functional example, visit this code sandbox project.

Full visibility into production React apps

Debugging React applications can be difficult, especially when users experience issues that are hard to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.

LogRocket is like a DVR for web and mobile apps, recording literally everything that happens on your React app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

I love modals because they save a lot of time required to load a new tab. In this post, we’re going to learn how to create an awesome reusable modal with React from scratch.


These are the steps for this tutorial:

What we’ll learn

  1. Create a simple file structure
  2. Use react toggle state to build a simple modal
  3. Pass data between components with props
  4. Warn the user to pass a parameter while using the component

Tip: When using React components, share them with Bit so that they become reusable building blocks your team can easily share, use and sync across your projects. When building with a shared Lego box, you build faster. Try it:

Bit — Share and build with code components

Bit helps you share, discover and use code components between projects and applications to build new features and…


So, first, let’s add the boilerplate code.

Then, let’s include the Modal in the main App .

Now inject App component to the entry point.

Here’s the output after the initial setup.


The mechanism of the modal is to show and hide. This is quite easy to achieve in react because of the built-in toggle state.

First, create a button to toggle the state.

Now create a showModal function

Now apply this state to Modal component.

Next, make a conditional render from Modal, condition being the show state.

Don’t forget to return null if show is false , this renders nothing. Render the component when show is true .

Let’s click the button- the output should be something like this.

Right now the content in Modal is hard-coded. Let’s make it dynamic by passing a prop.

Replace Message with in the Modal component.

This will render whatever is passed to this component, which is just perfect for a dynamic modal.


After opening a modal, it needs to be closed.

Let’s add a new close button.

Define the onClose function.

Now set the show state to false , which makes Modal hide.

Wait! It does not work because show state is defined in App component.

So, pass a value back to App component.

Now pass the whole event object back to App component, then trigger showModal

And toggle the show state:

The result hides the close button, as expected.

Right now you don’t see any modal body because, well, it’s not there.

Let’s create modal.css .


Grab the CSS snippet from Codepen here.

Also, include JavaScript to Modal.js and add a class modal in the returning div , and more classes as below.

In the App component, add a class toggle-button to the button.

Now, the result! Isn’t it beautiful? 💅

If the component that calls Modal does not pass onClose , the Modal won't close as the show state doesn't have any effect. To fix this, warn the component to pass the onClose.

Add the following to the bottom of the file, outside the class.

This results in a warning as below.


Читайте также: