Перерисовать модальный компонент onClick с параметрами случайного значения

я новичок в 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, и передать данные аэропорта обратно модальному компоненту.

Вы хотите, чтобы данные аэропорта были «случайными»… или основывались на реквизите id?

Drew Reese 25.07.2024 04:21

привет, при нажатии кнопки «случайный» он должен сгенерировать случайные данные аэропорта (json), затем передать их обратно в компонент, а затем перезагрузить компонент с «этими» случайно сгенерированными данными.

krxbnx 25.07.2024 04:31
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
3
2
59
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

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

Выполните рефакторинг вашего кода, чтобы:

  1. Отделите и объявите 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>
      )
    }
    
  2. Обновите 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 25.07.2024 04:43

@krxbnx Ах, понятно, я обновлю инициализатор ленивого состояния, чтобы использовать переданную id опору. Код остается очень похожим.

Drew Reese 25.07.2024 04:46

Здравствуйте, спасибо за помощь. Я снова попытался console.info результаты функции randomAirport, и да, она возвращает случайные данные. Но дело в том, что модальное окно не обновляет свое содержимое на основе возвращаемых данных аэропорта.

krxbnx 25.07.2024 05:01

@krxbnx Модальному модалу это не нужно, ему просто нужно отобразить компонент AirportDetails, передать ему данные аэропорта и позволить ему самому определить, какой контент отображать. Вы визуализируете контент из состояния data, верно? Думаете, вы могли бы создать работающую CodeSandbox демо-версию вашего кода, которую мы могли бы проверить вживую?

Drew Reese 25.07.2024 05:04

вот ссылка на приложение: ссылка и демо-версия кода: ссылка

krxbnx 25.07.2024 05:29

@krxbnx В этой песочнице при установке зависимостей есть ошибка, но мне может быть достаточно, чтобы создать минимальную песочницу. Я посмотрю что я могу сделать.

Drew Reese 25.07.2024 05:55

о, я попробовал еще раз, и единственное, что не обновляется при нажатии случайной кнопки, — это фоновое изображение, поскольку оно находится внутри компонента Modal, который зависит от данных из AirportDetails

krxbnx 25.07.2024 06:01

@krxbnx Хорошо, я обновил свой ответ, включив в него альтернативу, которая (IMO) является улучшением, поскольку она просто реагирует на параметр пути маршрута id и удаляет всю ненужную логику локального состояния и синхронизации. Пожалуйста, посмотрите, когда будет время.

Drew Reese 25.07.2024 07:27

Давайте продолжим обсуждение в чате.

krxbnx 25.07.2024 09:27

Привет @Drew, вот обновленная ссылка на мою песочницу. Пожалуйста, дайте мне знать, если вы снова встретите какую-либо ошибку. Я вернул свои коды обратно к первому предложенному вами решению.

krxbnx 25.07.2024 09:30

Я только что включил другой <div>s в компонент AirportDetails, поэтому он также будет обновляться при нажатии случайной кнопки. Спасибо за помощь! Однако ваше второе решение на данный момент у меня не сработало, поскольку компонент Modal не отображается после нажатия случайной кнопки.

krxbnx 25.07.2024 10:52

Чтобы повторно отобразить модальный компонент, вам необходимо обновить текущее состояние в модальном компоненте и передать это состояние нужному компоненту. В вашем проекте, который является компонентом 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>

Другие вопросы по теме