Skip to content

Commit

Permalink
Merge pull request #2608 from mahmoud-elgammal/feature/useWindowSize-…
Browse files Browse the repository at this point in the history
…onChange

feat: add onChange callback to useWindowSize (#915)
  • Loading branch information
streamich authored Dec 9, 2024
2 parents 7c4d696 + ea656f7 commit 7602956
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 7 deletions.
15 changes: 15 additions & 0 deletions docs/useWindowSize.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
29 changes: 26 additions & 3 deletions src/useWindowSize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,52 @@ import { useEffect } from 'react';
import useRafState from './useRafState';
import { isBrowser, off, on } from './misc/util';

const useWindowSize = (initialWidth = Infinity, initialHeight = Infinity) => {
// Define the type for options that can be passed to the hook
interface Options {
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 = ({
initialWidth = Infinity,
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;
};

Expand Down
6 changes: 5 additions & 1 deletion stories/useWindowSize.story.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div>
Expand Down
29 changes: 26 additions & 3 deletions tests/useWindowSize.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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);
Expand Down Expand Up @@ -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);
});
});

0 comments on commit 7602956

Please sign in to comment.