Получить высоту из html и вставить в состояние

#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]);