5 Integrations in React: Contentful, Tilda, Hubspot, Typeform, Builder.io

Written by rusanovn | Published 2024/01/12
Tech Story Tags: react | integrations-in-react | react-contentful-integration | react-tilda-integration | react-hubspot-integration | react-typeform-integration | react-builder.io-integration | react-guides-on-hackernoon

TLDRI will demonstrate how to solve three integration-related tasks in a React application: - Changing page content in React without involving a developer; - Extending the functionality of the React application using third-party services; - Creating pages with minimal development costs.via the TL;DR App

Greetings, all! Have you ever been asked by your business to accelerate development by incorporating third-party services? And doesn’t the idea of being able to modify design and content on your own, without having to involve a developer, sound enticing?


Currently, there are various tools available to facilitate effective collaboration among different team members, including developers, designers, and content managers. Tilda, Wordpress, and other similar solutions offer convenient means for working on different aspects of a project. However, integrating the outcomes of these tools into a React application can pose challenges. The optimal solution would be to enable interaction between our application and the content, allowing for event interception to achieve seamless integration and flexibility.

In this article, I will demonstrate how to solve three integration-related tasks in a React application:

  1. Changing page content in React without involving a developer;
  2. Extending the functionality of the React application using third-party services;
  3. Creating pages with minimal development costs.

These approaches will allow us to ensure flexibility and accessibility of our application while significantly speeding up the development process. Let’s take a closer look at each of these tasks and identify the optimal solutions.

Demo | Code

Conditions

This article will showcase different integration techniques that can save time for developers and offer greater flexibility to designers and content managers.

Typically, we expect the following from third-party solutions:

  1. Ease of adoption: The tool should be straightforward and easy to learn for all parties involved — how to implement and maintain it, manage its visual aspects, and handle content.
  2. Flexibility: While it may not be possible to create a one-size-fits-all solution for every case, the tool should fulfill its purpose and ideally offer possibilities for extending its functionality.
  3. Level of interaction: We want to provide users with rich interactive pages. Is it possible to dynamically display different elements depending on the state of the React application, such as authentication, and invoke functions from the main application?
  4. Ease of integration: How simple and fast is the process of initial integration and subsequent deployments? Can this process be automated?
  5. Clarity of integration: Some tools are specifically designed for integration, while others are not. How much effort will be required to display and interact with integrated content?

In the examples, we will address the following questions:

  • How to display the results of a tool’s work within our React application in a dedicated container?
  • How to subscribe to events from React?
  • What possibilities exist for Server Side Rendering?

It is worth noting that I will provide examples of solutions using specific products, such as Tilda, Hubspot, Typeform, Contentful, Builder.io, but there are analogs available that work in similar ways. This will allow us to examine different integration approaches and their practical applications visually.

Task: Changing content

If our goal is only to offload data management to an admin interface while still wanting to create other components of the application “the old way” (by writing code and designing in an editor), then using a Headless CMS can be an optimal solution. This approach allows storing data in SaaS solutions, retrieving it through SDKs or directly, and then rendering it on the client-side using our own efforts.

Examples of such programs are Strapi and Contentful. In this article, we will explore the integration approach using the latter. One of the main tasks of this approach is to solve the problem of hardcoding data into the markup. When business requirements change, you need to dive into the code and perform a deployment, which can take a lot of time.

In the Headless CMS paradigm, we first set up the data model. For example, we can have a model called “Article” with fields like “Title” and “Article Text.” This stage can be done by developers. Then, content managers can create specific instances of entities based on this model, such as “Article 1,” “Article 2,” and so on.

Next, we will look at examples of implementing this approach to demonstrate how Contentful can help solve the problem of data hardcoding and make the process of updating and modifying content more convenient and efficient for the team.

Integration

In the case of Contentful, we can retrieve data through the standard Web API using the fetch function. After adding access keys, we need to make a request to the Contentful API and save the received data locally for further use:

const [page, setPage] = useState(null);

useEffect(() => {
  window
    .fetch(
      `https://graphql.contentful.com/content/v1/spaces/${process.env.REACT_APP_CONTENTFUL_SPACE}/`,
      {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          // Authenticate the request
          Authorization: `Bearer ${process.env.REACT_APP_CONTENTFUL_API_KEY}`,
        },
        // send the GraphQL query
        body: JSON.stringify({ query }),
      }
    )
    .then((response) => response.json())
    .then(({ data, errors }) => {
      if (errors) {
        console.error(errors);
      }

      // rerender the entire component with new data
      setPage(data.blogPostCollection.items[0]);
    });
}, []);

After obtaining the data from Contentful and storing it locally, further handling of the data becomes familiar and convenient in React components. We can use the data in components just like any other data and render it on the page using JSX:

return (
  <div className={"bg-white rounded-2xl p-6"}>
    <h1 className={"text-6xl text-gray-900"}>{page.title}</h1>
    {page.body.json.content.map((item) => (
      <p className={"text-2xl m-5 p-2 text-gray-800"}>
        {item.content[0].value}
      </p>
    ))}
  </div>
);

Interaction with buttons and event handling in a React application using data from Contentful follows the familiar React pattern. We can easily add a button and pass an onClick handler to the component to respond to user actions.

Server Side Rendering

The process is smooth here as well. During server-side content rendering, we can make requests to Contentful and send the client pre-rendered data. If we are using Next.js, we can fetch data using getServerSideProps (for Server Side Rendering) or getStaticProps (for Static Site Generation). This allows us to provide users with fast and optimized content based on data retrieved from Contentful.

Deploy

In this aspect, everything works smoothly as well. Data is fetched every time there is a request, eliminating the need for redeploying the application. The only consideration is if you are using the Static Site Generation approach, you’ll need to utilize webhooks to observe changes in content. With webhooks, you’ll automatically receive notifications of any changes made in Contentful, allowing you to maintain up-to-date content on your website without manual intervention.

Summary

Pros:

  • Content managers can update page content without involving developers, significantly saving the team’s time.
  • Changes happen in real-time, which is critical when the application deployment takes considerable time, allowing instant content updates without reloading the entire application.
  • Ability to address tasks related to meta tags for the page, such as title, description, keywords, and other metadata.

Cons:

  • When using a large number of fields in Contentful, it’s important to manage them and use meaningful names, especially if the page contains multiple headings and various data.
  • Despite offloading content management to an admin interface, manual coding is still required for the layout. If the design changes, modifications will be needed in the application’s code. Additionally, if new variables or content blocks appear, they must be added to the codebase.

For a more in-depth look at integrating React and Contentful, you can read the details here.

In conclusion, the approach using Headless CMS, such as Contentful, provides efficient content management, saves developers’ time, and enables quick content updates on the website. However, it is essential to consider some drawbacks, such as managing multiple fields and the need for manual coding, which may require additional efforts and attention when adopting this approach.

Demo | Code

Task: Expansion of functionality

When we have a service that can be displayed within an iframe, and it does not have security headers like x-frame-options: SAMEORIGIN or Content-Security-Policy: frame-ancestors none, we can embed the content of this service on our website by displaying it inside an iframe. Some services, such as Typeform, offer integration through iframe and provide SDKs for easy integration. However, for websites without a ready-made SDK, if we want to add interactivity and interaction with the parent application, we need to use the postMessage mechanism.

Integration

Let’s consider a situation where we already have an SDK for integrating another web resource into our React application. Using Typeform as an example, which provides an SDK for seamless integration, we’ll explore this approach.

Typeform offers various display options on websites, such as a slider, popup, and more. The main thing we need to do is install the @typeform/embed-react package and add the corresponding widget to the page:

<Widget
 id="iUesUyX7"
 style={{ width: "100%" }}
 className="my-form"
 onQuestionChanged={() => {
   alert("onQuestionChanged");
 }}
/>

To influence the content inside the Typeform, we can use the hidden fields mechanism in Typeform. By passing data through hidden fields, we can interact with the form’s content and use it for additional processing and analysis.

If we need to respond to events occurring in Typeform, we can pass event handlers onSubmit and onReady to the Widget, just like we do with regular React components. However, it's essential to note that there are some limitations when working with the Typeform SDK, and we cannot directly add custom components to the form.

Server Side Rendering

We face limitations when using SSR, which makes it impossible to index content displayed through an iframe. This limitation applies to any solution using this technology.

Please be aware that the lack of Server Side Rendering support may impact your website’s SEO, as content loaded through an iframe is inaccessible to search engine crawlers and search engines.

Deploy

As we retrieve up-to-date content in the iframe every time there's a request, we are only responsible for displaying the tag. Content updates occur on the side of the third-party service, which allows us to automatically receive the latest information without any intervention on our part. This makes it easier to maintain the freshness of content on our website, as changes in the source are automatically reflected in the iframe.

Summary

Pros:

  • If a product supports integration, working through iframe allows solving business tasks with just a few lines of code.

  • Content changes on the page are automatically reflected in the iframe.

Cons:

  • Does not work with SSR (Server Side Rendering).
  • Not all solutions provide SDKs, which may require writing code wrappers. This can add complexity and increase development time.

Demo | Code

Task: Creating Pages

Method 1: Using Iframe

If we want to display a website inside an iframe and interact with it from our React application, we'll need to use postMessage. It's essential to note that for this approach, we should have the ability to add custom HTML to the page, preferably within the <head>.

The postMessage mechanism allows us to establish secure communication between the parent and embedded iframe application. With postMessage, we can send messages and pass data between the parent and the embedded window, even if they are on different domains.

Integration

We will add code to handle requests from the parent application and code to send events from the Hubspot page. Since Hubspot does not provide an official SDK for integration, we have to use an alternative approach. Although this method is not official, it is one of the available options for interacting with Hubspot through an iframe.

From the React application, we will add a link to Hubspot within an iframe and pass a frameRef, through which we'll interact with the service:

<iframe
  title={"hubspot"}
  src={HUBSPOT_URL}
  className={"w-full h-full rounded-2xl"}
  ref={frameRef}
/>

In the following code example, we have implemented the handling of links and individual buttons in Hubspot, which we defined in hubspotConfig. This allows us to intercept and process events from the service's side. Additionally, we can add configuration to control the behavior of the Hubspot page, such as enabling or disabling specific functionalities:

const hubspotConfig = useMemo(
  () => ({
    handlers: [
      {
        type: "click-button",
        name: CTA_BUTTON_NAME,
        selector: "a[href='#custom']",
      },
    ],
  }),
  []
);

Then, we will subscribe to the message event from the iframe inside a useEffect hook:

useEffect(() => {
 const handler = (event) => {
    if (!event.origin.includes("hubspot") || !event.data.type) { // #1
   return;
    }

  switch (event.data.type) {
        case "click-button": { // #2
          if (
            event.data.payload.buttonName === "link" &&
            event.data.payload.href
          ) {
            window.location.href = event.data.payload.href;
            break;
          }

          alert(`Button: ${event.data.payload.buttonName}`);
          break;
        }
        case "load": {
          if (hubspotConfig && frameRef.current?.contentWindow) {
            frameRef.current.contentWindow.postMessage(
              createAction("set-config", { config: hubspotConfig }), // #3
              "*"
            );
          }
          break;
        }
        default: {
          console.log("There is not event type");
        }
      }
  };

 window.addEventListener("message", handler, false);

 return () => {
   window.removeEventListener("message", handler);
 };
}, [...])

Some interesting points to note:

  1. It’s essential to filter events by checking the origin field, using the condition event.origin.includes("hubspot"). This is crucial because the message event can come from various iframe and workers, and we need to process only those events originating from Hubspot.
  2. When we receive an action with the type click-button generated on the Hubspot side, we handle the click in React. This way, we can react to actions happening inside the iframe and synchronize them with the React application.
  3. We send the set-config action only after the iframe has loaded (action type load - details below). This allows us to inform Hubspot about which buttons we want to handle additionally.

In the Hubspot admin for the page, we add the following script to the head section:

(function () {
  const createAction = (type, payload) => {
    return {
      type,
      payload,
    };
  };
  const sendMessage = (action) => {
    window.parent.postMessage(action, "*");
  };

  window.addEventListener("DOMContentLoaded", () => { // #1
    window.addEventListener( // #2
      "message",
      (event) => {
        if (event.data.type === "set-config") { // #4
          const config = event.data.payload.config;

          config.handlers?.forEach((handler) => {
            const element = document.querySelector(handler.selector);

            switch (handler.type) {
              case "click-button": {
                element?.addEventListener("click", (e) => {
                  e.preventDefault();

                  sendMessage(
                    createAction("click-button", {
                      buttonName: handler.name,
                    })
                  );
                });
                break;
              }
              default: {
                // eslint-disable-next-line no-console
                console.log("There is not the event: " + handler.type);
              }
            }
          });

          const links = document.querySelectorAll("a");

          links.forEach((link) => {
            link.onclick = "event.preventDefault()";

            link.addEventListener("click", (e) => {
              e.preventDefault();

              sendMessage(
                createAction("click-button", {
                  buttonName: "link",
                  href: link.href,
                })
              );
            });
          });
        }
      },
      false
    );

    sendMessage(
      createAction("load", { // #3
        title: document.title,
        description:
          document.querySelector('meta[name="description"]')?.content || "",
      })
    );
  });
})();

The sequence of actions is as follows:

  1. The iframe is loaded, and the DOMContentLoaded event is triggered.
  2. In the parent application (React), we subscribe to the message event.
  3. We send an action with type load to notify the React application that this script is ready to receive config. Here, we can also pass metadata about the page to add them to the React application's <head> section for SEO purposes.
  4. Upon receiving the set-config action, we iterate through all the links and buttons specified in the config and add click handlers to them. Now, when these elements are clicked, we send information about the event to the parent application (React).

This process allows for seamless communication and interaction between the embedded Hubspot page (inside the iframe) and the parent React application. It enables React to handle events triggered within the Hubspot page and to synchronize actions between the two environments effectively.

Server Side Rendering

Using iframe poses challenges with Server Side Rendering (SSR), and the content inside the iframe cannot be indexed by search engines.

The reason is that the content inside the iframe is dynamically loaded on the client-side, not on the server. As a result, when a search engine crawls the page, it cannot see the content within the iframe because it is not available in the HTML markup that is delivered from the server.

This limitation can have a negative impact on SEO and affect content indexing, making the use of iframe unsuitable for applications in terms of accessibility and search engine optimization.

Deploy

Similar to the example with Typeform, in this case, it is also impossible to predict what may be loaded inside the iframe, as the content can change. The content within the iframe will always be up-to-date, and we need to interact with it based on the information received through postMessage and the shared config.

However, it’s essential to note that you should update the “contract” between the parent React application and the iframe in a timely manner. If there are changes inside the iframe that can affect the message exchange or the usage of the shared config, you need to ensure synchronization and appropriate updates in both applications. This guarantees the correct functioning of the integration and the preservation of up-to-date information.

Summary

Pros:

  • Ability to implement functionality of any complexity using both our React application and a third-party service.
  • No need to involve developers to change the content of the page.

Cons:

  • Lack of SSR support, which can have a negative impact on SEO.

  • Need to add a code wrapper for message exchange via postMessage. This approach may be challenging for individuals unfamiliar with this functionality and can cause difficulties during integration and debugging.

The approach of using iframe and postMessage has certain advantages and limitations, and the choice of this approach depends on the project's requirements and the experience of the developers who will be working with it.

Demo | Code

Method 2: Export HTML/CSS/JS

Sometimes, there are situations where we have a ready-made design exported from another website, and we want to integrate it into our React application. However, using iframe or a proxy to interact with the third-party service is not suitable in this case.

If our site has x-frame-options: SAMEORIGIN|DENY or Content-Security-Policy: frame-ancestors none in the HTTP headers, it will be impossible to display it inside an iframe. In such cases, we need to approach the task differently - by copying the markup, including all styles and scripts, and then attempt to insert it into our React application.

To demonstrate this method of integration, we will use the platform Tilda. This service allows us to export files that can be hosted on our own server. It is essential to note that officially modifying the exported content is prohibited, but in this example, we are doing it solely for educational purposes.

Integration

To begin, you need to download a zip archive from the settings of your Tilda website. After successfully exporting the files, you should move them to the appropriate directory for static resources.

In this example, Tilda contains a considerable number of scripts, which can complicate the integration task, especially when dealing with images. The issue arises due to the use of “magic” function calls responsible for lazy loading images. Unfortunately, this approach is not compatible with React routing, as they assume initialization on page load rather than dynamic URL switching. This problem can be resolved by making slight changes to the scripts.

It is also essential to replace all URLs for static assets (images, scripts, styles) from the Tilda domain to your own. Add links in index.html to assets (js, css). For instance, js/lazyload-1.3.min.js becomes %PUBLIC_URL%/tilda/js/lazyload-1.3.min.js.

In the code, we need to place our html content inside a string variable:

export const TILDA_HTML = `
 <div
   id="allrecords"
   data-tilda-export="yes"
   class="t-records"
   data-hook="blocks-collection-content-node"
   data-tilda-project-id="4457918"
   data-tilda-page-id="33161614"
   data-tilda-formskey="599a4bf5b358fcc8f5ce82f8419bbb21"
   data-tilda-lazy="yes"
 >
 ...
 </div>
`

And then insert it into the React component using dangerouslySetInnerHTML:

return (
  <div
    dangerouslySetInnerHTML={{ __html: TILDA_HTML }}
    className={"w-full bg-white rounded-2xl overflow-hidden"}
  />
);

Interaction

To add event handlers, we can use the standard DOM API:

useEffect(() => {
  const tildaButton = document.getElementById("tilda-button");

  const onClick = () => {
    alert("onClick tilda button");
  };

  tildaButton.addEventListener("click", onClick);

  return () => {
    tildaButton.removeEventListener("click", onClick);
  };
}, []);

Please ensure that you add appropriate ids or classes to the elements you want to interact with.

Server Side Rendering

Server-Side Rendering works well here. We render the html content within our React component, which allows web crawlers to easily read and index the content on the page.

Deploy

Every time design changes are made in the Tilda editor, manual intervention is required to go through the entire integration process. You need to download files, place them in the necessary directories, and update all the links. This complicates the automation of the process, which can be inconvenient with frequent changes.

Summary

Pros:

  • Quick integration of ready-made landing pages into your React application. Estimated transfer time is about 2 hours of work.

Cons:

  • Lack of automation for the integration process, requires manual intervention.
  • Unclear behavior of scripts, which can lead to various issues.
  • Official restrictions on modifying exported content from such resources.

Despite its simplicity, this approach is useful when you have limited time and need to quickly transfer a landing page from a builder to a React application. It allows for fast results, especially for urgent projects.

However, in the long run, this approach may become challenging to maintain and scale. After transferring the landing page, any subsequent changes to its design or content may require reintegration, reducing the efficiency and flexibility of development.

Demo | Code

Method 3: React App Builder

If we want to use React components in a familiar format, provide designers with the ability to work with them in a visual editor, and allow content managers to edit data separately from the design, we can consider website builders. There are many interesting solutions for designers and content managers, such as Framer or Webflow. They offer powerful visual editors and content management systems (CMS) or allow data import from external sources. However, today, I will focus on Builder.io because this tool enables working with React components.

Integration

Working with data and design is beyond the scope of this article. Let’s jump right into the code:

const [content, setContent] = useState(null);

useEffect(() => {
  builder
    .get(MODEL_NAME, {
      userAttributes: {
        urlPath: "/builder",
      },
    })
    .promise()
    .then(setContent);

  const clickHandler = () => {
    alert("Click button!");
  };

  document.addEventListener("click-button", clickHandler);

  return () => {
    document.removeEventListener("click-button", clickHandler);
  };
}, []);

return (
  <div className={"w-full h-full"}>
    <BuilderComponent model={MODEL_NAME} content={content} />
  </div>
);

To integrate this tool, we need to render the BuilderComponent and pass the data we obtained from builder.get to it. That's all that is required from the developer for a successful integration.

Additionally, we register a click-button event that will be triggered when a button is clicked. The event configuration is done in the Builder.io admin panel. This allows us to use the event for navigating to other pages or performing actions when the button is clicked.

Additionally, we have the ability to register custom components that will be available in the Builder.io admin panel. This allows us to create and use customized components that cater to the unique needs of our project:

const CustomTicker = ({ title }) => {
 // ...
  return (
    <div>
      {title}: {count}
    </div>
  );
};

Builder.registerComponent(CustomTicker, {
  name: "Custom Ticker",
  inputs: [{ name: "title", type: "text", defaultValue: "Ticks" }],
});

Once we have registered a component, it can easily be added to the page from the left menu in Builder.io. This provides a convenient way to select and place custom components on the page for further editing and display in the React application.

Interaction

When working with custom components, we have access to all the functionality of our application. For example, we can interact with the Context API to manage state and pass data between components.

Additionally, we can easily define our own handlers directly in the visual editor. This allows us to connect Headless CMS and use data in our scripts, providing greater flexibility and possibilities when developing and configuring components.

Server Side Rendering

SSR works excellently if we fetch data on the server. As a result, we create HTML that is almost indistinguishable from the “native” codebase. Developers can use the provided tutorials to successfully integrate Next.js and Builder.io, which offers additional possibilities and advantages when developing and optimizing our application.

Deploy

We obtain the content at the request stage, ensuring that the information is always up-to-date. There is no need to redeploy if we want to make changes to a page created with Builder.io. This provides a quick and convenient way to update content without the need for a full application restart.

Summary

Pros:

  • Easy integration for React developers. We simply need to add the BuilderComponent to the page and register our custom components.
  • Ability to import designs from Figma. However, there might be some nuances and adaptation of the designs for export is required.
  • Increased development speed. By training designers and content managers on this tool, pages can be created without the need for developer intervention.

Cons:

  • The visual editor is not as powerful as other design tools for designers. Understanding CSS is required, as in some cases, CSS code needs to be written directly.
  • Entry barrier. The presence of multiple abstractions may require a few days of learning the concept and understanding the authors’ intentions.

Overall, this tool allows delegating some of the developer’s work to other team members. The main advantage lies in the clear division of responsibilities among team members and reducing interdependence.

Demo | Code

Comparison

When summarizing the subjective conclusions regarding the task of “creating pages,” it’s important to base them on personal experiences with the integration. Understanding that these tools are initially intended for different purposes, there are still some overlaps in terms of their capabilities within a React application:

  • Event Handling: Iframe(Hubspot), Export html(Tilda), Constructor(Builder.io)
  • Visual Editor: Iframe(Hubspot), Export html(Tilda), Constructor(Builder.io)
  • Routing: Iframe(Hubspot), Export html(Tilda), Constructor(Builder.io)
  • Automation of Deployments: Iframe(Hubspot), Constructor(Builder.io)
  • Separation of Data and Design: Iframe(Hubspot), Constructor(Builder.io)
  • Content Indexing (SSR): Export HTML(Tilda), Constructor(Builder.io)
  • SDK: Constructor(Builder.io)
  • Integration Documentation: Constructor(Builder.io)

Conclusion

Undoubtedly, the choice of the tool depends on the specific task at hand. If your goal is to deliver content, a Headless CMS would be suitable. Using an SDK might be a viable solution if you need to add specific functionality, such as forms. The purpose of this article was to inspire experimentation with various approaches and encourage the pursuit of solving business challenges using the most suitable tools.

Demo | Code

Thank you! Best of luck in your experiments and development as well! If you have any more questions or need further assistance in the future, feel free to reach out. Happy experimenting!


Written by rusanovn | I am a software engineer with extensive experience in front-end development. Additionally, I am a digital nomad :)
Published by HackerNoon on 2024/01/12