Skip to content

Commit c7cd87b

Browse files
committed
feat: Added nwe hook, useWindowFocus
1 parent 9ef9535 commit c7cd87b

5 files changed

Lines changed: 152 additions & 0 deletions

File tree

docs/useWindowFocus.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# `useWindowFocus`
2+
3+
React sensor hook that tracks if the browser window is focused.
4+
5+
## Usage
6+
7+
```jsx
8+
import {useWindowFocus} from 'react-use';
9+
10+
const Demo = () => {
11+
const defaultState = document.hasFocus();
12+
const isFocused = useWindowFocus(defaultState);
13+
14+
return (
15+
<div>
16+
Window is {isFocused ? 'focused' : 'not focused'}
17+
</div>
18+
);
19+
};
20+
```
21+
22+
## Reference
23+
24+
```js
25+
const isFocused = useWindowFocus(initialState);
26+
```
27+
28+
- `initialState` &mdash; `boolean`, optional initial state before the actual focus is determined, defaults to `false`.
29+
- `isFocused` &mdash; `boolean`, whether the browser window is currently focused.

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ export { default as useVideo } from './useVideo';
106106
export { default as useStateValidator } from './useStateValidator';
107107
export { useScrollbarWidth } from './useScrollbarWidth';
108108
export { useMultiStateValidator } from './useMultiStateValidator';
109+
export { default as useWindowFocus } from './useWindowFocus';
109110
export { default as useWindowScroll } from './useWindowScroll';
110111
export { default as useWindowSize } from './useWindowSize';
111112
export { default as useMeasure } from './useMeasure';

src/useWindowFocus.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { useEffect, useState } from 'react';
2+
3+
const useWindowFocus = (defaultState: boolean = false) => {
4+
const [isFocused, setIsFocused] = useState(defaultState);
5+
6+
useEffect(() => {
7+
const handleFocus = () => setIsFocused(true);
8+
const handleBlur = () => setIsFocused(false);
9+
10+
window.addEventListener('focus', handleFocus);
11+
window.addEventListener('blur', handleBlur);
12+
13+
return () => {
14+
window.removeEventListener('focus', handleFocus);
15+
window.removeEventListener('blur', handleBlur);
16+
};
17+
}, []);
18+
19+
return isFocused;
20+
};
21+
22+
export default useWindowFocus;

stories/useWindowFocus.story.tsx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { storiesOf } from '@storybook/react';
2+
import * as React from 'react';
3+
import useWindowFocus from '../src/useWindowFocus';
4+
import ShowDocs from './util/ShowDocs';
5+
6+
const Demo = () => {
7+
const defaultState = document.hasFocus();
8+
const isFocused = useWindowFocus(defaultState);
9+
10+
return (
11+
<div>
12+
<p>Click outside this window or switch to another tab to see the focus state change.</p>
13+
<div style={{ fontSize: '24px', fontWeight: 'bold' }}>
14+
Window is {isFocused ? '✅ Focused' : '❌ Not Focused'}
15+
</div>
16+
</div>
17+
);
18+
};
19+
20+
storiesOf('Sensors/useWindowFocus', module)
21+
.add('Docs', () => <ShowDocs md={require('../docs/useWindowFocus.md')} />)
22+
.add('Demo', () => <Demo />);

tests/useWindowFocus.test.ts

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { renderHook, act } from '@testing-library/react-hooks';
2+
import useWindowFocus from '../src/useWindowFocus';
3+
4+
describe('useWindowFocus', () => {
5+
it('should be defined', () => {
6+
expect(useWindowFocus).toBeDefined();
7+
});
8+
9+
it('should return false initially', () => {
10+
const { result } = renderHook(() => useWindowFocus());
11+
12+
expect(result.current).toBe(false);
13+
});
14+
15+
it('should return true initially when defaultState is true', () => {
16+
// Mock document.hasFocus() to return true
17+
const hasFocusSpy = jest.spyOn(document, 'hasFocus').mockReturnValue(true);
18+
19+
const { result } = renderHook(() => useWindowFocus(true));
20+
21+
expect(result.current).toBe(true);
22+
23+
hasFocusSpy.mockRestore();
24+
});
25+
26+
it('should return false initially when initialState is false', () => {
27+
const { result } = renderHook(() => useWindowFocus(false));
28+
29+
expect(result.current).toBe(false);
30+
});
31+
32+
it('should return true when window receives focus', () => {
33+
const { result } = renderHook(() => useWindowFocus());
34+
35+
act(() => {
36+
window.dispatchEvent(new Event('focus'));
37+
});
38+
39+
expect(result.current).toBe(true);
40+
});
41+
42+
it('should return false when window loses focus', () => {
43+
const { result } = renderHook(() => useWindowFocus());
44+
45+
act(() => {
46+
window.dispatchEvent(new Event('focus'));
47+
});
48+
expect(result.current).toBe(true);
49+
50+
act(() => {
51+
window.dispatchEvent(new Event('blur'));
52+
});
53+
expect(result.current).toBe(false);
54+
});
55+
56+
it('should add event listeners on mount', () => {
57+
const addEventListenerSpy = jest.spyOn(window, 'addEventListener');
58+
59+
renderHook(() => useWindowFocus());
60+
61+
expect(addEventListenerSpy).toHaveBeenCalledWith('focus', expect.any(Function));
62+
expect(addEventListenerSpy).toHaveBeenCalledWith('blur', expect.any(Function));
63+
64+
addEventListenerSpy.mockRestore();
65+
});
66+
67+
it('should remove event listeners on unmount', () => {
68+
const removeEventListenerSpy = jest.spyOn(window, 'removeEventListener');
69+
70+
const { unmount } = renderHook(() => useWindowFocus());
71+
unmount();
72+
73+
expect(removeEventListenerSpy).toHaveBeenCalledWith('focus', expect.any(Function));
74+
expect(removeEventListenerSpy).toHaveBeenCalledWith('blur', expect.any(Function));
75+
76+
removeEventListenerSpy.mockRestore();
77+
});
78+
});

0 commit comments

Comments
 (0)