# Building a Standalone React Widget with UMD and Dynamically Loading It in Another React App Using Single-SPA

## **Introduction to Microfrontends and Single-SPA**

As web applications grow in complexity, maintaining a monolithic frontend can become challenging. **Microfrontends** solve this by breaking down a large frontend application into smaller, manageable parts that can be developed and deployed independently.

**Single-SPA** is a powerful JavaScript framework that enables this architecture by dynamically loading and mounting frontend applications or components (parcels) at runtime. It allows different teams to develop features in different frameworks (React, Vue, Angular) while keeping them seamlessly integrated in one application.

---

## **Why Use Single-SPA?**

* **Scalability:** Microfrontends enable large teams to work independently without conflicts.
    
* **Tech Stack Independence:** Different frameworks can be used within the same application.
    
* **Incremental Upgrades:** Individual microfrontends can be updated without affecting the entire system.
    
* **Code Reusability:** Modules can be shared across multiple applications without duplication.
    

---

## **Setting Up the Host Application**

The host app will be responsible for embedding the microfrontend parcel.

### **Step 1: Initialize the Host App**

Run the following commands to set up a React-based host application using Vite:

```sh
mkdir host-app && cd host-app
yarn create vite . --template react
yarn install
```

### **Step 2: Install Dependencies**

```sh
yarn add single-spa single-spa-react
```

### **Step 3: Expose React and ReactDOM**

Modify `src/main.jsx` to ensure React and ReactDOM are available globally:

```javascript
import React from "react";
import * as ReactDOM from "react-dom/client"; // ✅ Use react-dom/client for React 18+
import { StrictMode } from "react";
import App from "./App.jsx";

// ✅ Expose the correct version of ReactDOM (from react-dom/client)
window.React = React;
window.ReactDOM = ReactDOM;

console.log("React is now exposed on window:", window.React);
console.log("ReactDOM is now exposed on window:", window.ReactDOM);

ReactDOM.createRoot(document.getElementById("root")).render(
  <StrictMode>
    <App />
  </StrictMode>
);
```

### **Step 4: Add SystemJS to the HTML File**

Modify `index.html` to include **SystemJS** for loading the parcel dynamically:

```html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Host Application</title>
    <script src="https://unpkg.com/systemjs/dist/system.js"></script>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.jsx"></script>
  </body>
</html>
```

### **Step 5: Load the Parcel in** `App.jsx`

Modify `src/App.jsx` to embed the microfrontend parcel:

```javascript
import React from "react";
import { mountRootParcel } from "single-spa";
import Parcel from 'single-spa-react/parcel'; 

function App() {
  return (
    <div>
      <h1>Host Application</h1>
      <Parcel
        mountParcel={mountRootParcel}
        config={() => window.System.import('http://localhost:3000/parcel.js')}
        wrapWith="div"
        wrapStyle={{ width: "100%", height: "400px" }}
      />
      <hr />
    </div>
  );
}

export default App;
```

---

## **Setting Up the Microfrontend Parcel**

### **Step 1: Initialize the Parcel App**

```sh
mkdir carousel-widget && cd carousel-widget
yarn create vite . --template react
yarn install
```

### **Step 2: Install Dependencies**

```sh
yarn add webpack webpack-cli babel-loader @babel/preset-react @babel/preset-env style-loader css-loader single-spa-react
```

### **Step 3: Configure Webpack for UMD Build**

Modify `webpack.config.cjs`:

```javascript
const path = require('path');

module.exports = {
  entry: "./src/index.js",
  output: {
    filename: "parcel.js",
    path: path.resolve(process.cwd(), "dist"),
    library: "parcel",
    libraryTarget: "umd", // ✅ Must be "umd"
    globalObject: "window",
    publicPath: "/",
  },
  mode: "production",
  module: {
    rules: [
      {
        test: /\.jsx?$/, // Ensures both .js and .jsx files are processed
        exclude: /node_modules/,
        use: {
          loader: "babel-loader",
          options: {
            presets: ["@babel/preset-env", "@babel/preset-react"],
          },
        },
      },
      {
        test: /\.css$/,
        use: ["style-loader", "css-loader"],
      },
    ],
  },
  resolve: {
    extensions: [".js", ".jsx"],
  },
  externals: {
    react: "React",
    "react-dom": "ReactDOM",
  },
};
```

### **Step 4: Implement the Parcel Component**

Modify `src/index.js`:

```javascript
import React from "react";
import Carousel from "./Carousel";
import "./carousel.css";

const roots = new Map();

export function mount(props) {
  return new Promise((resolve, reject) => {
    try {
      const { domElement } = props;
      let root = roots.get(domElement);
      if (!root) {
        root = window.ReactDOM.createRoot(domElement);
        roots.set(domElement, root);
      }
      root.render(<Carousel />);
      resolve();
    } catch (error) {
      reject(error);
    }
  });
}

export function unmount(props) {
  return new Promise((resolve, reject) => {
    try {
      const { domElement } = props;
      if (roots.has(domElement)) {
        roots.get(domElement).unmount();
        roots.delete(domElement);
      }
      resolve();
    } catch (error) {
      reject(error);
    }
  });
}
```

Create `src/Carousel.js`:

```javascript
import React, { useState } from "react";
import "./carousel.css";

const images = [
  "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTz5_pFXLFlros8tRZoOHLVZVI30KJEU411IQ&s",
  "https://kinsta.com/wp-content/uploads/2023/04/react-must-be-in-scope-when-using-jsx.jpg",
  "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcS58TIBqANB1PTufYIQTmZJBVAj4oN1KLVJFjM-0IOWVYMof6KNE6zhRjrUgHnH5CaWnwo&usqp=CAU",
];

const Carousel = () => {
  const [currentIndex, setCurrentIndex] = useState(0);

  const prevSlide = () => {
    setCurrentIndex((prevIndex) => (prevIndex === 0 ? images.length - 1 : prevIndex - 1));
  };

  const nextSlide = () => {
    setCurrentIndex((prevIndex) => (prevIndex === images.length - 1 ? 0 : prevIndex + 1));
  };

  return (
    <div className="carousel-container">
      <button onClick={prevSlide} className="carousel-btn left">❮</button>
      <img src={images[currentIndex]} alt={`Slide ${currentIndex + 1}`} className="carousel-image"/>
      <button onClick={nextSlide} className="carousel-btn right">❯</button>
    </div>
  );
};

export default Carousel;
```

Create `src/carousel.`css

```css
.carousel-container {
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
  width: 100%;
  height: 100%;
  background-color: #f0f0f0;
}

.carousel-image {
  width: 400px;
  height: 300px;
  object-fit: cover;
  border-radius: 10px;
}

.carousel-btn {
  position: absolute;
  background: rgba(0, 0, 0, 0.5);
  color: white;
  border: none;
  padding: 10px;
  font-size: 20px;
  cursor: pointer;
}

.left {
  left: 10px;
}

.right {
  right: 10px;
}

.carousel-btn:hover {
  background: rgba(0, 0, 0, 0.8);
}
```

### **Step 5: Update the package.json scripts**

```json
  "scripts": {
    "serve": "serve -s dist --cors",
    "build": "webpack --config webpack.config.cjs"
  },
```

### **Step 6: Build and Serve the Parcel Locally**

```sh
yarn add serve --dev
yarn build
yarn serve
```

Now, the parcel is accessible at.[`http://localhost:3000/parcel.js`](http://localhost:3000/parcel.js)

---

## **Final Steps: Run the Host and Parcel Apps**

### **1️⃣ Start the Parcel App**

```sh
cd carousel-widget
yarn serve
```

### **2️⃣ Start the Host App**

```sh
cd host-app
yarn dev
```

Now open [`http://localhost:5173/`](http://localhost:5173/) (or your Vite dev server URL) to see the host app dynamically loading the parcel! 🚀

---

## **Conclusion**

This guide covered setting up a **Single-SPA microfrontend with React**, exposing React versions properly, and ensuring smooth mounting/unmounting of parcels. By following this, you can create scalable and modular frontend architectures. 🚀

---

## **Github**

[https://github.com/adeeshsharma/react-single-spa-widget](https://github.com/adeeshsharma/react-single-spa-widget)
