'Check if the browser tab is in focus in ReactJS

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 how to use it in ReactJS.

In which lifecycle method do I register the callback for this?



Solution 1:[1]

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.

Solution 2:[2]

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);
        // Calls onFocus when the window first loads
        onFocus();
        // Specify how to clean up after this effect:
        return () => {
            window.removeEventListener("focus", onFocus);
            window.removeEventListener("blur", onBlur);
        };
  }, []);

    return <></>;
};

export default WindowFocusHandler;

Solution 3:[3]

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>
}

Solution 4:[4]

I found This library. It could be of help.

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>
      );
    }
}

Solution 5:[5]

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.

Solution 6:[6]

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
}

Solution 7:[7]

I think it's simpler to look at the direct state of document.visibilityState, bacause it return 'visible' or 'hidden':

const [visibilityState, setVisibilityState] = useState('visible')

useEffect(() => {
  setVisibilityState(document.visibilityState)
}, [document.visibilityState])

Solution 8:[8]

Modern hook:

import { useCallback, useEffect, useState } from "react";

const useTabActive = () => {
  const [visibilityState, setVisibilityState] = useState(true);

  const handleVisibilityChange = useCallback(() => {
    setVisibilityState(document.visibilityState === 'visible');
  }, []);

  useEffect(() => {
    document.addEventListener("visibilitychange", handleVisibilityChange)
    return () => {
      document.removeEventListener("visibilitychange", handleVisibilityChange)
    }
  }, []);

  return visibilityState;
}

export default useTabActive;

Uses Document.visibilityState

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Jeffrey Nicholson Carré
Solution 2
Solution 3
Solution 4 Dhia Djobbi
Solution 5 Nick09
Solution 6 Samuel Halff
Solution 7 Andronicus
Solution 8 Gal Zakay