There are many ways to integrate React into your applications, in fact, we even showed one way once upon a time. When we started our most recent project we sat down and thought about how we would do it better this time. Of course, the earlier approach could have worked but there were a few things that were different about this project that gave us pause.
The majority of our page this time was rendered on the backend via Django. This meant that our frontend would not be a full-blown stand-alone Single Page Application, but would instead be composed of small independent sections on the page, each with its own logic and data handling.
We wanted to define our components in such a way that using them would be as easy as calling a native HTML tag, for example:
<my-component school-id="1"/>
Well, first we’ll need to define the two types of components to see how they differ.
React Components are independent and reusable bits of code, think of them as React functions. They ideally work in isolation and return HTML. As the name suggests, they are specific to React.
Here’s a simple example of a React component
import React from "react"
const MyComponent = () => {
return <div>Hello World</div>
}
export default MyComponent
Of course, you can load a React component on an HTML page, for example
import { createRoot } from 'react-dom/client';
const domNode = document.getElementById('container');
const root = createRoot(domNode);
root.render(<MyComponent />);
This works well but isn’t very declarative and isn’t the way we envisioned initially.
Web Components is a suite of different technologies that allow you to create reusable custom elements that are natively supported by browsers and have their functionality encapsulated away from the rest of your code.
Unlike React components, they are not restricted to a framework, but the standard API is complex, low-level, and somewhat imperative. They are made of three technologies; custom elements, shadow DOM, and HTML templates. I won’t go into detail in this post but briefly;
Custom elements are HTML elements whose behavior is defined by the web developer, they extend the set of elements available in the browser. They have good browser support on desktop and mobile.
class MyComponent extends HTMLElement {
static observedAttributes = ["color", "size"];
constructor() {
// Always call super first in constructor
super();
}
connectedCallback() {
console.log("Custom element added to page.");
}
}
customElements.define("my-component", MyComponent);
The Shadow DOM is a hidden DOM tree attached to the web component that encapsulates its logic so it does not interfere with the logic on the rest of the page. The shadow Dom is supported across most major browsers.
HTML Templates are elements that you can use to create a flexible template that can then be used to populate the shadow DOM of a web component. Just like the other two parts, browser support is great.
<template id="my-template">
<p>My paragraph</p>
</template>
let template = document.getElementById("my-template");
let templateContent = template.content;
document.body.appendChild(templateContent);
Most of the complexity of Web Components comes from the Shadow DOM and Templates, but thankfully we will only be using Custom Elements in our approach.
If you google Web Components and React you get this description:
“Web components are native to the browser, so they can be used with any JavaScript framework or library. React components, on the other hand, are specific to the React library. React is a JavaScript library for building user interfaces, and it uses a virtual DOM to efficiently update the actual DOM.”
So the question is: Can we use React Components as Custom Elements?
If we look at the definitions we made before, they do similar enough things that this should be possible.
Let’s take a second to ask why. You can build Web Components without a framework just fine, so why take the trouble to use React?
As I said before, the standard API for web components is complex and low-level. You can use frameworks like Lit to get around this but doing that wouldn’t leverage existing knowledge.
You would also have to implement data binding and state management by yourself, and you don’t get reactivity by default. The library ecosystem for web components is still small when compared to React and there aren’t as many UI kits.
However, browser support for Web Components has become pretty good in recent years and we wanted to try them out.
React makes things easier since it provides unidirectional data flow, makes state management relatively easy, and re-renders components based on state changes. You would also have access to its vast and powerful library ecosystem, better developer support, documentation, and community resources.
React also makes UI development easier through JSX. Thanks to its component-based architecture and hooks, React makes organizing business logic much cleaner.
Also, React is fun so we preferred to use it to build our components.
It turns out, it was pretty easy. React-to-web-component is an npm package that converts React components to custom elements and lets you use them as native elements that don't need to be mounted through React.
import r2wc from "@r2wc/react-to-web-component"
const MyComponent = r2wc(ReactComponent)
customElements.define("my-component", MyComponent)
We used Webpack to build the js file, loaded it into the template, and called the web component as if it were a native tag.
<body>
<h1>Testing</h1>
<my-component></my-component>
</body>
We aren’t completely done just yet since there is still a piece missing, the ability to pass props from the template. Luckily this is also easy.
First, you have to define your props when you define the Web Component, as shown below:
const MyComponent = r2wc(ReactComponent, {
props: {
schoolId: 'number',
readOnly: 'boolean'
}
})
A few things to note
You can read more about the types here.
Now using the attributes is simply a matter of passing the data to the web component:
<my-component
school-id="{{ school.id }}"
read-only="{{ read_only }}"/>
Notice that here we are passing values from Django into the web component but this approach will work with any backend or template engine not just Django. The values are provided as strings, which then will be parsed by r2wc
and passed on into the react component as JS primitives or objects.
I mentioned earlier that we used Webpack, so let’s talk more about that. The short version is that if you build the frontend, you can simply include the script in your template; it works, just like that.
But we also wanted to make developing easier so we wanted it to work in development mode, with live changes.
devServer: {
...
liveReload: true,
devMiddleware: {
writeToDisk: true
}
...
}
Just like last time, we used Webpack to save everything to disk every time we made changes to the frontend, then had Django use the frontend build directory as static files.
We used HTMLWebpackPlugin
, just like last time, alongside MiniCssExtractPlugin
to generate our JS and CSS files.
The result was that we had our React components available to our Django application as Web components.
We started just trying to see if it was doable and in the end, we had a working solution that was ridiculously simple to do. Using React components as Web Components was a bonus and added a new tool to our toolbag.
However this wasn’t the only new thing we tried with our project, we also made a better API like a ninja which you can read about on our website.