Libception. Learn how to use React inside of your Aurelia applications.
Aurelia's design embraces flexibility and interoperability, making it well-suited for integration with other libraries and frameworks. One common scenario is incorporating React components into an Aurelia application. This integration showcases Aurelia's adaptability and how it can leverage the strengths of other ecosystems. Below, we provide a detailed guide and code examples to integrate a React component seamlessly into an Aurelia 2 application.
Install Dependencies
First, ensure that your Aurelia project has the necessary dependencies to use React. You'll need React and ReactDOM, plus the matching type packages when you compile with TypeScript.
Keep the @types versions in sync with the React major you're using (for example, @types/react@19 with react@19). If you're validating a future React release candidate, follow the React upgrade guide instructions to alias the preview types-react packages so your compiler picks up the experimental definitions.
Create a React Component
For this example, let's create a simple React component. You can replace this with any React component you need.
// src/components/my-react-component.tsximporttype{FC}from'react';exportinterfaceMyReactComponentProps{message?:string;}exportconstMyReactComponent:FC<MyReactComponentProps>=({message='Hello from React!'})=> (<div>{message}</div>);exportdefaultMyReactComponent;
Create an Aurelia Wrapper Component
To integrate the React component into Aurelia, create a wrapper Aurelia component that will render the React component.
This wrapper exposes the React component and its props as bindables. It uses React's createRoot API—the modern entry point for React 18 and React 19 apps—to render the component inside Aurelia's DOM container. The wrapper handles the full React lifecycle by creating the root in attached(), re-rendering in response to Aurelia bindable updates via propertyChanged(), and calling unmount() inside detaching() so that React cleans up timers and subscriptions.
Register the Wrapper Component and Use It
Now, register the wrapper component with Aurelia and then use it in your application.
Then, reference the wrapper in a view:
Ensure you import and make the React component available in your Aurelia component:
Advanced Integration Patterns
Error Boundaries
For production applications, consider wrapping React components in error boundaries:
Then modify your wrapper to use the error boundary:
React Root Error Hooks
React 19 exposes additional root options, letting you hook into caught or uncaught errors even before they reach an error boundary. Add another bindable for rootOptions and pass it to createRoot when you need centralized logging:
You can populate rootOptions from Aurelia with callbacks such as:
Wire it up in the view: <react-wrapper root-options.bind="reactRootOptions" ...></react-wrapper>.
React 18+ and 19 Compatibility
This integration pattern embraces the React DOM client API introduced in React 18 and retained in React 19:
createRoot is the entry point to Concurrent React. Using it ensures your embedded components can opt into features like transitions without any extra glue.
React 18+ automatically batches state updates, so React components you host inside Aurelia get predictable renders even if multiple Aurelia bindings mutate the props in the same tick.
React 19 ships improved TypeScript definitions and keeps the expanded root options (onCaughtError, onUncaughtError, and onRecoverableError), letting you consolidate error handling in one place.
Performance Considerations
React components are re-rendered when props change via Aurelia's propertyChanged() lifecycle
Use React.memo() (or PureComponent) for expensive components to prevent unnecessary re-renders when Aurelia pushes the same data reference
Avoid recreating prop objects inline in your view—store them on the view-model so Aurelia only notifies React when values actually change
Consider implementing shouldComponentUpdate logic or memoization in the React component itself for fine-grained control
Following these steps, you can integrate React components into your Aurelia 2 application. This process highlights the flexibility of Aurelia, allowing you to take advantage of React's component library while enjoying the benefits of Aurelia's powerful features.
// src/my-view.ts
import MyReactComponent from './components/my-react-component';
export class MyView {
public myReactComponent = MyReactComponent;
public reactProps = { message: 'Hello from Aurelia!' };
}
// src/components/error-boundary.tsx
import { Component, type ErrorInfo, type ReactNode } from 'react';
interface ErrorBoundaryProps {
readonly children?: ReactNode;
}
interface ErrorBoundaryState {
hasError: boolean;
}
class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
public state: ErrorBoundaryState = { hasError: false };
public static getDerivedStateFromError(): ErrorBoundaryState {
return { hasError: true };
}
public componentDidCatch(error: unknown, errorInfo: ErrorInfo): void {
console.error('React component error:', error, errorInfo);
}
public render(): ReactNode {
if (this.state.hasError) {
return <div>Something went wrong with the React component.</div>;
}
return this.props.children;
}
}
export default ErrorBoundary;