From 3eb531ac9e9e04641df29aafbd2677869cdcb085 Mon Sep 17 00:00:00 2001 From: Mahmoud El-Gammal Date: Mon, 2 Dec 2024 16:51:00 +0200 Subject: [PATCH 1/2] feat: add onChange callback to useWindowSize --- docs/useWindowSize.md | 15 +++++++++++++++ src/useWindowSize.ts | 17 ++++++++++++++++- tests/useWindowSize.test.tsx | 29 ++++++++++++++++++++++++++--- 3 files changed, 57 insertions(+), 4 deletions(-) diff --git a/docs/useWindowSize.md b/docs/useWindowSize.md index e84501cf3b..836d8c10ec 100644 --- a/docs/useWindowSize.md +++ b/docs/useWindowSize.md @@ -19,3 +19,18 @@ const Demo = () => { ); }; ``` + +## Reference + +```js +useWindowSize(options); +``` + +- `initialWidth` — Initial width value for non-browser environments. +- `initialHeight` — Initial height value for non-browser environments. +- `onChange` — Callback function triggered when the window size changes. + +## Related hooks + +- [useSize](./useSize.md) +- [useMeasure](./useMeasure.md) \ No newline at end of file diff --git a/src/useWindowSize.ts b/src/useWindowSize.ts index 3137307e3a..45f82de7c8 100644 --- a/src/useWindowSize.ts +++ b/src/useWindowSize.ts @@ -3,7 +3,17 @@ import { useEffect } from 'react'; import useRafState from './useRafState'; import { isBrowser, off, on } from './misc/util'; -const useWindowSize = (initialWidth = Infinity, initialHeight = Infinity) => { +interface Options { + initialWidth?: number; + initialHeight?: number; + onChange?: (width: number, height: number) => void; +} + +const useWindowSize = ({ + initialWidth = Infinity, + initialHeight = Infinity, + onChange, +}: Options = {}) => { const [state, setState] = useRafState<{ width: number; height: number }>({ width: isBrowser ? window.innerWidth : initialWidth, height: isBrowser ? window.innerHeight : initialHeight, @@ -12,10 +22,15 @@ const useWindowSize = (initialWidth = Infinity, initialHeight = Infinity) => { useEffect((): (() => void) | void => { if (isBrowser) { const handler = () => { + const width = window.innerWidth; + const height = window.innerHeight; + setState({ width: window.innerWidth, height: window.innerHeight, }); + + if (onChange) onChange(width, height); }; on(window, 'resize', handler); diff --git a/tests/useWindowSize.test.tsx b/tests/useWindowSize.test.tsx index dc24244328..da5646f165 100644 --- a/tests/useWindowSize.test.tsx +++ b/tests/useWindowSize.test.tsx @@ -21,8 +21,8 @@ describe('useWindowSize', () => { expect(useWindowSize).toBeDefined(); }); - function getHook(...args) { - return renderHook(() => useWindowSize(...args)); + function getHook(options?: any) { + return renderHook(() => useWindowSize(options)); } function triggerResize(dimension: 'width' | 'height', value: number) { @@ -44,7 +44,7 @@ describe('useWindowSize', () => { }); it('should use passed parameters as initial values in case of non-browser use', () => { - const hook = getHook(1, 1); + const hook = getHook({ initialWidth: 1, initialHeight: 1 }); expect(hook.result.current.height).toBe(isBrowser ? window.innerHeight : 1); expect(hook.result.current.width).toBe(isBrowser ? window.innerWidth : 1); @@ -85,4 +85,27 @@ describe('useWindowSize', () => { expect(hook.result.current.width).toBe(2048); }); + + it('should call onChange callback on window resize', () => { + const onChange = jest.fn(); + getHook({ onChange }); + + act(() => { + triggerResize('width', 720); + triggerResize('height', 480); + requestAnimationFrame.step(); + }); + + expect(onChange).toHaveBeenCalledWith(720, 480); + expect(onChange).toHaveBeenCalledTimes(2); + + act(() => { + triggerResize('width', 1920); + triggerResize('height', 1080); + requestAnimationFrame.step(); + }); + + expect(onChange).toHaveBeenCalledWith(1920, 1080); + expect(onChange).toHaveBeenCalledTimes(4); + }); }); From ea656f7e751b8366360ce2aa8238057bbbc1251a Mon Sep 17 00:00:00 2001 From: Mahmoud El-Gammal Date: Mon, 2 Dec 2024 17:02:44 +0200 Subject: [PATCH 2/2] feat: add onChange callback to useWindowSize --- src/useWindowSize.ts | 18 +++++++++++++----- stories/useWindowSize.story.tsx | 6 +++++- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/useWindowSize.ts b/src/useWindowSize.ts index 45f82de7c8..10d9402767 100644 --- a/src/useWindowSize.ts +++ b/src/useWindowSize.ts @@ -3,10 +3,11 @@ import { useEffect } from 'react'; import useRafState from './useRafState'; import { isBrowser, off, on } from './misc/util'; +// Define the type for options that can be passed to the hook interface Options { - initialWidth?: number; - initialHeight?: number; - onChange?: (width: number, height: number) => void; + initialWidth?: number; // Initial width of the window (Default value is Infinity) + initialHeight?: number; // Initial height of the window (Default value is Infinity) + onChange?: (width: number, height: number) => void; // Callback function to execute on window resize (optional) } const useWindowSize = ({ @@ -14,33 +15,40 @@ const useWindowSize = ({ initialHeight = Infinity, onChange, }: Options = {}) => { + // Use the useRafState hook to maintain the current window size (width and height) const [state, setState] = useRafState<{ width: number; height: number }>({ width: isBrowser ? window.innerWidth : initialWidth, height: isBrowser ? window.innerHeight : initialHeight, }); useEffect((): (() => void) | void => { + // Only run the effect on the browser (to avoid issues with SSR) if (isBrowser) { const handler = () => { const width = window.innerWidth; const height = window.innerHeight; + // Update the state with the new window size setState({ - width: window.innerWidth, - height: window.innerHeight, + width, + height, }); + // If an onChange callback is provided, call it with the new dimensions if (onChange) onChange(width, height); }; + // Add event listener for the resize event on(window, 'resize', handler); + // Cleanup function to remove the event listener when the component is unmounted (it's for performance optimization) return () => { off(window, 'resize', handler); }; } }, []); + // Return the current window size (width and height) return state; }; diff --git a/stories/useWindowSize.story.tsx b/stories/useWindowSize.story.tsx index 663b483a69..3265057d1c 100644 --- a/stories/useWindowSize.story.tsx +++ b/stories/useWindowSize.story.tsx @@ -1,10 +1,14 @@ import { storiesOf } from '@storybook/react'; import * as React from 'react'; import { useWindowSize } from '../src'; +import { action } from '@storybook/addon-actions'; // Import addon-actions import ShowDocs from './util/ShowDocs'; const Demo = () => { - const { width, height } = useWindowSize(); + const { width, height } = useWindowSize({ + // Log the resize event to the Storybook actions panel + onChange: action('window resize'), + }); return (