Skip to content

Commit

Permalink
✨ Added debounce option to both useScreen and useWindowSize (#482)
Browse files Browse the repository at this point in the history
  • Loading branch information
juliencrn committed Feb 9, 2024
1 parent 7d74e09 commit d60f1c6
Show file tree
Hide file tree
Showing 7 changed files with 78 additions and 13 deletions.
5 changes: 5 additions & 0 deletions .changeset/sixty-planets-nail.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"usehooks-ts": minor
---

Added debounce option to both `useScreen` and `useWindowSize`
4 changes: 2 additions & 2 deletions packages/usehooks-ts/src/useScreen/useScreen.demo.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { useScreen } from './useScreen'

export default function Component() {
const { width = 0, height = 0 } = useScreen()
const screen = useScreen()

return (
<div>
The current window dimensions are:{' '}
<code>{JSON.stringify({ width, height })}</code>
<code>{JSON.stringify(screen, null, 2)}</code>
</div>
)
}
5 changes: 4 additions & 1 deletion packages/usehooks-ts/src/useScreen/useScreen.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
Easily retrieve `window.screen` object with this Hook React which also works onResize.

**Note**: If you use this hook in an SSR context, set the `initializeWithValue` option to `false`, it will initialize with `undefined`.
### Parameters

- `initializeWithValue?: boolean`: If you use this hook in an SSR context, set it to `false`, it will initialize with `undefined` (default `true`).
- `debounceDelay?: number`: The delay in milliseconds before the callback is invoked (disabled by default for retro-compatibility).
11 changes: 10 additions & 1 deletion packages/usehooks-ts/src/useScreen/useScreen.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { useState } from 'react'

import { useDebounceCallback } from '../useDebounceCallback'
import { useEventListener } from '../useEventListener'
import { useIsomorphicLayoutEffect } from '../useIsomorphicLayoutEffect'

type UseScreenOptions<InitializeWithValue extends boolean | undefined> = {
initializeWithValue: InitializeWithValue
debounceDelay?: number
}

const IS_SERVER = typeof window === 'undefined'
Expand All @@ -17,6 +19,7 @@ export function useScreen(options?: Partial<UseScreenOptions<true>>): Screen
* Custom hook for tracking the screen dimensions and properties.
* @param {?UseScreenOptions} [options] - The options for customizing the behavior of the hook (optional).
* @param {?boolean} [options.initializeWithValue] - If `true` (default), the hook will initialize reading the screen dimensions. In SSR, you should set it to `false`, returning `undefined` initially.
* @param {?number} [options.debounceDelay] - The delay in milliseconds before the state is updated (disabled by default for retro-compatibility).
* @returns {Screen | undefined} The current `Screen` object representing the screen dimensions and properties, or `undefined` if not available.
* @see [Documentation](https://usehooks-ts.com/react-hook/use-screen)
* @example
Expand Down Expand Up @@ -45,9 +48,15 @@ export function useScreen(
return undefined
})

const debouncedSetScreen = useDebounceCallback(
setScreen,
options?.debounceDelay,
)

/** Handles the resize event of the window. */
function handleSize() {
const newScreen = readScreen()
const setSize = options?.debounceDelay ? debouncedSetScreen : setScreen

if (newScreen) {
// Create a shallow clone to trigger a re-render (#280).
Expand All @@ -61,7 +70,7 @@ export function useScreen(
pixelDepth,
} = newScreen

setScreen({
setSize({
width,
height,
availHeight,
Expand Down
5 changes: 4 additions & 1 deletion packages/usehooks-ts/src/useWindowSize/useWindowSize.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
Easily retrieve window dimensions with this React Hook which also works onResize.

**Note**: If you use this hook in an SSR context, set the `initializeWithValue` option to `false`.
### Parameters

- `initializeWithValue?: boolean`: If you use this hook in an SSR context, set it to `false` (default `true`)
- `debounceDelay?: number`: The delay in milliseconds before the callback is invoked (disabled by default for retro-compatibility).
46 changes: 40 additions & 6 deletions packages/usehooks-ts/src/useWindowSize/useWindowSize.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ import { act, renderHook } from '@testing-library/react'

import { useWindowSize } from './useWindowSize'

const setupHook = () => renderHook(() => useWindowSize())

const windowResize = (dimension: 'width' | 'height', value: number): void => {
if (dimension === 'width') {
window.innerWidth = value
Expand All @@ -16,20 +14,32 @@ const windowResize = (dimension: 'width' | 'height', value: number): void => {
window.dispatchEvent(new Event('resize'))
}

describe('useElementSize()', () => {
describe('useWindowSize()', () => {
beforeEach(() => {
vi.clearAllMocks()
vi.useFakeTimers() // Mock timers

// Set the initial window size
windowResize('width', 1920)
windowResize('height', 1080)
})

it('should initialize', () => {
const { result } = setupHook()
const { result } = renderHook(() => useWindowSize())
const { height, width } = result.current
expect(typeof height).toBe('number')
expect(typeof width).toBe('number')
expect(result.current.width).toBe(1920)
expect(result.current.height).toBe(1080)
})

it('should return the corresponding height', () => {
const { result } = setupHook()
const { result } = renderHook(() => useWindowSize())

act(() => {
windowResize('height', 420)
})

expect(result.current.height).toBe(420)

act(() => {
Expand All @@ -40,7 +50,8 @@ describe('useElementSize()', () => {
})

it('should return the corresponding width', () => {
const { result } = setupHook()
const { result } = renderHook(() => useWindowSize())

act(() => {
windowResize('width', 420)
})
Expand All @@ -53,4 +64,27 @@ describe('useElementSize()', () => {

expect(result.current.width).toBe(2196)
})

it('should debounce the callback', () => {
const { result } = renderHook(() => useWindowSize({ debounceDelay: 100 }))

expect(result.current.width).toBe(1920)
expect(result.current.height).toBe(1080)

act(() => {
windowResize('width', 2196)
windowResize('height', 2196)
})

// Don't changed yet
expect(result.current.width).toBe(1920)
expect(result.current.height).toBe(1080)

act(() => {
vi.advanceTimersByTime(200)
})

expect(result.current.width).toBe(2196)
expect(result.current.height).toBe(2196)
})
})
15 changes: 13 additions & 2 deletions packages/usehooks-ts/src/useWindowSize/useWindowSize.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useState } from 'react'

import { useDebounceCallback } from '../useDebounceCallback'
import { useEventListener } from '../useEventListener'
import { useIsomorphicLayoutEffect } from '../useIsomorphicLayoutEffect'

Expand All @@ -10,6 +11,7 @@ interface WindowSize<T extends number | undefined = number | undefined> {

type UseWindowSizeOptions<InitializeWithValue extends boolean | undefined> = {
initializeWithValue: InitializeWithValue
debounceDelay?: number
}

const IS_SERVER = typeof window === 'undefined'
Expand All @@ -24,6 +26,7 @@ export function useWindowSize(
* Custom hook that tracks the size of the window.
* @param {?UseWindowSizeOptions} [options] - The options for customizing the behavior of the hook (optional).
* @param {?boolean} [options.initializeWithValue] - If `true` (default), the hook will initialize reading the window size. In SSR, you should set it to `false`, returning `undefined` initially.
* @param {?number} [options.debounceDelay] - The delay in milliseconds before the state is updated (disabled by default for retro-compatibility).
* @returns {object} An object containing the width and height of the window.
* @property {number} width - The width of the window.
* @property {number} height - The height of the window.
Expand Down Expand Up @@ -54,14 +57,22 @@ export function useWindowSize(
}
})

const debouncedSetWindowSize = useDebounceCallback(
setWindowSize,
options?.debounceDelay,
)

function handleSize() {
setWindowSize({
const setSize = options?.debounceDelay
? debouncedSetWindowSize
: setWindowSize

setSize({
width: window.innerWidth,
height: window.innerHeight,
})
}

// TODO: Prefer incoming useResizeObserver hook
useEventListener('resize', handleSize)

// Set size at the first client-side load
Expand Down

0 comments on commit d60f1c6

Please sign in to comment.