Using Ember and React on the Same Page
In This Guide
WarpDrive doesn't wrap your data in any one framework's signals. Instead, it calls a set of SignalHooks whenever data is read or changes, and each framework's install entry point provides the hooks for that framework. Ember only re-renders for Ember's tags, and React only re-renders for the signals its watchers subscribe to. So when both frameworks render the same data, both need to hear about every read and every change.
@warp-drive/alien-signals does this for you. Its install entry point configures a signals graph that other frameworks add their signals to, instead of replacing it. This guide installs it alongside Ember and React, so that one store re-renders components in both frameworks.
Before You Start
This guide assumes an Ember app that also renders React components, with both @warp-drive/ember and @warp-drive/react installed as described in Installation. You don't need PolarisMode for this; see Do I need PolarisMode to share state between Ember and React on the same page?.
Add @warp-drive/alien-signals to your app, pinned to the same version as your other WarpDrive packages. @warp-drive/react already depends on it, but your app imports it directly, so it needs to be one of your app's own dependencies too.
pnpm add -E @warp-drive/alien-signalsInstall the Signals
Import @warp-drive/alien-signals/install at the top of your app's entry point and your test setup, before the Ember and React install imports:
import '@warp-drive/alien-signals/install';
import '@warp-drive/ember/install';
import '@warp-drive/react/install';import '@warp-drive/alien-signals/install';
import '@warp-drive/ember/install';
import '@warp-drive/react/install';The order matters for Ember. On its own, @warp-drive/ember/install configures Ember's autotracking as WarpDrive's only signals implementation. When @warp-drive/alien-signals/install has already run, Ember registers its tags with the alien-signals graph instead. Importing @warp-drive/alien-signals/install after @warp-drive/ember/install throws an error that says so.
@warp-drive/react/install imports @warp-drive/alien-signals/install itself, so React works in either position. Listing alien-signals first anyway keeps the rule simple: it always comes first.
Already composing signal hooks yourself?
If your app combines each framework's buildSignalConfig in its own setupSignals call, so that createSignal returns one signal per framework, you no longer need it. Delete that call, and replace the import of the module it lives in with the three imports above.
How the Frameworks Share Signals
You don't need any of this to use the setup above, but it explains why it works.
- Signals. Each time WarpDrive creates a signal, the alien-signals graph creates an Ember tag to go with it. Reading or changing the data consumes or dirties both, so Ember's autotracking and the graph each see every read and change.
- React. React's integration watches the graph directly. A signal read while a component renders inside a
ReactiveContextis added to that context's watcher. - Memos. Values WarpDrive memoizes, such as derived fields and some request and pagination state, are memos in the alien-signals graph; neither framework creates its own. A React component watches the memo itself, so it re-renders whenever anything the memo depends on changes. Ember sees each memo as one more tag: reading the memo consumes it, whether the memo runs or returns a cached value, and the graph dirties it as soon as anything the memo depends on changes. Memoized values update in both frameworks, whichever one read them first.
- Test waiters. Each framework's
waitForhook still runs, soawait settled()from@ember/test-helperswaits for WarpDrive's requests in your Ember tests.
Two behaviors differ from an Ember app that only imports @warp-drive/ember/install:
- A memo only recomputes when a signal managed by WarpDrive changes. If a derived field reads state that only a framework tracks, such as an Ember
@trackedproperty or a ReactuseStatevalue, the memo keeps returning its cached value when that state changes. Keep the state a derivation reads in WarpDrive, for instance as a field on the resource it derives from. - With the
DEPRECATE_COMPUTED_CHAINSdeprecation active, a classic computed property that depends on a memoized key may not recompute when that memo changes.
Share the Store
Both frameworks need to render from the same store instance, not two stores built from the same class. Create it the usual way for Ember, then hand that instance to React's StoreProvider with its store prop, as Setup - React describes.
<StoreProvider store={store}>
<UserList />
</StoreProvider>Other Frameworks
The same setup works for other frameworks: import @warp-drive/alien-signals/install first, then each framework's install entry point. An install entry point that calls registerSignals, as @warp-drive/ember/install and @warp-drive/tc39-proposal-signals/install do, registers with the graph when alien-signals is installed, and configures that framework on its own when it isn't.
To add a framework that doesn't have a WarpDrive package yet, pass its hooks to registerSignalIntegration. SignalIntegration describes the two shapes an integration can take: one that brings its own signals, like Ember, and one that observes the graph's signals and memos, like React.