#javascript #reactjs
#javascript #reactjs
Вопрос:
Мне нужно составить список комментариев. Я получаю html из серверной части. Когда высота блока комментариев высока, мне нужно обрезать блок, используя свойство max-height . Проблема в том, что я не могу получить эту высоту из серверной части, я получаю ее из html, который я вставляю, то есть после рендеринга. Оказывается, мне нужно сделать два рендеринга, первый происходит после получения комментариев, чтобы можно было вычислить высоту, второй происходит, когда я беру высоту и вставляю ее в состояние с комментариями.
Когда я вставляю состояние с высотой в список комментариев, возникает ошибка:
Ошибка: превышена максимальная глубина обновления. Это может произойти, когда компонент повторно вызывает setState внутри componentWillUpdate или componentDidUpdate . React ограничивает количество вложенных обновлений, чтобы предотвратить бесконечные циклы.
Как я могу решить эту проблему?
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import {CommentsList} from "./CommentsList";
const comments = [
{
id: '1',
author: 'Han Solo',
avatar: 'https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png',
content: '<p>Ddwe23f423<strong><em>fw</em></strong></p>',
date: 1607350845385,
},
{
id: '2',
author: 'Han11',
avatar: 'https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png',
content:
'<p>asdasad adfs dh d hadg</p><p>asdasad adfs dh d hadg</p><p>asdasad adfs dh d hadg</p><p>asdasad adfs dh d hadg</p><p>asdasad adfs dh d hadg</p><p>asdasad adfs dh d hadg</p><p>asdasad adfs dh d hadg</p><p>asdasad adfs dh d hadg</p><p>asdasad adfs dh d hadg</p><p>asdasad adfs dh d hadg</p><p>asdasad adfs dh d hadg</p><p>asdasad adfs dh d hadg</p><p>asdasad adfs dh d hadg</p><p>asdasad adfs dh d hadg</p><p>asdasad adfs dh d hadg</p><p>asdasad adfs dh d hadg</p>',
date: 1607342028321,
},
{
id: '3',
author: 'Han Solo',
avatar: 'https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png',
content: '<p>Ddwe23f42dddddddddd3<strong><em>fw</em></strong></p>',
date: 1607342028321,
},
{
id: '4',
author: 'Han Solo',
avatar: 'https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png',
content: '<p>asdasad adfs dh d hadg</p>',
date: 1607342028321,
},
]
const App = () => {
return (
<div>
<CommentsList comments={comments} />
</div>
);
}
ReactDOM.render(
<App/>,
document.querySelector('#root')
);
CommentsList.js
import React, {useEffect, useState} from 'react';
export function CommentsList({comments}) {
const [commentsList, commentsListSet] = useState([])
useEffect(() => {
commentsListSet(comments)
}, [comments])
const heightCheck = (height, id) => { // here second render I want insert height
commentsListSet(commentsList.map((comment) => { // Error here
if (comment.id === id) {
comment.height = height; // I need this height in commentsList for button "more"
}
return comment
}))
}
return <ul>
{commentsList.map(e => { // here first render
return <li key={e.id}>
<div
dangerouslySetInnerHTML={{__html: e.content}}
ref={(html) => html amp;amp; heightCheck(html.clientHeight, e.id)} // Here i get height from ref
/>
</li>
})}
</ul>
}
Ответ №1:
Ошибка, которую вы получаете, заключается в том, что вы вызываете установщик состояния в операторе return, что приводит к повторному рендерингу компонента, который снова вызывает установщик и снова повторно отображает ваш компонент, и это устанавливает цикл. Вместо этого вы можете инициализировать флаг, который содержит статус рендеринга и устанавливается при монтировании вашего компонента, как я сделал в приведенном ниже коде.
App.js
import React from "react";
import {CommentsList} from './CommentList';
export default function App() {
return (
<div className="App">
<CommentsList comments={[{id:1, content:'React is all about UI'}, {id:2, content:'UseEffect can be used as componentDidUpdate or componentDidMount'}]} />
</div>
);
}
CommentsList.js
import React, {useEffect, useState, useRef} from 'react';
export function CommentsList({comments}) {
const [commentsList, commentsListSet] = useState(comments)
const [htmlRendered, setHtmlRendered] = useState(false)
const commentItemsRef = useRef();
useEffect(() => {
// Work as componentDidMount
setHtmlRendered(true);
}, [])
const commentHeight = htmlRendered?commentItemsRef.current.clientHeight:0;
return (
<React.Fragment>
<div>Height: {commentHeight}</div>
<div ref={commentItemsRef}>
<ul>
{
commentsList.map((comment) => <li key={comment.id}>{comment.content}</li>)
}
</ul>
</div>
</React.Fragment>);
}
Надеюсь, это поможет. Дайте мне знать, если у вас возникнут какие-либо проблемы при запуске кода.
[Обновленный код] Здесь я рассчитал высоту каждого элемента списка, как вы это сделали.
import React, { useEffect, useState, useRef } from "react";
export function CommentsList({ comments }) {
const [commentsList, commentsListSet] = useState(comments);
const [htmlRendered, setHtmlRendered] = useState(false);
const commentItemsRef = useRef();
useEffect(() => {
// Work as componentDidMount
setHtmlRendered(true);
}, []);
const commentHeight = htmlRendered ? commentItemsRef.current.clientHeight : 0;
const renderedComments = [];
if (htmlRendered) {
const commentListItems = Array.from(
commentItemsRef.current.querySelectorAll("li")
);
commentListItems.forEach((item) => {
const commentid = item.getAttribute("commentid");
const content = item.innerHTML;
renderedComments.push({
id: commentid,
content: content,
height: item.clientHeight
});
});
// Final Rendered Comment that can be used
console.log(renderedComments);
}
return (
<React.Fragment>
<div>Height: {commentHeight}</div>
<div ref={commentItemsRef}>
<ul>
{commentsList.map((comment) => (
<li key={comment.id} commentid={comment.id}>
{comment.content}
</li>
))}
</ul>
</div>
</React.Fragment>
);
}
Комментарии:
1. в вашем примере вы вычисляете высоту одного блока, мне нужно вычислить в цикле, лучше попробуйте изменить мой пример, чтобы не было путаницы
2. Я обновил свой код в соответствии с тем, чего вы пытались достичь. Единственное, что я хочу проиллюстрировать из своего кода, это то, что вы не можете установить состояние внутри оператора return . Вы можете установить состояние только в обработчиках событий или использовать хук эффекта внутри функциональных компонентов. Я предоставил обходной путь для достижения этой цели.
3. и все же я не могу получить высоту в блоке li
Ответ №2:
Я не использую react, но возможна ли установка высоты строки? Вы можете проверить ок. количество строк и вычислить его в точке извлечения и соответствующим образом обрезать?
Комментарии:
1. нет, я не могу посчитать количество строк, потому что при разных разрешениях экрана будет разное количество строк
2. Вы должны иметь возможность устанавливать размер шрифта и высоту строки… В качестве альтернативы вы можете использовать div и установить скрытое переполнение.
3. Я не понимаю, как здесь использовать размер шрифта и высоту строки. Как я могу использовать div, если я не знаю, где это нужно, а где нет? потому что я получаю высоту после того, как вставляю данные в html
4. Я не знаю react, я надеюсь, что кто-то еще может вам помочь. Я предполагал, что вы знаете высоту div, т.Е. Поэтому Она фиксирована, и переполнение было бы скрыто. Еще одна идея, которая приходит на ум, — использовать mutation observer и проверить атрибут height, но я остановлюсь на этом, поскольку я могу загрязнять раздел ответов.
Ответ №3:
Я знаю, что этот ответ — не лучший способ. Лучше всего это делать с помощью css. Потому что, как только вы добавите это содержимое в dom, вам не нужно их удалять.
Но чтобы решить это с помощью javascript, вам нужно представить.
const [commentsList, commentsListSet] = useState(comments);
React.useEffect(() => {
const wrapper = document.createElement('div');
wrapper.style.pointerEvents = 'none';
wrapper.style.position = 'absolute';
wrapper.style.top = '-99999px';
wrapper.style.left = '-99999px';
const domItems = commentsList.map((item) => {
const domItem = document.createElement('div');
domItem.innerHTML = item.content;
return { item, domItem };
});
domItems.forEach((element) => {
wrapper.appendChild(element.domItem);
});
document.body.appendChild(wrapper);
commentsListSet(
domItems.map(({ item, domItem }) => ({
...item,
height: domItem.clientHeight,
}))
);
wrapper.remove();
}, [commentsList]);