Make React Components Consumable Again

Matan Shiloh

Matan Shiloah

Front-end developer at the video player team. Responsible for all those beloved video ads you see on Taboola's publishers.

Matan Shiloah | 24 Oct 2019 | Javascript

Tags: backstage, javascript, middleware, react, redux

The integration between a newly developed code into an existing code is always a challenge. Recently I was assigned a task to develop a new app inside Taboola’s Backstage. As a developer who is always looking to learn new technologies, I decided to develop it using React, Redux and Middleware.

I have never developed using Redux and Middleware, only old-school React. So the first thing I had to do was to learn how to use these funky libraries. With the help of my wonderful teammates, who had some experience with these libraries, I had an easy start. I was able to create my project and install Redux. Then I became acquainted with the single source of truth concept and how to create actions.

 

Challenges ahead

Backstage was not developed using React, which raised two concerns. First, I did not want to break the existing code. Second, how to integrate between my React app and the parent site.

Thanks to the React build command, the first concern was solved pretty easily. React build command creates a bundle code, based on defaults configuration. I injected the bundle code into an iframe and I was able to isolate my app’s code. Errors were encapsulated into the iframe scope, and the parent site didn’t break if an error was thrown.

But now how can I export data from my app to the parent site?

 

The parent site – iframe communication dilemma

My first attempt to interact with Backstage was by using an unfriendly iframe. The way to communicate between parent site and an unfriendly iframe is by using events and postMessages. The disadvantage of using this approach is that the communication becomes asynchronous. 

Why is that bad? Unnecessary async communication in JS causing CPU and time overhead. Obviously, I didn’t want to lag Backstage. It is also harder to debug and read. I wanted the next developer who works on this project to have a smooth sailing into the code.

 

Make friends with your iframe

I still wanted to encapsulate my code inside an iframe, but I had to “make peace” with my iframe before.

By replacing the iframe with a friendly one, I still got all the benefits of error handling I mentioned. It also created a new opportunity for me – I was able to expose an API to the main page. 

Why was that an advantage? It enabled me to synchronize the communication between the main page and the React app. As mentioned before, it makes the work much easier, for both the browser and future developers.

 

Wrap it up

I decided on writing the API as part of the main React component. In this case, I had to expose the main component to the main page. In create-react-app’s default code, the entry point to the code is not returning any value. The running of the entry point is independant. Also, ReactDom.render is not returning it’s result. It only renders the basic React code into an existing element.

The first thing I had to do is to change this behavior – I wanted to be able to get the result of ReactDOM.render. Sounds simple, doesn’t it?

I found a helpful GitHub project to start my vision. With the help of this project, I was able to understand how to wrap my React code and expose it to the main page.

 

Make your React code consumable

Let’s take this structure for example:

First of all, we want the iframe to consume our React app and store it as a variable. When using create-react-app, React provides a native build command – “react-scripts build”. It creates a directory with a bundled version of our code, ready for production. We need to change it a little bit, by creating our own Webpack.

Our new webpack would have to return our entry point and make it assignable into a variable. So, we had to change only the output config for our build. All other configs can stay as default. I do suggest adding the entry point, for readability reasons.

The option that configures how the library will be exposed is libraryTarget. The safest and most recommended value for libraryTarget is “var”. By using this option, the value returned from Index.js will be assigned into a variable in the iframe. Then, we will be able to use its returned value in the HTML code:

Expose your API

If I develop the API in Main component, I can return the component from Index.js and call the API from the main page. For example, I will be able call Main’s “changeText” method after 5 seconds from the main page:

Easy as a pie. But a short reading in ReactDOM documentation revealed the next note:

The solution that is offered in the note is callback ref. In this case, callback ref could not help me. It will allow me to create a reference of Main component and use it as a member of App component. However, I will not be able to simply return the referenced component to the main page.

 

Overcoming the async barrier

Besides React’s documentation advice, I faced the same issue when I added Redux into the mix. Redux’s Provider component does not return any value, because it loads asynchronously.

How can we overcome this barrier? With Promise

We can pass the resolve callback to Main component. When the component is mounted, we can resolve the promise. We can pass the component as the resolve value, and store it in the iframe. Then we will be able to use it from the main page:

Let’s Wrap it up (yes, I used the same pun again)

Now we can add to our API, consume it from the main page and use it as we please. We made our code simpler for the next developer and debugging became easier. We removed redundant CPU usage and run-time lags by avoiding unnecessary asynchronous code.

This method is very useful when writing a React app. You can export data to any other app, regardless of which library is being used. By doing some small changes, we can do great things. Using React app, we can integrate a new React app with any Angular, Vue, jQuery and plain javascript apps. Sounds freakin’ awesome!

A little anecdote for the end: I was told about this solution from a friend, who used it for a Redux-less React app. Also, the example from GitHub is not directed for Redux projects. I didn’t think it would require any special adaptation. I was wrong. 

At the beginning, a null object was returned from my Index.js file to the main page. I learned the hard way that Provider is created asynchronously. For an entire day I banged my head against my laptop. But once I understood how Provider works, I was able to synchronize my code using promises. Only afterwards I read the note about ReactDOM.render. 

Hopefully, this will save other developers precious time.