single-spa-react
single-spa-react is a utility library designed to simplify the integration of React applications and components into a single-spa microfrontend architecture. It provides helper functions that adapt React's rendering and lifecycle methods (bootstrap, mount, unmount) to single-spa's expectations. The current stable version is 6.0.2, with ongoing active development including patch releases and a v7.0.0-beta.0 in progress. Key differentiators include robust support for React 18's `createRoot` API, explicit compatibility options for older React versions, built-in error boundary mechanisms, and a dedicated `<Parcel>` component for managing framework-agnostic micro-frontends within a React application. It focuses on ensuring React applications adhere to single-spa's lifecycle contracts and work seamlessly within a polyglot microfrontend environment.
Common errors
-
TypeError: ReactDOM.render is not a function
cause Attempting to use `ReactDOM.render` with React 18 in `single-spa-react` after the default rendering mechanism changed to `createRoot`.fixUpdate your `singleSpaReact` configuration to use `ReactDOMClient` from `react-dom/client` (for React 18+) and remove any explicit `renderType: 'render'` if you are on React 18. If intentionally using React 17 or below, explicitly set `renderType: 'render'`. -
Error: Target container is not a DOM element.
cause The `domElementGetter` function provided to `singleSpaReact` is returning `null` or an invalid DOM element, preventing React from mounting.fixEnsure your `domElementGetter` function correctly selects or creates a valid DOM element in the document. Verify the element ID or class name, and ensure the element exists by the time `mount` is called. Remember that `domElementGetter` is not required when creating a single-spa parcel. -
Unable to resolve bare specifier 'react' from <your-app-bundle.js>
cause This typically occurs in single-spa setups using SystemJS and import maps where the `react` (or `react-dom`) dependency is not correctly externalized in the application's build or is missing from the root config's import map.fixConfigure your application's bundler (e.g., Webpack `externals`) to treat `react` and `react-dom` as external dependencies. Ensure your `single-spa` root configuration's import map correctly defines the URLs for the shared `react` and `react-dom` libraries.
Warnings
- breaking Upgrading to `single-spa-react` v5.0.0 or higher, particularly when targeting React 18, requires significant changes to ReactDOM usage. The default `renderType` switched from `render` to `createRoot` to align with React 18's new concurrent rendering API.
- breaking Version 6.0.0 introduced 'Enhanced compatibility with various bundlers and TypeScript `moduleResolution` strategies,' along with refined package export patterns. This change, while improving modern tooling integration, may cause issues with older or non-standard bundler configurations or TypeScript setups that relied on specific internal module paths.
- gotcha For React applications (>=16) integrated with single-spa, it is considered best practice to implement `componentDidCatch` (or an equivalent functional error boundary) in your root component. Failure to do so will result in console warnings from `single-spa-react` and could lead to the entire application unmounting unexpectedly on an unhandled error.
- gotcha Internet Explorer 11 (IE11) support has been deprecated or removed in `single-spa` core v6.0.0, and `single-spa-react` may follow a similar trajectory. Using recent versions with IE11 may lead to compatibility issues.
Install
-
npm install single-spa-react -
yarn add single-spa-react -
pnpm add single-spa-react
Imports
- singleSpaReact
const singleSpaReact = require('single-spa-react').default;import singleSpaReact from 'single-spa-react';
- SingleSpaContext
import SingleSpaContext from 'single-spa-react/SingleSpaContext';
import { SingleSpaContext } from 'single-spa-react'; - Parcel
import { Parcel } from 'single-spa-react';import Parcel from 'single-spa-react/parcel';
Quickstart
import React from 'react';
import ReactDOMClient from 'react-dom/client'; // For React 18+
import singleSpaReact from 'single-spa-react';
// A simple React component for the microfrontend
const MyReactApp = ({ name, mountParcel }) => {
const [count, setCount] = React.useState(0);
React.useEffect(() => {
console.log(`${name} mounted!`);
return () => console.log(`${name} unmounted!`);
}, [name]);
return (
<div style={{
padding: '20px',
border: '1px solid #61dafb',
borderRadius: '8px',
margin: '10px',
backgroundColor: '#282c34',
color: 'white'
}}>
<h2>Hello from {name}!</h2>
<p>Count: {count}</p>
<button onClick={() => setCount(c => c + 1)} style={{ padding: '8px 16px', borderRadius: '4px', cursor: 'pointer' }}>Increment</button>
<p>This is a single-spa React application.</p>
{/* Optionally mount a parcel here */}
{/* <Parcel config={mountParcel} wrapWith='div' /> */}
</div>
);
};
// A basic error boundary component is highly recommended for robustness.
class ErrorBoundary extends React.Component {
state = { hasError: false };
static getDerivedStateFromError(error) { return { hasError: true }; }
componentDidCatch(error, errorInfo) {
console.error("React component error:", error, errorInfo);
}
render() {
if (this.state.hasError) {
return <h1 style={{ color: 'red' }}>Something went wrong in {this.props.name}.</h1>;
}
return this.props.children;
}
}
// Configure single-spa-react with your React application.
const lifecycles = singleSpaReact({
React, // Pass the React library
ReactDOMClient, // Use ReactDOMClient for React 18+ for createRoot. For React 17-, use ReactDOM and specify renderType: 'render'.
rootComponent: MyReactApp, // Your top-level React component
// A function that returns the DOM element where the React app will be mounted.
domElementGetter: () => {
let el = document.getElementById('react-app-container');
if (!el) {
el = document.createElement('div');
el.id = 'react-app-container';
document.body.appendChild(el); // Append to body or a specific parent defined in your root config
}
return el;
},
// Recommended: Provide an error boundary for better fault tolerance.
errorBoundary: ErrorBoundary,
// Optional: Pass custom props to your root component.
getAppProps: ({ name, ...props }) => ({ name: name || 'Default React App', ...props }),
// Suppress warning if using an errorBoundary function/class
suppressComponentDidCatchWarning: true
});
// Export single-spa lifecycles for your microfrontend.
export const { bootstrap, mount, unmount } = lifecycles;