Skip to content

Instantly share code, notes, and snippets.

@drianoaz
Created June 2, 2021 22:38
Show Gist options
  • Save drianoaz/be29bb6189e28ac341f9e3a840182a93 to your computer and use it in GitHub Desktop.
Save drianoaz/be29bb6189e28ac341f9e3a840182a93 to your computer and use it in GitHub Desktop.
An optimized react hook for window resize
import { renderHook } from '@testing-library/react-hooks';
import { useWindowResize } from './useWindowResize';
jest.useFakeTimers();
describe('useWindowResize', () => {
it('should run the callback when the window is resized', () => {
const mock = jest.fn();
renderHook(() => useWindowResize(mock));
global.dispatchEvent(new Event('resize'));
jest.runAllTimers();
expect(mock).toHaveBeenCalledTimes(1);
});
describe('trottle', () => {
it('should run the callback', () => {
const mock = jest.fn();
renderHook(() => useWindowResize(mock));
global.dispatchEvent(new Event('resize'));
jest.advanceTimersByTime(50);
global.dispatchEvent(new Event('resize'));
jest.advanceTimersByTime(50);
global.dispatchEvent(new Event('resize'));
jest.advanceTimersByTime(50);
global.dispatchEvent(new Event('resize'));
jest.runAllTimers();
expect(mock).toHaveBeenCalledTimes(1);
});
it('should be able to resize many times', () => {
const mock = jest.fn();
renderHook(() => useWindowResize(mock));
global.dispatchEvent(new Event('resize'));
jest.runAllTimers();
global.dispatchEvent(new Event('resize'));
jest.runAllTimers();
expect(mock).toHaveBeenCalledTimes(2);
});
});
});
import { useCallback, useEffect, useMemo, useRef } from 'react';
const FPS_15 = 66;
/**
* run the callback for each window resize
*
* @see https://developer.mozilla.org/pt-BR/docs/Web/API/Window/resize_event
* @param callback the method that will be called;
*/
export const useWindowResize = (callback: (event: Event) => void) => {
const requestRef = useRef<number | undefined>(undefined);
const memoizedCallback = useMemo(() => {
return callback;
/**
* As an expected behavior, the memoized callback must never be changed to
* prevent it being called twice immediately. Because of this, the callback
* can't be inside the useMemo dependencies and the lint rule must be disabled
*/
}, []); // eslint-disable-line react-hooks/exhaustive-deps
const resizeTrottler = useCallback(
(event: UIEvent) => {
window.clearTimeout(requestRef.current);
requestRef.current = window.setTimeout(() => {
requestRef.current = undefined;
memoizedCallback(event);
}, FPS_15);
},
[memoizedCallback],
);
useEffect(() => {
window.addEventListener('resize', resizeTrottler, false);
return () => {
window.removeEventListener('resize', resizeTrottler);
};
}, [memoizedCallback, resizeTrottler]);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment