Обновление состояния в компоненте React, вызывающее его размонтирование

У меня есть компонент, в котором мне нужно получить некоторые данные и отобразить их. Сначала компонент визуализируется. Проблема, с которой я сталкиваюсь, заключается в том, что когда функция обработчика switchDocumentType вызывается после нажатия кнопки для определенного типа, весь компонент размонтируется/не визуализируется.

Во время самостоятельной отладки я обнаружил, что это происходит после того, как setDocumentType запускается внутри функции обработчика событий. Что не так в приведенном ниже фрагменте кода, что может вызвать эту проблему? Я вижу, что useEffect также не входит в бесконечный цикл.

Фрагмент кода:

import * as React from 'react';

const MyComponent = (props) => {
  const [documentType, setDocumentType] = React.useState('alpha');
  const [documentData, setDocumentData] = React.useState('');
  const types = ['alpha', 'beta', 'gamma'];

  React.useEffect(() => {
    myDataFetch('https://example.com/foo/?bar=123').then(async (response) => {
      const data = await response.json();
      setDocumentData(data.terms); // html string
      const myDiv = document.getElementById('spacial-div');
      myDiv.innerHTML = data; // need to render raw HTML inside a div
    });
  }, [documentType]);

  const switchDocumentType = (type) => {
    setDocumentType(type);
    // send some analytics events
  };

  const convertToPDF = () => {
    // uses documentData to generate PDF
  };

  return (
    <div className = "container-div">
      {types.map((type) => {
        return (
          <button key = {type} onClick = {(type) => switchDocumentType(type)}>
            {type}
          </button>
        );
      })}
      <div id = "special-div" />
    </div>
  );
};

export default MyComponent;

Компоненты перерисовываются, когда их состояние обновляется. Эффекты запускаются при обновлении их зависимостей. Это ожидаемое поведение. Ваш ответ JSON или HTML? Я не могу сказать по опубликованному коду.

Hunter McMillen 03.12.2022 18:10

Если вы не используете documentData, то прокомментируйте setDocumentData(data) внутри useEffect, потому что установка состояния внутри useEffect приведет к повторному рендерингу компонента, в идеале ответ должен возвращать данные json, которые можно использовать для рендеринга элементов в React.

Azzy 03.12.2022 18:13

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

Hunter McMillen 03.12.2022 18:19

@Azzy обновил фрагмент кода, чтобы показать, как я использую состояние documentData. Кроме того, проблема заключается не только в том, что HTML, который я получил, удаляется, весь компонент вместе с кнопками удаляется из дерева компонентов в соответствии с моим наблюдением в инструментах React-Dev (повторный рендеринг не должен вызывать этого).

EternalObserver 03.12.2022 18:41

@HunterMcMillen обязательно изменит его на dangerouslySetInnerHTML в реальном коде.

EternalObserver 03.12.2022 18:44

не используйте опасно SetInnerHTML в дереве реакции...

sheepiiHD 03.12.2022 19:01

Замена setDocumentData(data.terms) на documentDataRef.current = data.terms с помощью useRef вот так const documentDataRef = useRef('') решит проблему в вашем случае

Azzy 03.12.2022 19:30

@Azzy просто предположение, но я не думаю, что использование ref сработает. Во время отладки я мог обнаружить проблему, заключающуюся в том, что внутри функции обработчика, как только вызывается `setDocumentType`, весь компонент размонтируется. Шутка моего вопроса в том, почему обновление внутреннего состояния компонента приводит к тому, что он размонтируется и не перерисовывается?

EternalObserver 03.12.2022 19:38

Как правило, повторный рендеринг родителя вызывает размонтирование дочернего элемента, но я не вижу, чтобы какой-либо обратный вызов вызывался из родителя для изменения его состояния, а useEffect запускается дважды в режиме dev stritct, мне было интересно, вызывает ли настройка innerHTML его размонтирование,

Azzy 03.12.2022 19:46

Как вы думаете, какой-то родительский компонент также реагирует на щелчок

Azzy 03.12.2022 19:50

@Azzy нет, в этом случае родительский компонент не зависит от состояния дочернего, это было первое, что я проверил.

EternalObserver 03.12.2022 19:54

Вы пробовали event.preventDefault, как указано в ответе ниже.

iaq 04.12.2022 10:12

@iaq да, это не влияет

EternalObserver 04.12.2022 10:14

@EternalObserver проблема кажется интересной, можно ли создать минимальный воспроизводимый пример, если вы можете понять проблему, опубликуйте решение, мне любопытно узнать, в чем причина

Azzy 04.12.2022 10:32

@Аззи конечно. Хотя теперь я подозреваю, что проблема может быть связана с этим (stackoverflow.com/questions/56442582/…). Возврат функции очистки из useEffect решил мою проблему.

EternalObserver 04.12.2022 12:28
Поведение ключевого слова "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) для оценки ваших знаний,...
1
15
94
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Не используйте useEffect в качестве обработчика, используйте хуки useEffect для инициализации. Вместо использования/установки innerHtml пусть реакция сделает это за вас. Я предполагаю, что вы где-то определили myDataFetch, и я не вижу, чтобы ваша выборка данных использовала этот тип.

В любом случае, попробуйте использовать модифицированный код ниже.

   import * as React from 'react';

const MyComponent = (props) => {
  const [documentType, setDocumentType] = React.useState('alpha');
  const [documentData, setDocumentData] = React.useState('');
  const types = ['alpha', 'beta', 'gamma'];

  const fetchData = async () => {
    const response = await myDataFetch('https://example.com/foo/?bar=123')
    const data = await response.json();
    setDocumentData(data);
  }

  React.useEffect(() => {
    fetchData();
  }, []);

  const switchDocumentType = async (e, type) => {
    e.preventDefault();
    setDocumentType(type);
    await fetchData();
    // send some analytics events
  };

  return (
    <div className = "container-div">
      {types.map((type) => {
        return (
          <button key = {type} onClick = {(e) => switchDocumentType(e, type)}>
            {type}
          </button>
        );
      })}
      <div id = "special-div">{documentData}</div>
    </div>
  );
};

export default MyComponent;

Даже после рефакторинга кода, как вы упомянули, проблема все еще сохраняется.

EternalObserver 03.12.2022 19:09

Можете ли вы проверить на вкладке сети инструментов разработчика браузера, что вы получаете в ответных данных после нажатия кнопки? Также вам не нужен тип параметра для вашего обработчика onclick. Я изменил код. Попробуй еще раз.

iaq 03.12.2022 19:18

да, я получаю ответ.

EternalObserver 03.12.2022 19:21

Попробуйте сейчас, я обновил код. Добавлен preventDefault с помощью события и удален тип в качестве параметра

iaq 03.12.2022 19:28

Вы не должны редактировать DOM напрямую. В React есть два DOM: виртуальный DOM и реальный DOM. Рендеринг может быть немного привередливым, если вы решите отредактировать настоящий DOM.

Вы можете безопасно разбирать html, используя html-react-parser . Это лучший способ сделать это, потому что он становится частью дерева реагирования, тогда как опасноSetInnerHTML заменит весь HTML, чтобы сбросить изменения в DOM. С согласованием это может привести к экспоненциальному увеличению времени загрузки.

Это также будет дезинфицировать ваши входы, вы знаете... для безопасности. :)

import parse from 'html-react-parser';

const SpecialDiv = ({html}) => {
   const reactElement = parse(html);
   return reactElement
}

Если вы решили, что должны использовать dangerouslySetInnerHTML, вы можете сделать это так:

const [someHTML, setSomeHTML] = useState(null)

const someFunction = async() => {
   const response = await getData();
   const data = await response.json();

   setSomeHTML(data);
}

return( 
   <div>
      {someHTML && <div dangerouslySetInnerHTML = {{__html: someHTML}} id = "special-div"/>}
   </div>
)

При этом я бы сказал, что, позволяя это, вы открываете себя для возможности XSS-атаки без надлежащего разбора и очистки ваших входных данных.

Я все это знаю, но думаю, что это не имеет отношения к моей проблеме. Кроме того, у меня нет возможности добавить еще одну зависимость в свой код только для добавления небольшого HTML-контента.

EternalObserver 03.12.2022 19:12

Вы можете использовать setDangerouslySetInnerHTML, просто это не рекомендуется по вышеуказанным причинам.

sheepiiHD 03.12.2022 19:14
Ответ принят как подходящий

Не знаю, почему, но размещение отладчиков перед обновлением состояния вызывает эту проблему не только для этого компонента, но и для всех других компонентов, с которыми я пробовал. Кажется, проблема либо с отладчиком, либо с React. Удаление отладчиков решило проблему.

Кроме того, теперь я возвращаю функцию очистки внутри useEffect, как указано в некоторых сообщениях о переполнении стека. Я также провел рефакторинг кода, предложенный @iaq и @sheepiiHD, чтобы следовать рекомендациям React.

Обновленный код:

import * as React from 'react';

const MyComponent = (props) => {
  const [documentType, setDocumentType] = React.useState('alpha');
  const [documentData, setDocumentData] = React.useState('');
  const types = ['alpha', 'beta', 'gamma'];

  const fetchData = async () => {
    const response = await myDataFetch('https://example.com/foo/?bar=123')
    const data = await response.json();
    setDocumentData(data);
  }

  React.useEffect(() => {
    fetchData();
    return () => {
      setDocumentType('');
      setDocumentData('');
    };
  }, []);

  const switchDocumentType = async (e, type) => {
    e.preventDefault();
    setDocumentType(type);
    await fetchData();
    // send some analytics events
  };

  return (
    <div className = "container-div">
      {types.map((type) => {
        return (
          <button key = {type} onClick = {(e) => switchDocumentType(e, type)}>
            {type}
          </button>
        );
      })}
      <div id = "special-div" dangerouslySetInnerHTML = {{__html: documentData.terms}} />
    </div>
  );
};

export default MyComponent;

Я предполагаю, что что-то смешное в вашем documentData.terms вызывает эту проблему. Если вызывается функция очистки, это означает, что вы размонтированы. Убедитесь, что у вас может быть элемент с корневым идентификатором в вашем documentData.terms или некоторые другие конфликтующие теги элементов. Попробуйте использовать значение напрямую, а не как html, например <div>{document.terms}</div>

iaq 05.12.2022 00:37

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