Элемент имеет нулевые размеры при вызове offsetWidth offsetHeight в componenDidMount React
Необходимо в жизненном цикле ComponentDidMount компонента получить размеры элемента, для этого в методе render() я повесил ссылку на элемент:
public render() {
return (
<div ref={this.gridRef} id="grid-widget" className="h-100 overflow-hidden w-100 p-0 d-flex flex-column">
{this.renderGrid()}
</div>
);
}
В ComponentDidMount получаю элемент и беру его размеры:
public componentDidMount() {
this.props.recalculateGrid({
width: this.gridRef.current.offsetWidth,
height: this.gridRef.current.offsetHeight
});
}
Но дело в том, что в данный момент размеры элемента w = 0 и h = 0. Хотя если открыть разметку, то элемент имеет размеры. Причем до определенного момента это работало, после изменения стилей это сломалось, к сожалению, конкретное изменение сложно найти, но хотелось бы понять, почему так вообще происходит. Можно ли повесить на элемент какой то коллбэк, который сработает тогда, когда у элемента будут размеры?
Ответы (5 шт):
Вероятно вызов метода offsetWidth происходит до того как у элемента действительно появляются размеры отличные от нуля (проще говоря слишком рано).
Что бы это проверить попробуйте завернуть вашу логику по вычислению размера в setTimeout и отложите выполнение на пару секунд.
Если дело действительно в этом то при использовании хуков можно обезопасить себя от такого следующим образом:
const gridRef = useRef()
useEffect(() => {
const width = gridRef.current.offsetWidth
console.log(width)
}, [gridRef])
return <div ref={gridRef}></div>
Дело явно в стилях или в том, что соседние компоненты делают что-то асинхронно. Для того, чтобы сказать, что конкретно не так, в вопросе кода недостаточно. Обрати внимание, что хотя componentDidMount вызывается когда текущий элемент примонтирован, может оказаться, что последующие элементы (sibling) ещё нет (у меня с ангуляром была такая проблема).
Что касается второй части вопроса - можно ли отследить изменение размеров dom-элемента: в современных браузерах имеется ResizeObserver, который предназначен именно для этого. Но это не означает, что стоит его бросаться использовать - вполне допускаю, что есть более хорошее решение, задействующее только css.
React никак не отвечает за отрисовку стилей. цикл componentDidMount сработает тогда, когда у компонента будут смонтирована разметка и его логика. за отрисовку и перерисовку отвечают графические движки (например, WebKit) и сам JS движок (его EventLoop). это процесс банально называется repaint/redraw.
в результате, теоретически, EventLoop первоначально отрисовывает Ваш компонент, ставит в очередь componentDidMount и только после этого делает redraw Вашего документа
Я решил свою проблему. Пробовал useEffect это не помогло. Пробовал использовать setTimeout, иногда помогает, иногда вываливаются ошибки связанные с рекурсией. Пробовал MutationObserver, но, к сожалению, он не реагирует на изменение размеров блока.
Мне пришлось откатывать коммиты и смотреть когда этот баг пропадет. Хочу обратить внимание, что баг воспроизводится не с первого раза, иногда нужно несколько раз перезагрузить страницу.
Компонент Grid, с которым у меня была проблема лежит в контейнере. Помимо него в этом контейнере есть еще два компонента. Все три компонента я импортировал в контейнер с помощью lazy import. Я переписал это на обычные импорты.
Сам компонент контейнера вместе с другими компонентами (достаточно простыми) так же импортировался с помощью lazy import. Импорт контейнера я оставил на lazy, а вот у компонентов, которые импортировались рядом с этим контейнером я убрал lazy импорт и это помогло.
Но таким решением я не очень доволен, оно не чувствуется надежным. Так что я продолжаю поиски более надежного метода.
- Попробуйте использовать
useLayoutEffect - Попробуйте использовать библиотеку react-resize-detector