2

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.
Kay
  • 17,906
  • 63
  • 162
  • 270
  • 1
    It's not react fault. It doesn't work in vanilla js as well – Konrad Oct 03 '22 at 12:17
  • I can see in the docs there are "message" and "load" events https://developer.mozilla.org/en-US/docs/Web/API/Window/message_event https://developer.mozilla.org/en-US/docs/Web/API/Window/load_event – Kay Oct 03 '22 at 12:27
  • I know, I'm also surprised that it's not working – Konrad Oct 03 '22 at 12:31
  • If you were on the same origin, in the child page, you can do `window.parent.postMessage("some message")` and on the parent window, you can add a event listener: `window.addEventListener('message', handlePopupMessage)` – vighnesh153 Oct 03 '22 at 12:33
  • @vighnesh153 I am on the same origin, I also came across that qwerk, the `message` event listener will work but only when you apply it to `window.addEventListener` BUT it does not work when you apply it to `popupRef.current.addEventListener` – Kay Oct 03 '22 at 12:35
  • @vighnesh153 `message` is a special event that is created to communicate between different windows. – Konrad Oct 03 '22 at 12:39
  • 1
    Yep. So, we can add message eventListener to our window object and not our child's window and vice-versa. – vighnesh153 Oct 03 '22 at 12:40
  • Okay that makes sense, you add a `message` event to the "current "window and not the child window. – Kay Oct 03 '22 at 12:42
  • Fine, but according to [this answer](https://stackoverflow.com/a/3030893/5089567) it should be possible to listen for `load` event at least. – Konrad Oct 03 '22 at 12:44
  • Knowing when its closed is of greater importance. ( i need to know if its closed, without finished processing) – Kay Oct 03 '22 at 12:46
  • Maybe this might work: `popupRef.current.onload = () => {};` for load and `popupRef.current.onclose = () => {}` for close just like suggested in the above question? – vighnesh153 Oct 03 '22 at 12:48
  • @vighnesh153 onload did not work, and i believe there is no close event – Kay Oct 03 '22 at 12:50

3 Answers3

1

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.
vighnesh153
  • 4,354
  • 2
  • 13
  • 27
  • Just had a chance to test this in react, that onload does not fire also can you explain` if (popupRef.current === null) return;` – Kay Oct 04 '22 at 08:47
  • Niether does the message, to get the message to work i had to revert back to using window rather than popupRef. – Kay Oct 04 '22 at 08:49
  • maybe using next.js / stackblitz changes something :/ – Kay Oct 04 '22 at 08:49
  • finally i notice the unload triggers when popup first loads but does not trigger when popup closed – Kay Oct 04 '22 at 08:51
  • I tried recreating it in pure react as well. https://stackblitz.com/edit/react-ts-cyo7ka?file=App.tsx It seems to be working. – vighnesh153 Oct 04 '22 at 10:35
  • `(popupRef.current === null) return;` this was just to make TS happy :D – vighnesh153 Oct 04 '22 at 10:35
  • 1
    yes i see taht its workign in stackblitz, strange how its not working on my local machine. I wonder if it has anyting to do with waht you are loading inside the popup. I will give it another go later today and get back – Kay Oct 04 '22 at 10:39
0

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);
  };
  • 1
    And don't forget about same-origin policy security requirements – Levon Ghazaryan Oct 03 '22 at 14:37
  • 1
    How is this different from OP's `login` function? – vighnesh153 Oct 03 '22 at 14:40
  • The difference is `openPopup('https://same-origin.com')` especially this https://same-origin.com what Im trying to say is that there is no need to store sub window reference in ref.current and listeners are not invoked due to same-origin security policy – Levon Ghazaryan Oct 03 '22 at 16:19
0

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])
Vitor Gomes
  • 132
  • 3