React Custom Element Implementation Guide for AIV
This guide provides a complete implementation for creating React components that can be deployed as custom elements and used within AIV applications.
Overview
The goal is to create a React component that can be:
- Built as a custom element (Web Component)
- Deployed independently
- Used within Angular applications
- Communicate with Angular through attributes and events
Project Structure
react-custom-element/
├── src/
│ ├── components/
│ │ ├── ReactComponent.jsx # Main React component
│ │ └── ReactComponent.css # Component styles
│ ├── CustomElement.js # Custom element wrapper
│ └── index.js # Entry point
├── public/
│ └── index.html # Demo HTML
├── dist/ # Built files
├── webpack.config.js # Webpack configuration
├── package.json # Dependencies and scripts
├── .babelrc # Babel configuration
├── demo.html # Standalone demo
└── README.md # Documentation
Step 1: Setup the React Custom Element Project
1.1 Create the React Component
// src/components/ReactComponent.jsx
import React, { useState, useEffect } from 'react';
import './ReactComponent.css';
const ReactComponent = ({
title = 'React Component',
message = 'This is a React component wrapped as a custom element.',
theme = 'primary'
}) => {
const [count, setCount] = useState(0);
const [currentTime, setCurrentTime] = useState(new Date());
useEffect(() => {
const timer = setInterval(() => {
setCurrentTime(new Date());
}, 1000);
return () => clearInterval(timer);
}, []);
const handleIncrement = () => {
setCount(prev => prev + 1);
};
const handleReset = () => {
setCount(0);
};
return (
<div className={`react-component react-component--${theme}`}>
<div className="react-component__header">
<h2 className="react-component__title">{title}</h2>
<div className="react-component__time">
{currentTime.toLocaleTimeString()}
</div>
</div>
<div className="react-component__content">
<p className="react-component__message">{message}</p>
<div className="react-component__counter">
<h3>Counter: {count}</h3>
<div className="react-component__buttons">
<button
className="react-component__button react-component__button--primary"
onClick={handleIncrement}
>
Increment
</button>
<button
className="react-component__button react-component__button--secondary"
onClick={handleReset}
>
Reset
</button>
</div>
</div>
<div className="react-component__features">
<h4>Features:</h4>
<ul>
<li>React 18 with hooks</li>
<li>Custom element wrapper</li>
<li>Theme support</li>
<li>Real-time updates</li>
<li>Interactive components</li>
<li>Shadow DOM encapsulation</li>
</ul>
</div>
</div>
</div>
);
};
export default ReactComponent;
1.2 Create the Custom Element Wrapper
// src/CustomElement.js
import React from 'react';
import { createRoot } from 'react-dom/client';
import ReactComponent from './components/ReactComponent';
class ReactCustomElement extends HTMLElement {
constructor() {
super();
this.shadow = this.attachShadow({ mode: 'open' });
this.root = null;
}
connectedCallback() {
this.render();
}
disconnectedCallback() {
if (this.root) {
this.root.unmount();
this.root = null;
}
}
static get observedAttributes() {
return ['title', 'message', 'theme'];
}
attributeChangedCallback(name, oldValue, newValue) {
if (oldValue !== newValue) {
this.render();
}
}
getProps() {
return {
title: this.getAttribute('title') || undefined,
message: this.getAttribute('message') || undefined,
theme: this.getAttribute('theme') || 'primary'
};
}
render() {
if (!this.root) {
const container = document.createElement('div');
this.shadow.appendChild(container);
this.root = createRoot(container);
}
const props = this.getProps();
this.root.render(React.createElement(ReactComponent, props));
}
}
// Define the custom element
customElements.define('react-custom-element', ReactCustomElement);
export default ReactCustomElement;
1.3 Create the Entry Point
// src/index.js
import ReactCustomElement from './CustomElement';
// Export the custom element class for manual registration
export { ReactCustomElement };
// Export a function to register the custom element
export function registerReactCustomElement() {
if (!customElements.get('react-custom-element')) {
customElements.define('react-custom-element', ReactCustomElement);
}
}
// Auto-register if this module is loaded
registerReactCustomElement();
// Export as default for UMD builds
export default ReactCustomElement;
Step 2: Build Configuration
2.1 Webpack Configuration
// webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'react-custom-element.js',
library: {
name: 'ReactCustomElement',
type: 'umd',
export: 'default'
},
globalObject: 'this'
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [
'@babel/preset-env',
['@babel/preset-react', { runtime: 'automatic' }]
]
}
}
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}
]
},
resolve: {
extensions: ['.js', '.jsx']
},
plugins: [
new HtmlWebpackPlugin({
template: './public/index.html',
filename: 'index.html'
})
],
devServer: {
static: {
directory: path.join(__dirname, 'public')
},
port: 3000,
hot: true
}
};
2.2 Package.json
{
"name": "react-custom-element",
"version": "1.0.0",
"description": "React custom element for Angular integration",
"main": "dist/index.js",
"scripts": {
"build": "webpack --mode production",
"dev": "webpack --mode development --watch",
"start": "webpack serve --mode development"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@babel/core": "^7.22.0",
"@babel/preset-env": "^7.22.0",
"@babel/preset-react": "^7.22.0",
"babel-loader": "^9.1.0",
"css-loader": "^6.8.0",
"html-webpack-plugin": "^5.5.0",
"style-loader": "^3.3.0",
"webpack": "^5.88.0",
"webpack-cli": "^5.1.0",
"webpack-dev-server": "^4.15.0"
}
}
Step 3: Build and Deploy
3.1 Build the Custom Element
npm install
npm run build
This creates dist/react-custom-element.js which contains the custom element.
3.2 Deploy Options
Here’s the improved version of your documentation for section 3.2 Deploy Options, with clearer structure, grammar corrections, and more professional language:
3.2 Deploy Options
To deploy your React custom app, follow the steps below carefully:
1. Create app.json
Within the same folder where your project build is located, create a file named **app.json**.
Use the following structure for the file. Do not modify the structure or include comments in the JSON:
{
"outputs": [],
"appJs": [
"react-custom-element.js"
],
"framework": "react",
"inputs": [],
"appCss": [],
"name": "React Custom App",
"selector": [
"react-custom-element"
],
"id": 1,
"tag": "react-custom-element",
"datasets": [{}]
}
✅ Note:
- Replace
"name"with your actual app name.- Ensure the
"id"is unique.- The
"tag"and"selector"should match your custom element tag.
2. Prepare ZIP File
- Rename the main folder to match the actual app name if needed.
- Ensure the folder structure inside the ZIP matches the sample project structure (refer to the provided sample).
- Include your
app.jsonfile inside this folder. - Create a
.ziparchive of the folder.
3. Upload to AIV
-
Login to AIV. If you are already logged in, continue to the next step.
-
Click on the hamburger menu (☰) on the left sidebar and select External App.
-
On the External App page, scroll down to the bottom and click the Upload button.
-
In the upload dialog:
- Click the Upload icon to open the file selector.
- Locate and select your ZIP file (e.g.,
react.zip). The app name will be auto-detected fromapp.json.
-
(Optional) Add up to 5 datasets to your app. These datasets will be injected into your React app and accessible via
data1,data2,data3,data4, anddata5. -
Once done, click Upload to complete the process. Your app will now be available within AIV.