Kay
Kay

Reputation: 19660

How to handle window event listeners in react

In react i need to be able to open a popup window https://developer.mozilla.org/en-US/docs/Web/API/Window/open and manage the events such as "mesage" https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage and "load" and "close" events.

However none of the events i have added listeners to are firing...

import * as React from 'react';
import './style.css';
import { useState, useRef } from 'react';

export default function App() {
  const { login, error } = useOAuth();

  return (
    <div>
      <button onClick={login}>Login</button>
    </div>
  );
}

const useOAuth = () => {
  const [error, setError] = useState();
  const popupRef = useRef<Window | null | undefined>();

  const login = () => {
    popupRef.current = openPopup('https://google.com');
    popupRef.current.addEventListener('load', handlePopupLoad);
    popupRef.current.addEventListener('close', handlePopupClose);
    popupRef.current.addEventListener('message', handlePopupMessage);
  };

  const handlePopupLoad = (data) => {
    console.log('load', data);
  };

  const handlePopupClose = (data) => {
    console.log('close', data);
  };

  const handlePopupMessage = (data) => {
    console.log('message', data);
  };

  const openPopup = (url: string) => {
    const params = `scrollbars=no,resizable=no,status=no,location=no,toolbar=no,menubar=no,
        width=500,height=600,left=100,top=100`;

    return window.open(url, 'Login', params);
  };

  return {
    login,
    error,
  };
};

https://stackblitz.com/edit/react-ts-qlfw9q?file=App.tsx

aside:

  1. Is there a way to differentiate between when a "user" closed the window using the "red x" button and when it was correctly closed using window.close().
  2. how can i nicely cleanup the popup once its closed.

Upvotes: 2

Views: 3113

Answers (3)

vighnesh153
vighnesh153

Reputation: 5388

I have changed the URL to a local one (to avoid any cross-origin issues).

Check out the demo (If this fails to load, try refreshing. Something seems to be off with Stackblitz)

In parent page, I have used the onload (for loading), onunload (for close)

import * as React from 'react';
import { BrowserRouter, Route, Routes } from 'react-router-dom';

export default function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/auth" element={<AuthPage />} />
      </Routes>
    </BrowserRouter>
  );
}

function Home() {
  const login = () => {
    console.clear();

    const url = '/auth';
    const popup = openPopup(url);

    // When the popup loads
    popup.onload = () => {
      console.log('loaded. this was logged');
    };

    // when the popup unloads
    popup.onunload = () => {
      console.log('unloading now');
    };

    // when the popup posts a message
    popup.addEventListener('message', ({ data }) => {
      console.log('message: ', data);
    });
  };

  const openPopup = (url: string) => {
    const params = `scrollbars=no,resizable=no,status=no,location=no,toolbar=no,menubar=no,
        width=500,height=600,left=100,top=100`;

    return window.open(url, 'Login', params);
  };

  return (
    <div>
      <h1>Home</h1>
      <button onClick={login}>Login</button>
    </div>
  );
}

function AuthPage() {

  // I have added a button to trigger postMessage to parent.
  const onClick = () => {
    window.parent.postMessage('To parent');
  };

  return (
    <div>
      <h1>Auth Page</h1>
      <button onClick={onClick}>Click me</button>
    </div>
  );
}

Few things I observed:

  • Since we are posting message from child to parent, I would expect window.addEventListener('message') to get triggered. But, for some reason, is popupRef.current.addEventListener('message') getting triggered.
  • popupRef.current.onunload gets triggered before the beginning of onload. If I had to guess, this is some sort of cleanup mechanism.

Upvotes: 1

Vitor Gomes
Vitor Gomes

Reputation: 132

well, you should probably wrap all your event listeners inside a useEffect to run it and cleanup after it, it should look like something like this

const popupRef = useRef<Window | null>(null)

    const handlePopupLoad = (data: any) => {
        console.log('load', data)
    }

    const handlePopupClose = (data: any) => {
        console.log('close', data)
    }

    const handlePopupMessage = (data: any) => {
        console.log('message', data)
    }

    const openPopup = (url: string) => {
        const params = `scrollbars=no,resizable=no,status=no,location=no,toolbar=no,menubar=no,
        width=500,height=600,left=100,top=100`

        return window.open(url, 'Login', params)
    }

    useEffect(() => {
        if (!popupRef.current) {
            return undefined
        }

        popupRef.current = openPopup('https://google.com')
        popupRef.current?.addEventListener('load', handlePopupLoad)
        popupRef.current?.addEventListener('close', handlePopupClose)
        popupRef.current?.addEventListener('message', handlePopupMessage)

        return () => {
            popupRef.current?.removeEventListener('load', handlePopupLoad)
            popupRef.current?.removeEventListener('close', handlePopupClose)
            popupRef.current?.removeEventListener('message', handlePopupMessage)
        }
    }, [popupRef])

Upvotes: 0

Levon Ghazaryan
Levon Ghazaryan

Reputation: 109

I think there is no react specific issue all you need to do to make this code work is to change your login function something like this.

const login = () => {
    const childWindow = openPopup('https://same-origin.com');
    childWindow.addEventListener('load', handlePopupLoad);
    childWindow.addEventListener('close', handlePopupClose);
    childWindow.addEventListener('message', handlePopupMessage);
  };

Upvotes: 0

Related Questions