docs/ecosystem/react.md
The official React bindings for qiankun provide a declarative way to integrate micro applications into your React main application. The @qiankunjs/react package offers a powerful <MicroApp /> component with built-in loading states, error handling, and TypeScript support.
npm install @qiankunjs/react
Requirements:
import React from 'react';
import { MicroApp } from '@qiankunjs/react';
function App() {
return (
<div className="main-app">
<h1>Main Application</h1>
<MicroApp
name="dashboard"
entry="//localhost:8080"
/>
</div>
);
}
export default App;
import React from 'react';
import { MicroApp } from '@qiankunjs/react';
function App() {
return (
<MicroApp
name="dashboard"
entry="//localhost:8080"
autoSetLoading // Enable automatic loading state
/>
);
}
import React from 'react';
import { MicroApp } from '@qiankunjs/react';
function App() {
return (
<MicroApp
name="dashboard"
entry="//localhost:8080"
autoSetLoading
autoCaptureError // Enable automatic error handling
/>
);
}
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
name | string | ✅ | - | Unique name for the micro application |
entry | string | ✅ | - | Entry URL of the micro application |
autoSetLoading | boolean | ❌ | false | Automatically manage loading state |
autoCaptureError | boolean | ❌ | false | Automatically handle errors |
loader | (loading: boolean) => React.ReactNode | ❌ | undefined | Custom loading component |
errorBoundary | (error: any) => React.ReactNode | ❌ | undefined | Custom error component |
className | string | ❌ | undefined | CSS class for the micro app container |
wrapperClassName | string | ❌ | undefined | CSS class for the wrapper (when using loader/errorBoundary) |
settings | AppConfiguration | ❌ | {} | qiankun configuration options |
lifeCycles | LifeCycles | ❌ | undefined | Lifecycle hooks |
Any additional props passed to <MicroApp /> will be forwarded to the micro application as props:
<MicroApp
name="user-profile"
entry="//localhost:8080"
// These props are passed to the micro app
userId={user.id}
theme="dark"
permissions={user.permissions}
/>
import React, { useRef, useEffect } from 'react';
import { MicroApp } from '@qiankunjs/react';
function App() {
const microAppRef = useRef<any>();
useEffect(() => {
// Get micro app status
console.log(microAppRef.current?.getStatus());
}, []);
const handleUnmount = () => {
microAppRef.current?.unmount();
};
return (
<div>
<button onClick={handleUnmount}>Unmount Micro App</button>
<MicroApp
ref={microAppRef}
name="dashboard"
entry="//localhost:8080"
/>
</div>
);
}
The micro app instance provides these status values:
NOT_LOADED - Initial state, not loaded yetLOADING_SOURCE_CODE - Loading application resourcesNOT_BOOTSTRAPPED - Resources loaded, not bootstrappedBOOTSTRAPPING - Running bootstrap lifecycleNOT_MOUNTED - Bootstrapped but not mountedMOUNTING - Running mount lifecycleMOUNTED - Successfully mounted and runningUPDATING - Running update lifecycleUNMOUNTING - Running unmount lifecycleUNLOADING - Cleaning up resourcesimport React from 'react';
import { MicroApp } from '@qiankunjs/react';
import { Spin, Alert } from 'antd';
const CustomLoader: React.FC<{ loading: boolean }> = ({ loading }) => {
if (!loading) return null;
return (
<div style={{ textAlign: 'center', padding: '50px' }}>
<Spin size="large" />
<p style={{ marginTop: '16px' }}>Loading micro application...</p>
</div>
);
};
function App() {
return (
<MicroApp
name="dashboard"
entry="//localhost:8080"
loader={(loading) => <CustomLoader loading={loading} />}
/>
);
}
import React from 'react';
import { MicroApp } from '@qiankunjs/react';
import { Alert, Button } from 'antd';
const CustomErrorBoundary: React.FC<{ error: Error }> = ({ error }) => {
const handleRetry = () => {
window.location.reload();
};
return (
<div style={{ padding: '20px' }}>
<Alert
message="Micro Application Error"
description={error.message}
type="error"
action={
<Button size="small" danger onClick={handleRetry}>
Retry
</Button>
}
/>
</div>
);
};
function App() {
return (
<MicroApp
name="dashboard"
entry="//localhost:8080"
errorBoundary={(error) => <CustomErrorBoundary error={error} />}
/>
);
}
import React from 'react';
import { MicroApp } from '@qiankunjs/react';
import './MicroApp.css';
function App() {
return (
<MicroApp
name="dashboard"
entry="//localhost:8080"
className="micro-app-container"
wrapperClassName="micro-app-wrapper"
autoSetLoading
/>
);
}
/* MicroApp.css */
.micro-app-wrapper {
border: 1px solid #e8e8e8;
border-radius: 6px;
overflow: hidden;
}
.micro-app-container {
min-height: 400px;
background: #fafafa;
}
import React, { useState } from 'react';
import { MicroApp } from '@qiankunjs/react';
import { Tabs } from 'antd';
const { TabPane } = Tabs;
function Dashboard() {
const [activeTab, setActiveTab] = useState('dashboard');
return (
<div className="multi-app-container">
<Tabs activeKey={activeTab} onChange={setActiveTab}>
<TabPane tab="Dashboard" key="dashboard">
<MicroApp
name="dashboard"
entry="//localhost:8080"
autoSetLoading
/>
</TabPane>
<TabPane tab="Analytics" key="analytics">
<MicroApp
name="analytics"
entry="//localhost:8081"
autoSetLoading
/>
</TabPane>
<TabPane tab="Settings" key="settings">
<MicroApp
name="settings"
entry="//localhost:8082"
autoSetLoading
/>
</TabPane>
</Tabs>
</div>
);
}
import React, { useState } from 'react';
import { MicroApp } from '@qiankunjs/react';
function ConditionalApp() {
const [showMicroApp, setShowMicroApp] = useState(false);
const [user, setUser] = useState(null);
// Only load micro app when user is authenticated
if (!user) {
return <div>Please log in to continue</div>;
}
return (
<div>
<button onClick={() => setShowMicroApp(!showMicroApp)}>
{showMicroApp ? 'Hide' : 'Show'} Micro App
</button>
{showMicroApp && (
<MicroApp
name="protected-app"
entry="//localhost:8080"
userId={user.id}
permissions={user.permissions}
autoSetLoading
autoCaptureError
/>
)}
</div>
);
}
import React, { useState } from 'react';
import { MicroApp } from '@qiankunjs/react';
function DynamicApp() {
const [environment, setEnvironment] = useState('development');
const entryUrls = {
development: '//localhost:8080',
staging: '//staging.example.com',
production: '//app.example.com'
};
return (
<div>
<select value={environment} onChange={(e) => setEnvironment(e.target.value)}>
<option value="development">Development</option>
<option value="staging">Staging</option>
<option value="production">Production</option>
</select>
<MicroApp
name="dynamic-app"
entry={entryUrls[environment]}
environment={environment}
autoSetLoading
/>
</div>
);
}
import React, { createContext, useContext, useState } from 'react';
import { MicroApp } from '@qiankunjs/react';
// Create a context for shared state
const AppContext = createContext();
function MainApp() {
const [sharedState, setSharedState] = useState({
user: { id: 1, name: 'John' },
theme: 'dark'
});
return (
<AppContext.Provider value={{ sharedState, setSharedState }}>
<div className="main-app">
<Navigation />
<MicroAppContainer />
</div>
</AppContext.Provider>
);
}
function MicroAppContainer() {
const { sharedState } = useContext(AppContext);
return (
<MicroApp
name="micro-app"
entry="//localhost:8080"
// Pass context data as props
user={sharedState.user}
theme={sharedState.theme}
autoSetLoading
/>
);
}
import React, { useEffect, useRef } from 'react';
import { MicroApp } from '@qiankunjs/react';
function CommunicatingApps() {
const microApp1Ref = useRef();
const microApp2Ref = useRef();
useEffect(() => {
// Set up communication channel
window.appCommunication = {
sendMessage: (from, to, message) => {
const event = new CustomEvent('microAppMessage', {
detail: { from, to, message }
});
window.dispatchEvent(event);
}
};
// Listen for messages
const handleMessage = (event) => {
console.log('Message received:', event.detail);
};
window.addEventListener('microAppMessage', handleMessage);
return () => {
window.removeEventListener('microAppMessage', handleMessage);
delete window.appCommunication;
};
}, []);
return (
<div style={{ display: 'flex' }}>
<div style={{ flex: 1 }}>
<MicroApp
ref={microApp1Ref}
name="app1"
entry="//localhost:8080"
autoSetLoading
/>
</div>
<div style={{ flex: 1 }}>
<MicroApp
ref={microApp2Ref}
name="app2"
entry="//localhost:8081"
autoSetLoading
/>
</div>
</div>
);
}
import React from 'react';
import { MicroApp } from '@qiankunjs/react';
interface UserProfileProps {
userId: string;
theme: 'light' | 'dark';
permissions: string[];
}
// Type the additional props
const UserProfileApp: React.FC = () => {
const user = getCurrentUser();
return (
<MicroApp
name="user-profile"
entry="//localhost:8080"
// TypeScript will validate these props
userId={user.id}
theme="dark"
permissions={user.permissions}
autoSetLoading
/>
);
};
import { useRef, useEffect, useState } from 'react';
import type { MicroApp as MicroAppType } from 'qiankun';
interface UseMicroAppOptions {
onStatusChange?: (status: string) => void;
onError?: (error: Error) => void;
}
export function useMicroApp(options: UseMicroAppOptions = {}) {
const microAppRef = useRef<MicroAppType>();
const [status, setStatus] = useState<string>('NOT_LOADED');
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
const checkStatus = () => {
if (microAppRef.current) {
const currentStatus = microAppRef.current.getStatus();
if (currentStatus !== status) {
setStatus(currentStatus);
options.onStatusChange?.(currentStatus);
}
}
};
const interval = setInterval(checkStatus, 1000);
return () => clearInterval(interval);
}, [status, options]);
const handleError = (err: Error) => {
setError(err);
options.onError?.(err);
};
return {
microAppRef,
status,
error,
handleError
};
}
// Usage
function App() {
const { microAppRef, status, error } = useMicroApp({
onStatusChange: (status) => console.log('Status changed:', status),
onError: (error) => console.error('App error:', error)
});
return (
<div>
<p>Status: {status}</p>
{error && <p>Error: {error.message}</p>}
<MicroApp
ref={microAppRef}
name="dashboard"
entry="//localhost:8080"
/>
</div>
);
}
import React, { Suspense, lazy } from 'react';
// Lazy load the MicroApp component
const LazyMicroApp = lazy(() =>
import('@qiankunjs/react').then(module => ({ default: module.MicroApp }))
);
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<LazyMicroApp
name="dashboard"
entry="//localhost:8080"
autoSetLoading
/>
</Suspense>
);
}
import React, { memo, useMemo } from 'react';
import { MicroApp } from '@qiankunjs/react';
const MemoizedMicroApp = memo(MicroApp);
function OptimizedApp({ user, settings }) {
const microAppProps = useMemo(() => ({
userId: user.id,
theme: settings.theme,
language: settings.language
}), [user.id, settings.theme, settings.language]);
return (
<MemoizedMicroApp
name="optimized-app"
entry="//localhost:8080"
{...microAppProps}
autoSetLoading
/>
);
}
import React from 'react';
import { MicroApp } from '@qiankunjs/react';
function DevMicroApp() {
const isDevelopment = process.env.NODE_ENV === 'development';
const handleError = (error: Error) => {
console.error('Micro app error:', error);
if (isDevelopment) {
// Show detailed error in development
return (
<div style={{ padding: '20px', background: '#ffe6e6' }}>
<h3>Development Error</h3>
<pre>{error.stack}</pre>
<button onClick={() => window.location.reload()}>
Reload App
</button>
</div>
);
}
// Show user-friendly error in production
return (
<div style={{ padding: '20px', textAlign: 'center' }}>
<p>Something went wrong. Please try again later.</p>
</div>
);
};
return (
<MicroApp
name="dashboard"
entry="//localhost:8080"
errorBoundary={handleError}
autoSetLoading
/>
);
}
// ✅ Good: Descriptive names
<MicroApp name="user-dashboard" entry="//localhost:8080" />
<MicroApp name="order-management" entry="//localhost:8081" />
// ❌ Bad: Generic names
<MicroApp name="app1" entry="//localhost:8080" />
<MicroApp name="app2" entry="//localhost:8081" />
// ✅ Good: Handle loading states
<MicroApp
name="dashboard"
entry="//localhost:8080"
autoSetLoading
loader={(loading) => <CustomSpinner loading={loading} />}
/>
// ❌ Bad: No loading indication
<MicroApp name="dashboard" entry="//localhost:8080" />
// ✅ Good: Handle errors gracefully
<MicroApp
name="dashboard"
entry="//localhost:8080"
autoCaptureError
errorBoundary={(error) => <ErrorFallback error={error} />}
/>
// ✅ Good: Environment-aware
const config = {
development: { entry: '//localhost:8080', debug: true },
production: { entry: '//app.example.com', debug: false }
};
<MicroApp
name="dashboard"
entry={config[process.env.NODE_ENV].entry}
debug={config[process.env.NODE_ENV].debug}
/>