React Как показывать разные попапы в зависимости от состояний?

Подскажите, пожалуйста, как правильно переключать состояния между компонентами вот на таком упрощенном примере

import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter as Router } from "react-router-dom";

import "./styles.css";

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      popupStatus: false, /// Показываю оббертку для попапов если true
      whichPopupToShow: {
        //// Переключаю значение в true что бы показать тот или иной попап
        infoPopup: false,
        ranksPopup: false
      }
    };
  }

  popupClose = () => {
    this.setState({
      popupStatus: false, /// Закрываю обберку попапов

      whichPopupToShow: {
        /// Закрываю все возможные попапы
        infoPopup: false,
        ranksPopup: false
      }
    });
  };

  popupShow = (choosePopup) => {
    this.setState({ popupStatus: true });
    this.setState({ whichPopupToShow: true }); //// Это не работает как задумано
    //// Идея в том что б в зависимости от переданого параметра whichPopupToShow показывало нужный попап
    //// что-то по типу this.setState({ whichPopupToShow[choosePopup]: true });
  };

  render() {
    return (
      <div className="field">
        <button className="btn" onClick={(e) => this.popupShow("infoPopup")}>
          Show infoPopup
        </button>
        <button className="btn" onClick={(e) => this.popupShow("ranksPopup")}>
          Show ranksPopup
        </button>

        {this.state.popupStatus ? (
          <PopupWindow
            closePopup={this.popupClose}
            whichPopupToShow={this.state.whichPopupToShow}
          />
        ) : null}
      </div>
    );
  }
}

function PopupWindow(props) {
  const { closePopup, whichPopupToShow } = props;

  return (
    <div className="ShadowBlock">
      <div className="popupInner">
        <button className={"btn_closePopups"} onClick={(e) => closePopup()}>
          <svg
            className={"btn_closePopups__icon"}
            xmlns="http://www.w3.org/2000/svg"
            x="0"
            y="0"
            width="12"
            height="12"
            viewBox="0 0 12 12"
          >
            <path
              fill={"#3b2d62"}
              d="M11 .8c.3.3.3.8 0 1.2l-8.8 8.8c-.3.3-.8.3-1.2 0-.3-.3-.3-.8 0-1.2L9.8.8c.3-.3.8-.3 1.2 0z"
            />
            <path
              fill={"#3b2d62"}
              d="M1 .8c.3-.3.8-.3 1.2 0L11 9.6c.3.3.3.8 0 1.2-.3.3-.8.3-1.2 0L1 2C.7 1.7.7 1.2 1 .8z"
            />
          </svg>
        </button>

        {whichPopupToShow.infoPopup ? <InfoPopup /> : null}
        {whichPopupToShow.ranksPopup ? <RanksPopup /> : null}
      </div>
    </div>
  );
}

function InfoPopup() {
  return <div>This is InfoPopup</div>;
}

function RanksPopup() {
  return <div>This is RanksPopup</div>;
}

ReactDOM.render(<App/>, document.getElementById("root"));
.App {
  font-family: sans-serif;
  text-align: center;
}

.field {
  width: 100%;
  height: 100vh;
  display: flex;
  flex-direction: column;
  justify-content: center;
}

.btn {
  display: inline-block;
  height: 40px;
  line-height: 40px;
  padding: 0 10px;
  border: 1px solid #000;
  margin: 10px;
}

.ShadowBlock {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  min-height: 100vh;
  background-color: rgba(22, 0, 50, 0.85);
  z-index: 15;

  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}

.popupInner {
  position: relative;
  min-height: 120px;
  min-width: 100px;
  background-color: #fff;
}

.btn_closePopups {
  position: absolute;
  right: -20px;
  top: -20px;
  width: 40px;
  height: 40px;
  border-radius: 50%;
}

.btn_closePopups:before {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 24px;
  height: 24px;
  background-color: #fff;
  border-radius: 50%;
  z-index: 1;
}

.btn_closePopups__icon {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: 2;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Хотелось бы получить правильно работающий компонент который в зависимости от того какой параметр передается в popupShow отображал бы нужный попап.


Ответы (1 шт):

Автор решения: entithat

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

this.setState({ ...this.state, whichPopupToShow: { [choosePopup]: true } });

Устанавливаем стейт изходя из старого, меняя только динамический ключ вложенного объекта whichPopupToShow.


Еще чтобы не закрывать вручную все popupы (потому что я могу забыть за какой-то и это приведет к потерянным часам поиска проблемы) стоит переписать функцию popupClose, чтобы она динамически закрывала все возможные popupы:

popupClose = () => {
    this.setState({
      popupStatus: false, /// Закрываю обберку попапов
      whichPopupToShow: Object.fromEntries(
        Object.entries(this.state.whichPopupToShow).map((e) => {
          e[1] = false;
          return e;
        })
      )
    });
  };

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

{whichPopupToShow.infoPopup && <InfoPopup />}

import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter as Router } from "react-router-dom";

import "./styles.css";

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      popupStatus: false, /// Показываю оббертку для попапов если true
      whichPopupToShow: {
        infoPopup: false,
        ranksPopup: false
      }
    };
  }

  popupClose = () => {
    this.setState({
      popupStatus: false, /// Закрываю обберку попапов

      whichPopupToShow: Object.fromEntries(
        Object.entries(this.state.whichPopupToShow).map((e) => {
          e[1] = false;
          return e;
        })
      )
    });
  };

  popupShow = (choosePopup) => {
    this.setState({ popupStatus: true });
    this.setState({ ...this.state, whichPopupToShow: { [choosePopup]: true } });
  };

  render() {
    return (
      <div className="field">
        <button className="btn" onClick={(e) => this.popupShow("infoPopup")}>
          Show infoPopup
        </button>
        <button className="btn" onClick={(e) => this.popupShow("ranksPopup")}>
          Show ranksPopup
        </button>

        {this.state.popupStatus ? (
          <PopupWindow
            closePopup={this.popupClose}
            whichPopupToShow={this.state.whichPopupToShow}
          />
        ) : null}
      </div>
    );
  }
}

function PopupWindow(props) {
  const { closePopup, whichPopupToShow } = props;

  return (
    <div className="ShadowBlock">
      <div className="popupInner">
        <button className={"btn_closePopups"} onClick={(e) => closePopup()}>
          <svg
            className={"btn_closePopups__icon"}
            xmlns="http://www.w3.org/2000/svg"
            x="0"
            y="0"
            width="12"
            height="12"
            viewBox="0 0 12 12"
          >
            <path
              fill={"#3b2d62"}
              d="M11 .8c.3.3.3.8 0 1.2l-8.8 8.8c-.3.3-.8.3-1.2 0-.3-.3-.3-.8 0-1.2L9.8.8c.3-.3.8-.3 1.2 0z"
            />
            <path
              fill={"#3b2d62"}
              d="M1 .8c.3-.3.8-.3 1.2 0L11 9.6c.3.3.3.8 0 1.2-.3.3-.8.3-1.2 0L1 2C.7 1.7.7 1.2 1 .8z"
            />
          </svg>
        </button>

        {whichPopupToShow.infoPopup && <InfoPopup />}
        {whichPopupToShow.ranksPopup && <RanksPopup />}
      </div>
    </div>
  );
}

function InfoPopup() {
  return <div>This is InfoPopup</div>;
}

function RanksPopup() {
  return <div>This is RanksPopup</div>;
}

ReactDOM.render(<App/>, document.getElementById("root"));
.App {
  font-family: sans-serif;
  text-align: center;
}

.field {
  width: 100%;
  height: 100vh;
  display: flex;
  flex-direction: column;
  justify-content: center;
}

.btn {
  display: inline-block;
  height: 40px;
  line-height: 40px;
  padding: 0 10px;
  border: 1px solid #000;
  margin: 10px;
}

.ShadowBlock {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  min-height: 100vh;
  background-color: rgba(22, 0, 50, 0.85);
  z-index: 15;

  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}

.popupInner {
  position: relative;
  min-height: 120px;
  min-width: 100px;
  background-color: #fff;
}

.btn_closePopups {
  position: absolute;
  right: -20px;
  top: -20px;
  width: 40px;
  height: 40px;
  border-radius: 50%;
}

.btn_closePopups:before {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 24px;
  height: 24px;
  background-color: #fff;
  border-radius: 50%;
  z-index: 1;
}

.btn_closePopups__icon {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: 2;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Demo

→ Ссылка