Работа с порталами в React: создание модальных окон и всплывающих сообщений

месяц назад·3 мин. на чтение

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

Одной из самых интересных и полезных функциональностей React является работа с порталами. В этой статье мы рассмотрим, как создать модальные окна и всплывающие сообщения с использованием порталов в функциональных компонентах React. Порталы в React позволяют отрендерить дочерний компонент в другой DOM элемент, который находится вне иерархии компонента. Это может быть полезно, когда требуется рендерить компоненты в других частях страницы или даже вне контейнера приложения. Для начала, установим все необходимые зависимости. Откройте командную строку и выполните следующую команду:
npm install react-dom
Окончив установку, импортируйте ReactDOM в файле компонента:
import ReactDOM from 'react-dom';
Теперь давайте создадим простой модальный компонент. Создайте новый файл Modal.js и добавьте следующий код:
import React from 'react';

const Modal = ({ isOpen, onClose, children }) => {
  if (!isOpen) {
    return null;
  }

  return ReactDOM.createPortal(
    <div className="modal">
      <div className="modal-content">{children}</div>
      <button onClick={onClose}>Закрыть</button>
    </div>,
    document.getElementById('modal-root')
  );
};

export default Modal;
В этом компоненте мы используем ReactDOM.createPortal для отображения содержимого модального окна в элементе с идентификатором 'modal-root'. Помните, что этот элемент должен быть добавлен в HTML файл вашего проекта:
<body>
  <div id="root"></div>
  <div id="modal-root"></div>
</body>
Теперь создадим компонент, который будет использовать модальное окно. Создайте новый файл App.js и добавьте следующий код:
import React, { useState } from 'react';
import Modal from './Modal';

const App = () => {
  const [isOpen, setIsOpen] = useState(false);

  const handleOpenModal = () => {
    setIsOpen(true);
  };

  const handleCloseModal = () => {
    setIsOpen(false);
  };

  return (
    <div>
      <button onClick={handleOpenModal}>Открыть модальное окно</button>
      <Modal isOpen={isOpen} onClose={handleCloseModal}>
        <h1>Модальное окно</h1>
        <p>Это модальное окно!</p>
      </Modal>
    </div>
  );
};

export default App;
В этом компоненте мы используем useState для создания состояния isOpen, которое определяет, открыто ли модальное окно или нет. При нажатии на кнопку "Открыть модальное окно" состояние isOpen устанавливается в true, что вызывает рендеринг модального окна. Когда модальное окно закрывается, состояние isOpen устанавливается в false. Теперь, чтобы отобразить наше приложение, импортируйте компонент App в файл index.js:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

ReactDOM.render(<App />, document.getElementById('root'));
Теперь, если вы запустите свое приложение React, вы должны увидеть кнопку "Открыть модальное окно". При нажатии на эту кнопку откроется модальное окно с заголовком "Модальное окно" и текстом "Это модальное окно!". Если вы нажмете на кнопку "Закрыть", модальное окно закроется.
Теперь давайте рассмотрим, как создать всплывающие сообщения с использованием порталов. Создайте новый файл Toast.js и добавьте следующий код:
import React from 'react';
import ReactDOM from 'react-dom';

const Toast = ({ message }) => {
  return ReactDOM.createPortal(
    <div className="toast">{message}</div>,
    document.getElementById('toast-root')
  );
};

export default Toast;
В этом компоненте мы используем ReactDOM.createPortal для отображения всплывающего сообщения в элементе с идентификатором 'toast-root'. Помимо сообщения, мы можем редактировать стили внутри компонента toast, чтобы обеспечить пользовательскую информацию и удобство использования. Добавьте следующий код в компонент App.js, чтобы использовать всплывающее сообщение:
import React, { useState } from 'react';
import Modal from './Modal';
import Toast from './Toast';

const App = () => {
  const [isOpen, setIsOpen] = useState(false);
  const [showToast, setShowToast] = useState(false);

  const handleOpenModal = () => {
    setIsOpen(true);
  };

  const handleCloseModal = () => {
    setIsOpen(false);
  };

  const handleShowToast = () => {
    setShowToast(true);
    setTimeout(() => {
      setShowToast(false);
    }, 2000);
  };

  return (
    <div>
      <button onClick={handleOpenModal}>Открыть модальное окно</button>
      <button onClick={handleShowToast}>Показать всплывающее сообщение</button>
      <Modal isOpen={isOpen} onClose={handleCloseModal}>
        <h1>Модальное окно</h1>
        <p>Это модальное окно!</p>
      </Modal>
      {showToast && <Toast message="Привет, я всплывающее сообщение!" />}
    </div>
  );
};

export default App;
В этом коде мы добавили новое состояние showToast, которое определяет, должно ли отображаться всплывающее сообщение или нет. При нажатии на кнопку "Показать всплывающее сообщение" состояние showToast становится true и всплывающее сообщение отображается. Через 2 секунды после отображения сообщение исчезает, так как мы используем setTimeout в функции handleShowToast. Добавьте элемент toast-root в ваш html-файл, также как и с элементом modal-root:
<body>
  <div id="root"></div>
  <div id="modal-root"></div>
  <div id="toast-root"></div>
</body>
Теперь, при запуске приложения и нажатии на кнопку "Показать всплывающее сообщение", вы должны увидеть всплывающее сообщение с текстом "Привет, я всплывающее сообщение!". Через 2 секунды оно исчезнет автоматически. Поздравляю, вы только что освоили работу с порталами React и создали модальные окна и всплывающие сообщения в функциональных компонентах React. Теперь вы можете использовать эти знания в своих проектах, чтобы создавать лучший пользовательский интерфейс.

Как передавать данные между компонентами в ReactJS

2 года назад·3 мин. на чтение

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

Есть несколько типичных ситуаций передачи данные между компонентами в React:
  • от родительского компонента к дочернему;
  • от дочернего компонента к родительскому;
  • между соседними компонентами;
  • от компонента к компоненту-потомку (через несколько уровней вниз);
  • от компонента к компоненту-предку (через несколько уровней вверх).
Данные можно передавать:
  • через пропсы;
  • используя callback-функцию;
  • пробросом пропсов от уровня к уровню (prop drilling);
  • при помощи контекста (React Context AP);
  • через хранилище (store);
На практике также приходится передавать данные не только между компонентами, но и между страницами. Способы передачи данных между компонентами, находящимися на разных страницах, с помощью React Router можно прочитать в статье Полное руководство по React Router v6. Часть 3 - Управление навигацией.

От родительского компонента к дочернему

Наиболее простой и часто встречающийся случай - это случай, когда дочерний компонент принимает данные от родителя через пропсы.
import { useState } from 'react'

const Parent = () => {
  const [value, setValue] = useState('')

  const handleChange = (event) => {
    setValue(event.target.value)
  }

  return (
    <div>
       <input
         type="text"
         onChange={handleChange }
       />
      {/* передаем проп в дочерний компонент */}
      <Child value={value} />
    </div>
  )
}

const Child = ({ value }) => {
  return (
    <span>Value is: {value || '<Not set>'}</span>
  )
}

От дочернего компонента к родительскому

Если необходимо передать данные от дочернего реакт компонента к родительскому, используются функции обратного вызова (callback-функции).
import { useState } from 'react'

const Child = ({ onChange }) => {
  const handleChange = (event) => {
    onChange(event.target.value) // callback-функция
  }

  return (
    <input
      type="text"
      onChange={handleChange}
    />
  )
}

const Parent = () => {
  const [value, setValue] = useState('')
  const handleChange = (value) => {
    setValue(value)
  }

  return (
    <div>
      <span>Value is: {value || '<Not set>'}</span>
      <Child onChange={handleChange} />
    </div>
  )
}

Между соседними компонентами

Данные между соседними компонентами, т.е. между компонентами на одном уровне, можно передать через общий предок. Обычно данные от одного Реакт компонента передаются вверх, в компонент-предок, через callback-функцию, а компонент-предок передает их в другой компонент через проп.
import { useState } from 'react'

const Parent = () => {
  const [value, setValue] = useState('')

  const handleChange = (value) => {
    setValue(value)
  }

  return (
    <div>
      <Sibling1 onChange={handleChange} />
      <Sibling2 value={value} />
    </div>
  )
}


const Sibling1 = ({ onChange }) => {
  const handleChange = (event) => {
    onChange(event.target.value)
  }

  return (
    <input
      type="text"
      onChange={handleChange}
    />
  )
}

const Sibling2 = ({ value}) => {
  return (
    <span>Value is: {value || '<Not set>'}</span>
  )
}

Через несколько уровней вверх/вниз

Если компоненты находится в несколько уровнях друг от друга, то также можно передать проп. Этот проп придется описывать во всех компонентах на всех промежуточных уровнях. Эта ситуация называется prop drilling. Если уровней много, то такой способ покажется не очень удобным. Если нужно передавать данные на несколько уровней вверх, то также придется описывать и вызывать callback-функцию на всех промежуточных уровнях. Однако, в подобных случаях можно использовать Context API (пример которого приведен ниже), или state management библиотеки, такие как Redux, MobX, Recoil и т.д.
import { useState, useContext, createContext } from 'react'

// создаем контекст
const ValueContext = createContext()

// Component1 записывает данные в контекст ValueContext
const Component1 = () => {
  const { setValue } = useContext(ValueContext)

  const handleChange = (event) => {
    setValue(event.target.value)
  }

  return (
    <input
      type="text"
      onChange={handleChange}
    />
  )
}

// Component2 читает данные из контекста ValueContext
const Component2 = () => {
  const { value } = useContext(ValueContext)

  return (
    <span>Value is: {value || '<Not set>'}</span>
  )
}

// компоненты, которым необходим доступ к контексту,
// должны быть обернуты в Provider
export default function App() {
  const [value, setValue] = useState('')

  return (
    <ValueContext.Provider value={{ value, setValue }}>
      <Component1 />
      <Component2 />
    </ValueContext.Provider>
  )
}
Исходный код Подробное руководство по React