SPA/ ReactJS - window.SPA object

In the Page component a window.SPA object is defined in the constructor.

Can we add the window.SPA object in the react lifecycle method componentDidMount()?
The window object will not be available for us in the constructor because we use next.js for server-sided rendering.

Thanks

You can define the window.SPA object anywhere you want.

The Channel Manager checks on the load event of the iframe (your site/SPA) if a window.SPA object exists on the iframe. If it does, it will invoke the window.SPA.init function of the iframe with an object that contains the createOverlay() and syncOverlay() functions (see next paragraph). So by setting the right callback function on window.SPA.init in your SPA/site, you can store references to the createOverlay() and syncOverlay() functions in the SPA’s context so you can invoke these functions when needed.

The createOverlay() and syncOverlay() functions are used for rendering all of the CM overlays when the SPA is done rendering. For more information, see https://www.onehippo.org/library/concepts/spa-plus/channel-manager-spa-api/introduction.html

You can run into timing issues with the above approach if the window.SPA object is not declared yet when the CM tries to initialize window.SPA.init. For example, when your SPA doesn’t immediately render on page-load. In that case, you want to define the window.SPA object outside of your SPA, e.g. inline on the page itself.

I suspect you’re trying to do server-side rendering, because accessing window.SPA in the constructor method is not an issue for client-side rendering. You can take a look at the React SDK and the example server-side rendered app for how this is done. The React SDK initializes the window.SPA object in the componentDidMount() lifecycle method, which is a better place to do this. You can find the code of the app here (uses Next.js for SSR): https://github.com/bloomreach/experience-react-sdk/tree/master/examples/server-side-rendered

Hi,

As you mentioned that you’re running server-side next.js, I don’t think it does really matter.
But just for clarification when using SPA in browser mode, I’d like to share some information for others below.

I guess you referred to [1].
The code there does the following:

  • When the CmsPage component is loaded, it checks if window.SPA.init(cms) has been invoked by Channel Manager before the React app was fully loaded. The variable, “windowSPAPreloaded”, is set to true if Channel Manager invoked that already before SPA is loaded.
    (As Channel Manager simply attaches “onload” event on the IFrame hosting the React app to find and invoke “window.SPA.init(cms)”, it can happen before your SPA is fully loaded.)
  • If “windowSPAPreloaded” is true, then it registers its callback function through “window.SPA.renderComponent” so that Channel Manager will be able to invoke the callback to refresh your SPA on some events such as after parameter settings.
  • If “windowSPAPreloaded” is false, then you’re free to redefine “window.SPA”, which was defined in prior in the layout template outside the SPA like [2], like the else block.
  • Finally, if “windowSPAPreloaded”, which means Channel Manager didn’t really have a chance to initialize your SPA, you are supposed to invoke the init method again with the stored (in [2]) “cms” context object again there.

I hope it helps you understand the logic there.

The reason why we have a big complex logic there is because Channel Manager does not know exactly when the embedded SPA completes its loading. Instead, it can exactly know when the IFrame hosting the SPA is loaded.
Therefore, the best practice regardless of any SPA platforms, any different techniques to load SPAs in a webpage, is to define “window.SPA” as early as possible like [2], and adapt your SPA to be able to handle the potential timing issue.

Regards,

Woonsan

[1] hippo-demo-spa-integration/page.js at master · onehippo/hippo-demo-spa-integration · GitHub
[2] hippo-demo-spa-integration/react-base-layout.ftl at master · onehippo/hippo-demo-spa-integration · GitHub

As you mentioned that you’re running server-side next.js, I don’t think it does really matter.

It does matter. There is no window object on the server, so the code of the demo project [1] won’t compile for server-side apps (e.g. Uncaught TypeError: Cannot set property ‘SPA’ of undefined). Therefore, you have to move it to one of the lifecycle methods that are executed client-side so that the window object is available. The SDK declares the window.SPA in the componentDidMount() method [2].

[1] https://github.com/onehippo/hippo-demo-spa-integration/blob/master/spa/react/src/cms-components/core/page.js
[2] https://github.com/bloomreach/experience-react-sdk/blob/master/src/cms-components/core/page.js

OK. Good to know.
So, should it be okay to move the following lines from the constructor to componentDidMount method then?

(inside typeof window !== ‘undefined’ block)

Regards,

Woonsan

In fact this is a server-side rendered site with next.js
Awesome help guys, thanks a lot!!!