Connect React App with PlayCanvas iFrame

Hello :slight_smile:

I’m building a React App which includes a Playcanvas project via iFrame. Which is the best way to connect these 2 together? I’m basically looking for a communication between React UI and Playcanvas 3D scene.

1 Like

Hi @Christina,

The most efficient way is to use the window.postMessage API, to communicate between the parent and iframe instances.

Here is some sample code on how I implement this on React/Playcanvas apps.

On the Playcanvas side:

var IframeManager = pc.createScript('iframeManager');

// initialize code called once per entity
IframeManager.prototype.initialize = function() {

    // --- execute
    this.connect();
    
    // --- events
    this.app.on('iFrame:sendMessage', function(type, data){
        
        this.sendMessage(type, data);        
    }, this);    
};

IframeManager.prototype.connect = function(){
  
    // --- postMessage event handlers
    window.addEventListener("message", function(event){
        this.parseMessage(event);
    }.bind(this));

    // --- inform parent window that we are ready
   this.sendMessage('IFrameReady');
};

IframeManager.prototype.sendMessage = function(type, data){
    if( !type ) return;
    
    parent.postMessage({
        type: type,
        data: data
    }, '*');
};

IframeManager.prototype.parseMessage = function(event){
    
    if (!event.data) return;

    var data = event.data.data;
    
    // use app events to communicate with the rest of the app
    switch (event.data.type) {
        case "ParseData":
            this.app.fire('ParseData', data);
            break;    
        case "StartGame":      
            this.app.fire('StartGame', data);
            break;            
    }
};

On the React side, using the React Context API to have your components consume the same instance of this provider (code in Typescript):

declare var document: any;

export enum EPlaycanvasEvents {
  IFrameReady = "IFrameReady",
  ParseData = "ParseData",
  StartGame = "StartGame",
}

class Playcanvas {
  private iframeContent: any;
  private iframeReady: boolean;
  private messagesQueue: any[];

  constructor() {
    this.iframeReady = false;
    this.messagesQueue = [];
  }

  connectToIframe() {
    // @ts-ignore
    this.iframeContent = document.getElementById("game-iframe").contentWindow;

    // --- postMessage event handlers
    window.addEventListener("message", this.parseMessage.bind(this));
  }

  parseMessage(event: any) {
    if (!event.data || !event.data.type) return;

    switch (event.data.type) {
      case EPlaycanvasEvents.IFrameReady:
        this.iframeReady = true;

        // --- send queue messages
        this.messagesQueue.forEach(message => {
          this.sendMessage(message.type, message.data);
        });
        break;
      case EPlaycanvasEvents.StartGame:
        break;
    }
  }

  sendMessage(type: EPlaycanvasEvents, data?: any) {
    // --- if we aren't ready, queue messages for sending later
    if (this.iframeReady === false) {
      this.messagesQueue.push({
        type: type,
        data: data
      });
      return;
    }

    this.iframeContent.postMessage(
      {
        type: type,
        data: data
      },
      "*"
    );
  }
}

export default Playcanvas;
2 Likes

Thanks @Leonidas, I think I got it!

@Leonidas Ok, I have another question for you. Do you have any tip on how to set up the Context API to use the Playcanvas class in my components?

Sure, it can be tricky initially but here is a way to do it. Create a simple React Context and export it:

import React from "react";

const PlaycanvasContext = React.createContext<any | null>(null);

export default PlaycanvasContext;

In your index.tsx file import the Context provider before you boot your app:

import React from "react";
import ReactDOM from "react-dom";
import App from "./App";

import PlaycanvasContext from "./components/playcanvasContext";
import Playcanvas from "./components/playcanvas";

ReactDOM.render(
    <PlaycanvasContext.Provider value={new Playcanvas()}>
        <App />
    </PlaycanvasContext.Provider>,
    document.getElementById("root")
);

Now the class is a singleton (one instance app wide) and you can easily reference it in any of your components like this:

import React, { useContext, useEffect } from "react";
import Playcanvas, { PlaycanvasContext } from "../components/user";

const MyComponent: React.FC = () => {
    const playcanvas: Playcanvas = useContext(PlaycanvasContext);

    useEffect(() => {
        playcanvas.connectToIframe();
    }, []);

    return (
    <div className="my-component">
    </div>
    );
};

export default MyComponent;  

1 Like