Skip to content

Commit 594f7b3

Browse files
fix(useElementSize): get element correct size + calc element size before browser paint
1 parent e37f65f commit 594f7b3

File tree

2 files changed

+29
-10
lines changed

2 files changed

+29
-10
lines changed

src/hooks/useElementSize/useElementSize.test.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ const mockResizeObserver = class ResizeObserver {
3232

3333
beforeEach(() => void vi.stubGlobal('ResizeObserver', mockResizeObserver));
3434
afterEach(() => void vi.unstubAllGlobals());
35+
afterEach(() => void vi.restoreAllMocks());
3536

3637
const targets = [
3738
undefined,
@@ -65,7 +66,11 @@ targets.forEach((target) => {
6566
return useElementSize<HTMLDivElement>({ width: 200, height: 200 });
6667
});
6768

68-
expect(result.current.value).toStrictEqual({ width: 200, height: 200 });
69+
if (!target) {
70+
expect(result.current.value).toStrictEqual({ width: 200, height: 200 });
71+
} else {
72+
expect(result.current.value).toStrictEqual({ width: 0, height: 0 });
73+
}
6974
});
7075

7176
it('Should change value after resize', () => {
@@ -86,6 +91,10 @@ targets.forEach((target) => {
8691
const element = (target ? getElement(target) : result.current.ref.current) as Element;
8792
if (!element) return;
8893

94+
vi.spyOn(Element.prototype, 'getBoundingClientRect').mockImplementation(
95+
() => new DOMRect(0, 0, 200, 200)
96+
);
97+
8998
trigger.callback(element, [
9099
{ contentRect: { width: 200, height: 200 } }
91100
] as unknown as ResizeObserverEntry[]);

src/hooks/useElementSize/useElementSize.ts

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import type { RefObject } from 'react';
22

3-
import { useEffect, useState } from 'react';
3+
import { useState } from 'react';
44

55
import { getElement, isTarget } from '@/utils/helpers';
66

77
import type { StateRef } from '../useRefState/useRefState';
88

9+
import { useIsomorphicLayoutEffect } from '../useIsomorphicLayoutEffect/useIsomorphicLayoutEffect';
910
import { useRefState } from '../useRefState/useRefState';
1011

1112
/** The element size value type */
@@ -59,23 +60,32 @@ export interface UseElementSize {
5960
*/
6061
export const useElementSize = ((...params: any[]) => {
6162
const target = (isTarget(params[0]) ? params[0] : undefined) as UseElementSizeTarget | undefined;
62-
const initialValue = (target ? params[1] : params[0]) as UseElementSizeTarget | undefined;
63+
const initialValue = (target ? params[1] : params[0]) as UseElementSizeValue | undefined;
6364

6465
const [size, setSize] = useState(initialValue ?? { width: 0, height: 0 });
6566
const internalRef = useRefState<Element>();
6667

67-
useEffect(() => {
68-
if (!target && !internalRef.current) return;
68+
useIsomorphicLayoutEffect(() => {
6969
const element = (target ? getElement(target) : internalRef.current) as Element;
7070

7171
if (!element) return;
7272

73-
const observer = new ResizeObserver(([entry]) => {
74-
const { width, height } = entry.contentRect;
75-
setSize({ width, height });
76-
});
73+
const callback = () => {
74+
const rect = element.getBoundingClientRect();
75+
setSize((prev) => {
76+
if (prev.width !== rect.width || prev.height !== rect.height) {
77+
return {
78+
width: rect.width,
79+
height: rect.height
80+
};
81+
}
82+
return prev;
83+
});
84+
};
7785

78-
if (element) observer.observe(element);
86+
const observer = new ResizeObserver(callback);
87+
observer.observe(element);
88+
callback();
7989

8090
return () => {
8191
observer.disconnect();

0 commit comments

Comments
 (0)