Skip to content

Instantly share code, notes, and snippets.

@doubleedesign
Last active March 31, 2023 02:20
Show Gist options
  • Save doubleedesign/ffb593f0301198812868f19512f8d873 to your computer and use it in GitHub Desktop.
Save doubleedesign/ffb593f0301198812868f19512f8d873 to your computer and use it in GitHub Desktop.
useResize React hook. Dynamically get height and width of an element when its size changes and store the values in state. Demo: https://codesandbox.io/s/useresize-demo-de04o8?file=/src/hooks/useResize.ts
import { MutableRefObject, useMemo, useEffect, useState } from 'react';
interface Dimensions {
width: number;
height: number;
}
export function useResize(ref: MutableRefObject<HTMLElement | undefined>, deps: unknown[]): Dimensions {
const [width, setWidth] = useState<number>(0);
const [height, setHeight] = useState<number>(0);
const observer = useMemo(() => {
return new ResizeObserver((entries) => {
// Using the scrollWidth and scrollHeight of the target ensures this works with CSS transitions
// because it accounts for the height of the content before it's visually fully expanded, which elements[0].contentRect does not
setWidth(entries[0].target.scrollWidth);
setHeight(entries[0].target.scrollHeight);
});
}, []);
useEffect(() => {
if (ref.current) {
observer.observe(ref.current);
}
}, [deps, observer, ref]);
return { width, height };
}
import { render, renderHook } from '@testing-library/react';
import React, { MutableRefObject } from 'react';
import { useResize } from './useResize';
function TestElement() {
return (<div data-testid="TestElement">Test content</div>);
}
describe('useResize', () => {
const { container } = render(<TestElement />);
const element: HTMLElement | undefined = container.querySelector('[data-testid="TestElement"]') as HTMLElement || undefined;
const testRef: MutableRefObject<HTMLElement | undefined> = {
current: element
};
const mockResult = { width: 0, height: 0 };
const mockObserve = jest.fn();
beforeEach(() => {
global.ResizeObserver = jest.fn().mockImplementation(() => ({
observe: mockObserve.mockImplementation((element) => {
mockResult.width = element.scrollWidth;
mockResult.height = element.scrollHeight;
})
}));
});
afterEach(() => {
jest.resetAllMocks();
});
it('calls the observer', () => {
renderHook(() => useResize(testRef, []));
expect(mockObserve).toHaveBeenCalledWith(element);
});
it('gets width and height', () => {
jest.spyOn(element, 'scrollHeight', 'get').mockReturnValue(30);
jest.spyOn(element, 'scrollWidth', 'get').mockReturnValue(30);
renderHook(() => useResize(testRef, []));
expect(mockResult).toEqual({ width: 30, height: 30 });
});
it('detects size change', () => {
jest.spyOn(element, 'scrollHeight', 'get').mockReturnValue(30);
jest.spyOn(element, 'scrollWidth', 'get').mockReturnValue(30);
const { rerender } = renderHook(() => useResize(testRef, []));
expect(mockResult).toEqual({ width: 30, height: 30 });
jest.spyOn(element, 'scrollHeight', 'get').mockReturnValue(50);
jest.spyOn(element, 'scrollWidth', 'get').mockReturnValue(50);
rerender();
expect(mockResult).toEqual({ width: 50, height: 50 });
expect(mockObserve).toHaveBeenCalledTimes(2);
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment