pub trait ReactAppExt {
// Required methods
fn add_react_message<T>(&mut self) -> &mut Self
where T: ReactPayload,
for<'a> <T as Event>::Trigger<'a>: Default;
fn add_react_handler<E, B, M, S>(&mut self, observer: S) -> &mut Self
where E: ReactPayload,
for<'a> <E as Event>::Trigger<'a>: Default,
B: Bundle,
S: IntoObserverSystem<E, B, M>;
fn add_react_request<T>(&mut self) -> &mut Self
where T: ReactRequest;
fn add_react_request_handler<E, B, M, S>(
&mut self,
observer: S,
) -> &mut Self
where E: Event + RequestEvent,
for<'a> <E as Event>::Trigger<'a>: Default,
B: Bundle,
S: IntoObserverSystem<E, B, M>;
fn add_react_event<E>(&mut self) -> &mut Self
where E: ReactEvent;
fn add_react_filter<T>(&mut self) -> &mut Self
where T: ReactFilter + DeserializeOwned + TS;
fn export_react_typescript(&self, path: impl AsRef<Path>) -> Result<()>;
}Expand description
Registers typed React message payloads on a Bevy App.
Required Methods§
Sourcefn add_react_message<T>(&mut self) -> &mut Self
fn add_react_message<T>(&mut self) -> &mut Self
Register a typed React message payload without attaching an observer.
After this, an emit(T::NAME, value) from the React app deserializes
value into T and triggers it. Prefer add_react_handler
unless you want to register the type and observe it separately.
Sourcefn add_react_handler<E, B, M, S>(&mut self, observer: S) -> &mut Selfwhere
E: ReactPayload,
for<'a> <E as Event>::Trigger<'a>: Default,
B: Bundle,
S: IntoObserverSystem<E, B, M>,
fn add_react_handler<E, B, M, S>(&mut self, observer: S) -> &mut Selfwhere
E: ReactPayload,
for<'a> <E as Event>::Trigger<'a>: Default,
B: Bundle,
S: IntoObserverSystem<E, B, M>,
Register a payload and attach an observer for it in one call.
The payload type is inferred from the observer’s On<T> parameter, so you
never name it twice. Call it again with another observer to add more
handlers for the same message — registration is idempotent.
app.add_react_handler(|count: On<Count>, mut desired: ResMut<DesiredCubes>| {
desired.0 = count.event().0;
});Sourcefn add_react_request<T>(&mut self) -> &mut Selfwhere
T: ReactRequest,
fn add_react_request<T>(&mut self) -> &mut Selfwhere
T: ReactRequest,
Register a typed React request without attaching an observer. Prefer
add_react_request_handler.
Sourcefn add_react_request_handler<E, B, M, S>(&mut self, observer: S) -> &mut Selfwhere
E: Event + RequestEvent,
for<'a> <E as Event>::Trigger<'a>: Default,
B: Bundle,
S: IntoObserverSystem<E, B, M>,
fn add_react_request_handler<E, B, M, S>(&mut self, observer: S) -> &mut Selfwhere
E: Event + RequestEvent,
for<'a> <E as Event>::Trigger<'a>: Default,
B: Bundle,
S: IntoObserverSystem<E, B, M>,
Register a request and attach its observer in one call.
The request type is inferred from the observer’s On<Request<T>> parameter.
The observer answers the request via Request::respond.
app.add_react_request_handler(|req: On<Request<BoardGet>>, board: Res<Board>| {
req.respond(board.clone());
});Sourcefn add_react_event<E>(&mut self) -> &mut Selfwhere
E: ReactEvent,
fn add_react_event<E>(&mut self) -> &mut Selfwhere
E: ReactEvent,
Register a Bevy → React event type so it appears in the generated
ReactEvents map and bevy.on typing. Sending an event with
ReactEvents does not require this, but then the type
won’t be known to the exporter.
Sourcefn add_react_filter<T>(&mut self) -> &mut Self
fn add_react_filter<T>(&mut self) -> &mut Self
Register a custom filter type (usually a
#[react_filter] struct) so the filter style
chain can resolve it by T::NAME — and so the exporter can mirror its
params type into the generated TypeScript.
The built-in filters (blur, brightness, …) are registered
automatically by ReactUiPlugin; call this
only for your own filters. Like events, keep the call in your single
register_bindings site so the exporter path sees the same filters
the running app does — a filter registered only at runtime never
appears in the generated typing (see
export_react_typescript).
To shadow a built-in name, register after ReactUiPlugin is
added: the plugin’s build registers the built-ins and would replace
an earlier custom (with a warn), while the exporter — which never
runs the plugin — would still show yours, silently diverging the
generated types from runtime.
Sourcefn export_react_typescript(&self, path: impl AsRef<Path>) -> Result<()>
fn export_react_typescript(&self, path: impl AsRef<Path>) -> Result<()>
Write a self-contained TypeScript module (conventionally src/bevy.ts)
mirroring every registered React binding to path.
The generated module covers all four typed surfaces in one pass: a type
declaration per payload (mirrored from the #[react_message] /
#[react_request] / #[react_event] structs and the filter params types
via ts-rs), the ReactMessages/ReactRequests/ReactEvents name→type
maps, a declare module "bevy-react" block augmenting the BevyFilters
interface with every registered filter (built-ins included, so the
filter style field types each name’s params), typed emit/request/
on wrappers, and a structured bevy proxy whose nested methods come
from dotted request names ("board.get" → bevy.board.get()).
App code imports that typed surface from ./bevy instead of the untyped
functions from "bevy-react", so every call is checked against the same
structs Bevy serializes and deserializes.
Keep a single registration site: put your add_react_* calls in a
register_bindings(app) function that both the real app (e.g. your
plugin’s build) and a small exporter entry point call, so a binding can
never exist at runtime without appearing in the generated types. Wire the
exporter to a CLI flag that returns before app.run(), commit the output,
and have CI regenerate + git diff --exit-code to guarantee the
TypeScript never drifts from Rust. (See examples/demos/main.rs and its
--export-bindings flag, exposed as npm run bevy:generate.)
if std::env::args().nth(1).as_deref() == Some("--export-bindings") {
let path = std::env::args().nth(2).expect("output path");
let mut app = App::new();
register_bindings(&mut app); // the same fn the real app calls
app.export_react_typescript(&path)?;
return;
}Dyn Compatibility§
This trait is not dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".