У меня есть компонент, в котором мне нужно получить некоторые данные и отобразить их. Сначала компонент визуализируется. Проблема, с которой я сталкиваюсь, заключается в том, что когда функция обработчика 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;
Если вы не используете documentData, то прокомментируйте setDocumentData(data) внутри useEffect, потому что установка состояния внутри useEffect приведет к повторному рендерингу компонента, в идеале ответ должен возвращать данные json, которые можно использовать для рендеринга элементов в React.
Вы также должны использовать опасноSetInnerHTML вместо того, чтобы напрямую манипулировать DOM.
@Azzy обновил фрагмент кода, чтобы показать, как я использую состояние documentData. Кроме того, проблема заключается не только в том, что HTML, который я получил, удаляется, весь компонент вместе с кнопками удаляется из дерева компонентов в соответствии с моим наблюдением в инструментах React-Dev (повторный рендеринг не должен вызывать этого).
@HunterMcMillen обязательно изменит его на dangerouslySetInnerHTML в реальном коде.
не используйте опасно SetInnerHTML в дереве реакции...
Замена setDocumentData(data.terms) на documentDataRef.current = data.terms с помощью useRef вот так const documentDataRef = useRef('') решит проблему в вашем случае
@Azzy просто предположение, но я не думаю, что использование ref сработает. Во время отладки я мог обнаружить проблему, заключающуюся в том, что внутри функции обработчика, как только вызывается `setDocumentType`, весь компонент размонтируется. Шутка моего вопроса в том, почему обновление внутреннего состояния компонента приводит к тому, что он размонтируется и не перерисовывается?
Как правило, повторный рендеринг родителя вызывает размонтирование дочернего элемента, но я не вижу, чтобы какой-либо обратный вызов вызывался из родителя для изменения его состояния, а useEffect запускается дважды в режиме dev stritct, мне было интересно, вызывает ли настройка innerHTML его размонтирование,
Как вы думаете, какой-то родительский компонент также реагирует на щелчок
@Azzy нет, в этом случае родительский компонент не зависит от состояния дочернего, это было первое, что я проверил.
Вы пробовали event.preventDefault, как указано в ответе ниже.
@iaq да, это не влияет
@EternalObserver проблема кажется интересной, можно ли создать минимальный воспроизводимый пример, если вы можете понять проблему, опубликуйте решение, мне любопытно узнать, в чем причина
@Аззи конечно. Хотя теперь я подозреваю, что проблема может быть связана с этим (stackoverflow.com/questions/56442582/…). Возврат функции очистки из useEffect решил мою проблему.



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Не используйте 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;
Даже после рефакторинга кода, как вы упомянули, проблема все еще сохраняется.
Можете ли вы проверить на вкладке сети инструментов разработчика браузера, что вы получаете в ответных данных после нажатия кнопки? Также вам не нужен тип параметра для вашего обработчика onclick. Я изменил код. Попробуй еще раз.
да, я получаю ответ.
Попробуйте сейчас, я обновил код. Добавлен preventDefault с помощью события и удален тип в качестве параметра
Вы не должны редактировать 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-контента.
Вы можете использовать setDangerouslySetInnerHTML, просто это не рекомендуется по вышеуказанным причинам.
Не знаю, почему, но размещение отладчиков перед обновлением состояния вызывает эту проблему не только для этого компонента, но и для всех других компонентов, с которыми я пробовал. Кажется, проблема либо с отладчиком, либо с 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>
Компоненты перерисовываются, когда их состояние обновляется. Эффекты запускаются при обновлении их зависимостей. Это ожидаемое поведение. Ваш ответ JSON или HTML? Я не могу сказать по опубликованному коду.