Integrate React JS into Sencha Ext JS

Holly Springsteen
7 min readAug 18, 2020

--

Create a simple React library usable within a Sencha Ext JS application

Adding React to an existing Ext JS application can be challenging. As of today (Aug 14, 2020) there are only a few solutions present in the wild for how best to incorporate a React application into an existing Ext application. That said, however, there are no great methods for incorporating components at a more module level or in a way that allows for the common use of JSX.

Recently, I approached this exact problem. While we would all love to throw away a project and start something new, that is unfortunately not the reality under most circumstances. Often the best solution, when possibly, is to allow for the integration of new content into an existing system. The goal, in this particular case, was to allow front-end engineering teams to develop new content in React while integrating into the existing application and a paced conversion of legacy content.

In this guide, I am going to create a React library that loads a basic header with a couple sub-components. Then I will take that code and turn it into a re-usable npm package that can be used in the browser and node with webpack, babel, and typescript. From that point we can easily integrate the React library into Ext containers via the React vanilla JS library.

Building a Basic React Library

🔗 See the repo on GitHub

Step 1: Create & init the app

$ mkdir react-library && cd react-library && npm init

Step 2: Create Some Components & Header Module

$ npm install react react-dom$ mkdir src src/components src/modules src/components/Title src/components/Nav src/modules/Header

With the structure in place we can create our components and styling.

Nav

src/components/Nav/nav.css

.global-nav ul {
display: flex;
justify-content: flex-end;
list-style: none;
padding: 0;
}
.global-nav ul li {
padding: 0 5px;
}
.global-nav ul li:not(:first-child) {
margin-left: 5px;
}

src/components/Nav/Nav.js

import React from 'react';const Nav = (props) => (
<nav {...props}>
<ul>
<li>
<a href="#">Home</a>
</li>
<li>
<a href="#">About</a>
</li>
<li>
<a href="#">Contact</a>
</li>
</ul>
</nav>
);
export default Nav;

Title

src/components/Title/Title.js

import React from 'react';

const Title = ({ children, ...rest }) => (
<h1 {...rest}>{children || 'React Library'}</h1>
);

export default Title;

Header

src/modules/Header/header.css

.global-header {
align-content: center;
align-items: center;
display: flex;
justify-content: space-between;
height: 64px;
}

src/modules/Header/Header.js

import React from 'react';
import Nav from '../../index.js';
import Title from '../../components/Title/Title';
import "./header.css";
const Header = (props) => (
<header className="global-header" {...props}>
<Title className="global-title" />
<Nav className="global-nav" />
</header>
);
export default Header;

Step 3: Setup Exports for Library

Use ES6 re-exporting to create a common library. Read more about this on 2ality.

src/index.js

// Components
import
Nav from './components/Nav/Nav';
import Title from './components/Title/Title';
// Modules
import
Header from './modules/Header/Header';
// Library
export
default {
components: {
Nav,
Title,
},
modules: {
Header,
},
};

Step 4: Testing

We’re going to utilize react-testing-library and jest for unit testing in this library. Follow the setup guidelines for react-testing-library.

Once installed and set up you’ll need to set up some basic testing for the existing library assets. See the sample library for how I set it up for the demo.

Now run jest

From here we’ll see our first error:

FAIL  src/modules/Header/Header.test.js
● Test suite failed to run
/Users/hollyos/dev/react-library/rtl.setup.js:2
import '@testing-library/jest-dom/extend-expect';
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
SyntaxError: Unexpected stringat Runtime._execModule (node_modules/jest-runtime/build/index.js:1179:56)

And I thought this would have been the easiest part. Apparently, jest doesn't like the use of the import statement.

At the moment, with just an index.js file and jest, its going to be running inside a node.js environment where import is not yet supported.

Transpile

Step 1: Babel

This is where babel comes into play. babel will transpile ("translate/compile") your js files from ES6 (where you can use import) and ES5 which node.js supports. Jest has a section on enabling babel support here. We'll have to install a few more packages, and a little configuration.

Install & Configure Babel

$ npm install --save-dev babel-jest @babel/core @babel/preset-env @babel/polyfill @babel/preset-react @babel/plugin-transform-modules-umd @babel/core @babel/cli

babel.config.js

module.exports = {
presets: [
[
'@babel/preset-env',
{
targets: {
node: 'current',
},
},
],
"@babel/preset-react",
],
plugins: [
['@babel/plugin-transform-modules-umd', {
exactGlobals: true,
globals: {
index: 'ReactLibrary'
}
}],
],
};

We’ll also want to add a "babel" script to our package.json

...
"scripts": {
"babel": "npx babel src/index.js -d lib",
...

Then when you run jest, you get to the next error:

FAIL  src/modules/Header/Header.test.js
● Test suite failed to run
Cannot find module 'react' from 'node_modules/@testing-library/react/dist/pure.js'Require stack:
node_modules/@testing-library/react/dist/pure.js
node_modules/@testing-library/react/dist/index.js
rtl.setup.js
at Resolver.resolveModule (node_modules/jest-resolve/build/index.js:307:11)
at Object.<anonymous> (node_modules/@testing-library/react/dist/pure.js:31:37)

Aha, progress. Now it knows how to read the import statement, and the test runs. Now i'm missing a dependency - react. npm install --save react. Now re-run jest and all green!

Then we can run our babel script which will create a lib/index.js file ready to be consumed as a script tag in your browser.

$ yarn babel

Using the library in a browser using a script tag

Step 1: Create a Demo

From the root directory let’s create a base HTML file.

index.html

<!DOCTYPE html>
<html lang="en">
...
<script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
...
<script src="../lib/index.js"></script>
<script>
const ReactElement = React.createElement;
const { Header } = ReactLibrary.default.modules;
ReactDOM.render(
ReactElement(Header, null, 'React Component'),
document.getElementById('root')
);
</script>

Step 2: Serve the Demo

Add a script to serve your demo with http-server

package.json

...
"scripts": {
...
"serve": "npx http-server --cors -o",
...

And serve the demo:

$ npm run serve> react-library@1.0.0 serve /Users/hollyos/dev/react-library
> npx http-server demo
Starting up http-server, serving demo
Available on:
http://127.0.0.1:8080
http://192.168.1.222:8080
Hit CTRL-C to stop the server
open: http://127.0.0.1:8080

Once the page loads we’ll see that we get another error:

Warning: React.createElement: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.Uncaught Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.

if we check in the console the value of ReactLibrary.default.modules.Header we’ll see that we’re getting undefined

> ReactLibrary.default.modules.Header
> undefined

All we did was transpile the index.js file. The import code isn’t there, and the browser doesn’t know that its supposed to search your node_modules directory for it (nor can it).

What we need to do is create one js file, that has all the dependencies of that library built into it, so that you only need to add the 1 script tag to your site. Babel can’t handle your dependencies, it can only translate.

Step 3: Webpack

We’ll use webpack to take a look at our file, and everything that is imported/required will then get “packed” into one file.

Install & Setup Webpack

$ npm install --save-dev webpack webpack-cli babel-loader style-loader css-loader

webpack.config.js

const path = require('path');module.exports = {
entry: './index.js',
output: {
path: path.resolve(__dirname, 'lib'),
filename: 'react-library.bundle.js',
library: 'ReactLibrary',
libraryTarget: 'var',
},
module: {
rules: [
{
test: /\.js$/,
use: 'babel-loader',
},
{
test: /\.css$/,
use: [
'style-loader',
'css-loader',
],
},
],
};

package.json

...
"scripts": {
...
"webpack": "npx webpack",
...

index.html

...
- <script src="./lib/index.js"></script>
+ <script src="./lib/react-library.js"></script>
...

Now that everything is setup and the config is in place we can run the webpack script.

npm run webpack

Refresh the page, and success!

Integrate

Now that we have our library built and working in browser we can incorporate our library into an existing Ext application, much in the same way. We’ll want to update our app.json and then create a container for the React components.

Step 1: Include React, ReactDOM, and Our New Library

Here we’ll need to ensure that we include React into our project from the index.html and then bundle our react library bundle into the ext application.

index.html

...
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
...

app.json

...
"js": [
{ "path": "path/to/library/react-library.bundle.js" },
...

Step 2: Update Webpack & React Inclusion

With the addition of React into the Ext app we’ll run into an error when we try to utilize hooks within our react-library where there’s multiple versions of React. Unfortunately, this error is not very clear and can lead to some confusion.

Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
See https://fb.me/react-invalid-hook-call for tips about how to debug and fix this problem.

We’ll need to update our webpack.config.js to exclude React from the bundle. But first, let’s create a dummy file that will add React to the global scope.

dummyReact.js

module.exports = window.React;

webpack.config.js

module.exports = {
...
resolve: {
alias: {
react: 'dummyReact.js',
},
},
externals: {
react: 'React',
},
};

Step 3: Access react-library Within Ext

Create component inside Ext container with the proper component or module. We’ll do this much the same way we did in our demo.

ReactContainer.js

Ext.define('view.ReactContainer', {
extend: 'Ext.panel.Panel',
alias: 'widget.reactContainer',
listeners: {
afterrender: function () {
const ReactElement = React.createElement;
const { Header } = ReactLibrary.default.modules;
ReactDOM.render(
ReactElement(Header, null, 'React Component'),
document.getElementById('root')
);
}
},
});

And there you have it! You’ve now built a custom React component library and incorporated it into an existing Sencha Ext JS application.

Resources

Sample Library

Research Articles

--

--

Holly Springsteen
Holly Springsteen

Written by Holly Springsteen

Accomplished woman with a diverse career: USMC Veteran, photographer, software dev, tech exec, Divemaster, and home chef. _____________ HIT FOLLOW ⤵

Responses (3)