I have a website in ReactJS. I want to get a callback whenever my tab comes in focus or is hidden. I came across the Page Visibility API for this but I\'m not able to figure out
Just built this using hooks as of React 16.8
import React, { useEffect } from 'react';
// User has switched back to the tab
const onFocus = () => {
console.log('Tab is in focus');
};
// User has switched away from the tab (AKA tab is hidden)
const onBlur = () => {
console.log('Tab is blurred');
};
const WindowFocusHandler = () => {
useEffect(() => {
window.addEventListener('focus', onFocus);
window.addEventListener('blur', onBlur);
// Specify how to clean up after this effect:
return () => {
window.removeEventListener('focus', onFocus);
window.removeEventListener('blur', onBlur);
};
});
return <></>;
};
export default WindowFocusHandler;
None of these worked well for what I needed, which was a way to detect if a user switched between tabs, or minimized the browser by double clicking icon in the taskbar.
They either fired off multiple times but did manage registered the correct state, didn't work when minimizing from the taskbar icon or just didn't manage to keep up with multiple actions one after another.
Seeing as I needed to make a server request each time the focus changed, the above situations were a bit 'no'.
So this is what I did:
const DetectChatFocus = () => {
const [chatFocus, setChatFocus] = useState(true);
useEffect(() => {
const handleActivityFalse = () => {
setChatFocus(false);
serverRequest(false);
};
const handleActivityTrue = () => {
setChatFocus(true);
serverRequest(true);
};
window.addEventListener('focus', handleActivityTrue);
window.addEventListener('blur', handleActivityFalse);
return () => {
window.removeEventListener('focus', handleActivityTrue);
window.removeEventListener('blur', handleActivityFalse);
};
}, [chatFocus]);
};
export default DetectChatFocus;
Currently this seems to work very well, tested on both Chrome and Firefox, all you need to do is initialize it in a main component or wherever you need and it will keep track of the window focus for all those scenarios and it will only make one server request per action.
This should work:
componentDidMount() {
window.addEventListener("focus", this.onFocus)
}
componentWillUnmount() {
window.removeEventListener("focus", this.onFocus)
}
onFocus = () => {
//
}
Edit: same goes for "blur" and it should work for when the tab becomes hidden.
Check @Assaf's answer for usage with hooks.
There is no reliable method to check it, so you need to combine few methods together. Here is Context for react-hooks
import React, { useState, useEffect } from 'react'
export const WindowContext = React.createContext(null)
export const WindowContextProvider = props => {
const [windowIsActive, setWindowIsActive] = useState(true)
function handleActivity(forcedFlag) {
if (typeof forcedFlag === 'boolean') {
return forcedFlag ? setWindowIsActive(true) : setWindowIsActive(false)
}
return document.hidden ? setWindowIsActive(false) : setWindowIsActive(true)
}
useEffect(() => {
const handleActivityFalse = () => handleActivity(false)
const handleActivityTrue = () => handleActivity(true)
document.addEventListener('visibilitychange', handleActivity)
document.addEventListener('blur', handleActivityFalse)
window.addEventListener('blur', handleActivityFalse)
window.addEventListener('focus', handleActivityTrue )
document.addEventListener('focus', handleActivityTrue)
return () => {
window.removeEventListener('blur', handleActivity)
document.removeEventListener('blur', handleActivityFalse)
window.removeEventListener('focus', handleActivityFalse)
document.removeEventListener('focus', handleActivityTrue )
document.removeEventListener('visibilitychange', handleActivityTrue )
}
}, [])
return <WindowContext.Provider value={{ windowIsActive }}>{props.children}</WindowContext.Provider>
}
Found this library. It could be of help... https://www.npmjs.com/package/react-page-visibility Here's how I would use it to solve your problem
import React from 'react;
import PageVisibility from 'react-page-visibility';
class YourComponent extends React.Component {
state = {
isWindowInFocus: true,
}
componentDidMount() {
const { isWindowInFocus } = this.props;
if (!isWindowInFocus) {
// do something
}
}
listentoWindow = isVisible => {
this.setState({
isWindowInFocus: isVisible,
});
}
render() {
return (
<PageVisibility onChange={this.listentoWindow}>
<div>
Your component JSX
</div>
</PageVisibility>
);
}
}
A more complete and optimized hook:
import React, { useState, useEffect } from 'react'
import _ from 'lodash'
export default function useIsWindowFocused(): boolean {
const [windowIsActive, setWindowIsActive] = useState(true)
const handleActivity = React.useCallback(
_.debounce(
(e: { type: string }) => {
if (e?.type == 'focus') {
return setWindowIsActive(true)
}
if (e?.type == 'blur') {
return setWindowIsActive(false)
}
if (e?.type == 'visibilitychange') {
if (document.hidden) {
return setWindowIsActive(false)
} else {
return setWindowIsActive(true)
}
}
},
100,
{ leading: false },
),
[],
)
useEffect(() => {
document.addEventListener('visibilitychange', handleActivity)
document.addEventListener('blur', handleActivity)
window.addEventListener('blur', handleActivity)
window.addEventListener('focus', handleActivity)
document.addEventListener('focus', handleActivity)
return () => {
window.removeEventListener('blur', handleActivity)
document.removeEventListener('blur', handleActivity)
window.removeEventListener('focus', handleActivity)
document.removeEventListener('focus', handleActivity)
document.removeEventListener('visibilitychange', handleActivity)
}
}, [])
return windowIsActive
}