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 шт):
В комментариях вы правильно указали суть, надо сделать так:
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>