Skip to content
Draft
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
29 changes: 29 additions & 0 deletions docs/useWindowFocus.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# `useWindowFocus`

React sensor hook that tracks if the browser window is focused.

## Usage

```jsx
import {useWindowFocus} from 'react-use';

const Demo = () => {
const defaultState = document.hasFocus();
const isFocused = useWindowFocus(defaultState);

return (
<div>
Window is {isFocused ? 'focused' : 'not focused'}
</div>
);
};
```

## Reference

```js
const isFocused = useWindowFocus(initialState);
```

- `initialState` &mdash; `boolean`, optional initial state before the actual focus is determined, defaults to `false`.
- `isFocused` &mdash; `boolean`, whether the browser window is currently focused.
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ export { default as useVideo } from './useVideo';
export { default as useStateValidator } from './useStateValidator';
export { useScrollbarWidth } from './useScrollbarWidth';
export { useMultiStateValidator } from './useMultiStateValidator';
export { default as useWindowFocus } from './useWindowFocus';
export { default as useWindowScroll } from './useWindowScroll';
export { default as useWindowSize } from './useWindowSize';
export { default as useMeasure } from './useMeasure';
Expand Down
22 changes: 22 additions & 0 deletions src/useWindowFocus.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { useEffect, useState } from 'react';

const useWindowFocus = (defaultState: boolean = false) => {
const [isFocused, setIsFocused] = useState(defaultState);

useEffect(() => {
const handleFocus = () => setIsFocused(true);
const handleBlur = () => setIsFocused(false);

window.addEventListener('focus', handleFocus);
window.addEventListener('blur', handleBlur);

return () => {
window.removeEventListener('focus', handleFocus);
window.removeEventListener('blur', handleBlur);
};
}, []);

return isFocused;
};

export default useWindowFocus;
22 changes: 22 additions & 0 deletions stories/useWindowFocus.story.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { storiesOf } from '@storybook/react';
import * as React from 'react';
import useWindowFocus from '../src/useWindowFocus';
import ShowDocs from './util/ShowDocs';

const Demo = () => {
const defaultState = document.hasFocus();
const isFocused = useWindowFocus(defaultState);

return (
<div>
<p>Click outside this window or switch to another tab to see the focus state change.</p>
<div style={{ fontSize: '24px', fontWeight: 'bold' }}>
Window is {isFocused ? '✅ Focused' : '❌ Not Focused'}
</div>
</div>
);
};

storiesOf('Sensors/useWindowFocus', module)
.add('Docs', () => <ShowDocs md={require('../docs/useWindowFocus.md')} />)
.add('Demo', () => <Demo />);
78 changes: 78 additions & 0 deletions tests/useWindowFocus.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { renderHook, act } from '@testing-library/react-hooks';
import useWindowFocus from '../src/useWindowFocus';

describe('useWindowFocus', () => {
it('should be defined', () => {
expect(useWindowFocus).toBeDefined();
});

it('should return false initially', () => {
const { result } = renderHook(() => useWindowFocus());

expect(result.current).toBe(false);
});

it('should return true initially when defaultState is true', () => {
// Mock document.hasFocus() to return true
const hasFocusSpy = jest.spyOn(document, 'hasFocus').mockReturnValue(true);

const { result } = renderHook(() => useWindowFocus(true));

expect(result.current).toBe(true);

hasFocusSpy.mockRestore();
});

it('should return false initially when initialState is false', () => {
const { result } = renderHook(() => useWindowFocus(false));

expect(result.current).toBe(false);
});

it('should return true when window receives focus', () => {
const { result } = renderHook(() => useWindowFocus());

act(() => {
window.dispatchEvent(new Event('focus'));
});

expect(result.current).toBe(true);
});

it('should return false when window loses focus', () => {
const { result } = renderHook(() => useWindowFocus());

act(() => {
window.dispatchEvent(new Event('focus'));
});
expect(result.current).toBe(true);

act(() => {
window.dispatchEvent(new Event('blur'));
});
expect(result.current).toBe(false);
});

it('should add event listeners on mount', () => {
const addEventListenerSpy = jest.spyOn(window, 'addEventListener');

renderHook(() => useWindowFocus());

expect(addEventListenerSpy).toHaveBeenCalledWith('focus', expect.any(Function));
expect(addEventListenerSpy).toHaveBeenCalledWith('blur', expect.any(Function));

addEventListenerSpy.mockRestore();
});

it('should remove event listeners on unmount', () => {
const removeEventListenerSpy = jest.spyOn(window, 'removeEventListener');

const { unmount } = renderHook(() => useWindowFocus());
unmount();

expect(removeEventListenerSpy).toHaveBeenCalledWith('focus', expect.any(Function));
expect(removeEventListenerSpy).toHaveBeenCalledWith('blur', expect.any(Function));

removeEventListenerSpy.mockRestore();
});
});