я новичок в ReactJS. И я пытаюсь создать приложение, которое отображает модальное окно с разными параметрами. Моя проблема в том, что у меня есть модальный компонент многократного использования, но как мне добиться того, чтобы нажатие/нажатие кнопки внутри модального окна повторно отображало весь модальный компонент с разными результатами/значениями?
Вот мой код:
Модал.js
import { useEffect, useRef, Fragment, Link, useState } from "react";
import { useParams, useNavigate, useLocation } from "react-router-dom";
import { disableBodyScroll, enableBodyScroll } from "body-scroll-lock";
import airportData from '../airports.json';
export function Modal() {
const modalRef = useRef();
const { id } = useParams();
const navigate = useNavigate();
const [reload, setReload] = useState(false);
useEffect(() => {
const observerRefValue = modalRef.current;
disableBodyScroll(observerRefValue);
return () => {
if (observerRefValue) {
enableBodyScroll(observerRefValue);
}
};
}, []);
function setImageUrlLarge(url) {
return {
backgroundImage: `url(${process.env.PUBLIC_URL}/assets/images/large/${url}.jpg)`
}
}
return (
<div ref = {modalRef} className = "modal-wrapper">
<div className = "modal">
<div className = {`detail ${id} overlay`} style = {setImageUrlLarge(id)}>
<a className='overlay' rel='noopener'></a>
<AirportDetails id = {id} airportData = {airportData} />
</div>
</div>
</div>
)
function AirportDetails({id, airportData}) {
const [data, setData] = useState(airportData);
const [airportId, setAirportId] = useState(id);
const airport = airportData.filter((airport) => airport.id.toLowerCase().includes(id.toLowerCase()))[0];
const [description, setDescription] = useState(airport.description);
const pattern = /\*([A-Za-z])\*/gi;
const em = description.replace(pattern, '<em>$1</em>');
const currUrl = window.location.href;
function capitalize(str) {
return str.toUpperCase();
}
const share = (socialType) => e => {
if (socialType == "twitter") {
const text = `Making sense of those three-letter airport codes: ${capitalize(id)}`;
const link = `https://twitter.com/intent/tweet?url=${currUrl}&text=${text}`;
return link;
}
const link = `https://www.facebook.com/dialog/share?display=popup&href=${currUrl}${id}&redirect_uri=${currUrl}${id}`;
return link;
}
function setTo(social) {
if (social= = "twitter") {
return "https://twitter.com/intent/tweet?url=$SHARE_URL&text=$TEXT";
}
else {
return "https://www.facebook.com/sharer/sharer.php?u=$SHARE_URL";
}
}
return (
<div className='container'>
<div className='detail-info'>
<h1>{airport.id}</h1>
<h2>{airport.name}</h2>
<h3><span className = "local_name">{airport.local_name}</span></h3>
<h4>{airport.city}</h4>
<div className = "description fl-edu">
<p dangerouslySetInnerHTML = {{ __html: em}}></p>
</div>
<a className = "close-detail" role = "button" onClick = {() => navigate('/')}></a>
<a className = "random" role = "button" onClick = {() => {randomAirport()}}>
Random Airport</a>
<div className = "social">
<a role = "button" className = "twitter" href = {setTo("twitter")} onClick = {() => {share("twitter")}} target = "_blank"></a>
</div>
<div className = "social">
<a className = "facebook" href = {setTo("facebook")} onClick = {() => {share("facebook")}} target='_blank'></a>
</div>
</div>
<div className = "photo-credit">
Photo by <a>{airport.imageCredit}</a>
</div>
<a className = "back" role = "button" onClick = {() => navigate('/')}>Airport Codes PH</a>
</div>
)
}
function randomAirport() {
const rand = Math.floor(Math.random() * airportData.length);
const airportRandData = airportData[rand];
setReload(!reload);
console.info(reload);
}
}
Чего я добиваюсь сейчас, так это изменения состояния модального окна каждый раз при нажатии кнопки и рандомизации значения объекта. Я ожидаю перезагрузить модальный компонент со значением, отличным от функции randomAirport, и передать данные аэропорта обратно модальному компоненту.
привет, при нажатии кнопки «случайный» он должен сгенерировать случайные данные аэропорта (json), затем передать их обратно в компонент, а затем перезагрузить компонент с «этими» случайно сгенерированными данными.
Идея должна заключаться не в том, чтобы повторно отображать модальное окно с другим содержимым, а просто держать его открытым и заставить компонент, который он отображает, повторно отображать со своим собственным новым состоянием/контентом/и т. д.
Выполните рефакторинг вашего кода, чтобы:
Отделите и объявите AirportDetails
вне компонента Modal
, чтобы он не объявлялся повторно при каждом цикле рендеринга. Это исправляет анти-шаблон React. Уберите состояние reload
, это тоже антипаттерн.
import { useEffect, useRef, Fragment, Link, useState } from "react";
import { useParams, useNavigate, useLocation } from "react-router-dom";
import { disableBodyScroll, enableBodyScroll } from "body-scroll-lock";
import airportData from '../airports.json';
export function Modal() {
const modalRef = useRef();
const { id } = useParams();
const navigate = useNavigate();
useEffect(() => {
const observerRefValue = modalRef.current;
disableBodyScroll(observerRefValue);
return () => {
if (observerRefValue) {
enableBodyScroll(observerRefValue);
}
};
}, []);
function setImageUrlLarge(url) {
return {
backgroundImage: `url(${process.env.PUBLIC_URL}/assets/images/large/${url}.jpg)`
}
}
return (
<div ref = {modalRef} className = "modal-wrapper">
<div className = "modal">
<div className = {`detail ${id} overlay`} style = {setImageUrlLarge(id)}>
<a className='overlay' rel='noopener'></a>
<AirportDetails id = {id} airportData = {airportData} />
</div>
</div>
</div>
)
}
Обновите AirportDetails
, чтобы инициализировать состояние data
данными случайного аэропорта, и обновите обработчик onClick
, чтобы выбрать данные случайного аэропорта и поставить в очередь обновление состояния. Отобразите все детали аэропорта из местного data
штата.
function AirportDetails({ id, airportData }) {
// Initialize data to specific airport by id,
// otherwise select a random airport.
const [data, setData] = useState(() => {
const airport = airportData.find(
(airport) => airport.id.toLowerCase().includes(id.toLowerCase())
);
return airport ? airport : randomAirport(airportData);
});
...
return (
<div className='container'>
<div className='detail-info'>
<h1>{data.id}</h1>
<h2>{data.name}</h2>
<h3><span className = "local_name">{data.local_name}</span></h3>
<h4>{data.city}</h4>
<div className = "description fl-edu">
<p dangerouslySetInnerHTML = {{ __html: em}}></p>
</div>
<a
className = "random"
role = "button"
onClick = {() => {
// Enqueue state update to new random airport data
setData(randomAirport(airportData));
}}
>
Random Airport
</a>
...
</div>
<div className = "photo-credit">
Photo by <a>{data.imageCredit}</a>
</div>
...
</div>
)
}
function randomAirport(airportData) {
const rand = Math.floor(Math.random() * airportData.length);
return airportData[rand];
}
Поскольку параметр пути маршрута id
и данные airports.json
используются для вычисления производного состояния в AirportDetails
, я могу предложить улучшение, заключающееся в том, чтобы не копировать какие-либо данные в какое-либо локальное состояние (в любом случае это что-то вроде антишаблона React) и напрямую вычислить соответствующие данные аэропорта на основе текущего значения id
и просто перейти к новому случайному значению идентификатора аэропорта.
Пример:
import { useEffect, useRef } from "react";
import { useParams } from "react-router-dom";
import { disableBodyScroll, enableBodyScroll } from "body-scroll-lock";
export function Modal() {
const modalRef = useRef();
const { id } = useParams();
useEffect(() => {
const observerRefValue = modalRef.current;
disableBodyScroll(observerRefValue);
return () => {
if (observerRefValue) {
enableBodyScroll(observerRefValue);
}
};
}, []);
function setImageUrlLarge(id) {
return {
backgroundImage: `url(${process.env.PUBLIC_URL}/assets/images/large/${id}.jpg)`
}
}
return (
<div ref = {modalRef} className = "modal-wrapper">
<div className = "modal">
<div className = {`detail ${id} overlay`} style = {setImageUrlLarge(id)}>
<a className='overlay' rel='noopener'></a>
<AirportDetails />
</div>
</div>
</div>
)
}
import { useParams, useNavigate, generatePath } from "react-router-dom";
import airportData from '../airports.json';
function randomAirportId() {
const rand = Math.floor(Math.random() * airportData.length);
return airportData[rand].id;
}
function AirportDetails() {
const { id } = useParams();
const navigate = useNavigate();
const airport = React.useMemo(() => {
return airportData.find(
(airport) => airport.id.toLowerCase().includes(id.toLowerCase())
);
}, [id]);
...
if (!airport) {
return null; // or navigate back, etc.
}
return (
<div className='container'>
<div className='detail-info'>
<h1>{airport.id}</h1>
<h2>{airport.name}</h2>
<h3><span className = "local_name">{airport.local_name}</span></h3>
<h4>{data.city}</h4>
<div className = "description fl-edu">
<p dangerouslySetInnerHTML = {{ __html: em}}></p>
</div>
<a
className = "random"
role = "button"
onClick = {() => {
navigate(
generatePath("/airport/:id", { id: randomAirport()}
),
{ replace: true }
);
}}
>
Random Airport
</a>
...
</div>
<div className = "photo-credit">
Photo by <a>{airport.imageCredit}</a>
</div>
...
</div>
)
}
О, спасибо за ответ, я забыл упомянуть, что модальный компонент использует исходные данные на основе параметра, переданного в функции AirportDetails. Таким образом, прежде чем инициализировать состояние данных как случайное, он должен сначала использовать данные, переданные родительским компонентом.
@krxbnx Ах, понятно, я обновлю инициализатор ленивого состояния, чтобы использовать переданную id
опору. Код остается очень похожим.
Здравствуйте, спасибо за помощь. Я снова попытался console.info результаты функции randomAirport, и да, она возвращает случайные данные. Но дело в том, что модальное окно не обновляет свое содержимое на основе возвращаемых данных аэропорта.
@krxbnx Модальному модалу это не нужно, ему просто нужно отобразить компонент AirportDetails
, передать ему данные аэропорта и позволить ему самому определить, какой контент отображать. Вы визуализируете контент из состояния data
, верно? Думаете, вы могли бы создать работающую CodeSandbox демо-версию вашего кода, которую мы могли бы проверить вживую?
вот ссылка на приложение: ссылка и демо-версия кода: ссылка
@krxbnx В этой песочнице при установке зависимостей есть ошибка, но мне может быть достаточно, чтобы создать минимальную песочницу. Я посмотрю что я могу сделать.
о, я попробовал еще раз, и единственное, что не обновляется при нажатии случайной кнопки, — это фоновое изображение, поскольку оно находится внутри компонента Modal
, который зависит от данных из AirportDetails
@krxbnx Хорошо, я обновил свой ответ, включив в него альтернативу, которая (IMO) является улучшением, поскольку она просто реагирует на параметр пути маршрута id
и удаляет всю ненужную логику локального состояния и синхронизации. Пожалуйста, посмотрите, когда будет время.
Давайте продолжим обсуждение в чате.
Привет @Drew, вот обновленная ссылка на мою песочницу. Пожалуйста, дайте мне знать, если вы снова встретите какую-либо ошибку. Я вернул свои коды обратно к первому предложенному вами решению.
Я только что включил другой <div>s
в компонент AirportDetails
, поэтому он также будет обновляться при нажатии случайной кнопки. Спасибо за помощь! Однако ваше второе решение на данный момент у меня не сработало, поскольку компонент Modal
не отображается после нажатия случайной кнопки.
Чтобы повторно отобразить модальный компонент, вам необходимо обновить текущее состояние в модальном компоненте и передать это состояние нужному компоненту. В вашем проекте, который является компонентом AirportDetails, вам необходимо создать этот компонент отдельно и передать данные в « Детали аэропорта» от props.
В вашем родительском компоненте это означает, что там, где генерируются все эти данные, мы получаем уникальный идентификатор аэропорта,
const [currentAirportId, setCurrentAirportId] = useState(id);
Затем нам нужно обновить реквизиты данных,
<div className = "modal">
<div className = {`detail ${currentAirportId} overlay`} style = {setImageUrlLarge(currentAirportId)}>
<a className='overlay' rel='noopener'></a>
<AirportDetails id = {currentAirportId} airportData = {airportData} setReload = {randomAirport} />
</div>
</div>
Также нам нужно обновить функцию случайного аэропорта, чтобы установить «текущий идентификатор аэропорта».
setCurrentAirportId(airportRandData.id);
ОК, теперь что касается отдельного компонента под названием AirportDetails, нам нужно получить отправляемые значения от родителя.
export function AirportDetails({ id, airportData, setReload }) {
Чтобы найти данные об аэропорте, вы можете повторно использовать свой метод,
const airport = airportData.find((airport) => airport.id.toLowerCase() === id.toLowerCase());
Также, чтобы поделиться своими ссылками, вы можете использовать window.open.
const share = (socialType) => (e) => {
//
window.open(link, '_blank');
};
Для перезагрузки вы можете добавить кнопку случайного аэропорта.
<a className = "random" role = "button" onClick = {() => setReload(prev => !prev)}>Random Airport</a>
Вы хотите, чтобы данные аэропорта были «случайными»… или основывались на реквизите
id
?