Named nodes
The name prop puts a Bevy Name on the entity a React element creates, so
Rust systems can find React-created UI and work with it directly: read its
layout or hover state, add their own components, or spawn 3D things that
follow it. Nothing but the name crosses from React; there are no messages and
no entity ids to pass around.
Usage
TSX
<node name="minimap" style={{ width: 200, height: 200 }} />Rust
use bevy::prelude::*;
use bevy::ui::{ComputedNode, UiGlobalTransform};
use bevy_react::{ReactApplySet, ReactNodes};
fn track_minimap(
nodes: ReactNodes,
layout: Query<(&ComputedNode, &UiGlobalTransform)>,
) {
let Some(minimap) = nodes.get("minimap") else { return };
let Ok((node, transform)) = layout.get(minimap) else { return };
// node.size() and transform.translation are in physical pixels
}
app.add_systems(Update, track_minimap.after(ReactApplySet));The name prop
Every element accepts name, a string.
- It lands on the entity as a Bevy
Nameand in theReactNodesindex. - It is dynamic: changing it renames the entity;
""or removing the prop removes theName. - Names are not unique. A list can give every item the same name, and Bevy reads them as a group.
Finding nodes
ReactNodes
ReactNodes is a read-only system param that looks entities up by name:
| Method | Returns |
|---|---|
get(name) |
Option<Entity>: the first mounted entity with the name |
all(name) |
&[Entity]: every entity with the name, in mount order |
contains(name) |
bool: whether any mounted entity has the name |
iter() |
every (name, &[Entity]) group, in no particular order |
When several entities share a name, get returns the first mounted one and
reports a nameAmbiguous warning (in the Bevy log and in
devtools). Use all for groups. This system reads
each card's hover state; a node has an Interaction when it declares a
pointer handler, onClick, hoverStyle or pressStyle:
fn glow_hovered_cards(
nodes: ReactNodes,
cards: Query<&Interaction>,
) {
for &card in nodes.all("card") {
if let Ok(Interaction::Hovered) = cards.get(card) {
// …
}
}
}Queries
Every entity the React renderer creates (nodes, text, SVG shapes, portals,
surfaces, roots) carries the ReactNode marker component. Filter with it, so
the query skips the other named entities in your app, such as glTF nodes:
fn on_mount(added: Query<(Entity, &Name), Added<ReactNode>>) {
for (entity, name) in &added {
info!("mounted {name} as {entity}");
}
}Added<ReactNode> and RemovedComponents<ReactNode> are the mount and
unmount signals. ReactNode holds the node's renderer id.
Ordering
React's changes are applied to the world in ReactApplySet, in Update;
that is where entities spawn and despawn and where Name and the
ReactNodes index change. Order your systems .after(ReactApplySet) to see
the current frame's mounts, or they run a frame behind.
Layout is computed later, in PostUpdate. For the current frame's
ComputedNode and UiGlobalTransform, run in PostUpdate after
UiSystems::Layout; to move 3D entities with the node in the same frame,
also run before TransformSystems::Propagate, as the
demo scene does.
An Entity is only valid while its React node is mounted. React despawns it
on unmount without telling anyone who kept the handle, and a full hot reload
rebuilds the whole tree. Look nodes up again each frame, or watch
RemovedComponents<ReactNode>.
What you may change
The renderer re-applies the components it owns whenever React updates the node, so your writes to them are overwritten, sometimes only much later (only what a change touches is re-applied). Read them freely; don't write them:
Node,BackgroundColor,BorderColor,Outline,BoxShadow,ZIndex,GlobalZIndex,LayoutConfig,Visibility,UiTransform,ImageNodeText,TextSpan,TextFont,TextColor,TextLayoutScrollPosition,Interaction,FocusPolicy,PickableName,Children,ChildOf
Every other component is yours: your own components and markers,
MaterialNode<M>, TabIndex, audio. In particular, don't insert a Name on
a React node yourself: the index doesn't see it, and the next name change
overwrites it.
Children you spawn
Entities you parent under a React node are despawned with it. But whenever
React changes that node's children (a child mounts, unmounts or moves), the
node's child list is rebuilt from the React tree and your children lose their
parent; an orphaned UI Node then renders as a top-level UI root. Only parent
your entities under nodes whose React children never change, or keep them
separate and follow the node's layout instead.
Limits
- The index is read-only, and a name can't select a single node out of a group.
- Entity handles go stale when React unmounts the node.
- The bridge-owned components above can't be driven from Rust; drive those from React (or with animated values).
See Identity in the element reference.
bevy-react