Description
Hi, I’m reaching out to ask for help regarding an issue I’ve encountered when using Suspense with a custom React renderer that integrates with Babylon.js.
Issue
I'm used to create the Babylon.js entities during the render phase, inside createInstance
method. However, if React suspends, it may discard the entire tree before ever reaching the commit phase.
The problem is that my createInstance
implementation immediately creates and attaches the Babylon entity to the scene graph. So, even though React throws the tree away, the entity stays in the scene and can't be cleaned up leading to "zombie" entities that were never committed but still exist.
This is expected behavior in React, it may build trees speculatively and discard them. So side-effects (like entity creation or mounting into a scene graph) must be deferred to the commit phase, not the render phase.
Attempted workaround
To work around this, I’ve tried delaying Babylon.js entity creation until I’m sure React has committed the tree, by moving logic into appendInitialChild
, appendChildToContainer
, appendChild
, insertBefore
, insertInContainerBefore
.
appendInitialChild(parentInstance, child) {
parentInstance.children.push(child);
mountBabylonEntity(parentInstance, child);
}
appendChild(parentInstance, child) {
parentInstance.children.push(child);
mountBabylonEntity(parentInstance, child);
},
appendChildToContainer(container, child) {
container.children.push(child);
mountBabylonEntity(container, child);
},
insertBefore(parentInstance, child, beforeChild) {
const index = parentInstance.children.findIndex(item => item.entity!.uniqueId === beforeChild.entity!.uniqueId);
parentInstance.children.splice(index, 0, child);
mountBabylonEntity(parentInstance, child);
},
insertInContainerBefore(container, child, beforeChild) {
const index = container.children.findIndex(item => item.entity!.uniqueId === beforeChild.entity!.uniqueId);
container.children.splice(index, 0, child);
mountBabylonEntity(container, child);
}
Then i recursively mount the child entities only when React appears to be finalizing the tree.
// create recursively the Babylon.js instances
function mountBabylonEntity(parent: Instance | RootContainer, child: Instance, isReady: boolean) {
const isParentRootContainer = parent.name === ROOT_IDENTIFIER; // true when the parent is a root container
if (isParentRootContainer || isReady){
finalTree.status = true;
// generate child Babylon.js entity
// generate parent Babylon.js entity
for (const currentChild of child.children){
mountBabylonEntity(child, currentChild, true);
}
}
}
This effectively delays creation until React commits the current child to container (appendChildToContainer
is called, maybe wrong assumption). This kind of works, but I'm unsure whether it's a good approach or if I'm misunderstanding how commit works in custom renderers.
Code reference
You can see the relevant reconciler implementation here: https://github.com/simonedevit/reactylon/blob/delay-instance-creation/packages/library/src/reconciler.ts#L153.
Versions
react
: ^19
react-reconciler
: 0.31.0