Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add onChange callback to useWindowSize (#915) #2608

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
});
});
Loading