A configurator contains at least two kinds of change. Choosing a roof style is a durable product decision. Moving a camera for the next animation frame is a transient view update.
Treating both as the same kind of state makes the application harder to reason about. Separating them gives React and the renderer clearer jobs.
Keep one authoritative configuration
The configuration should be serializable and independent of the canvas. Store dimensions, option identifiers, placements, and the catalog revision needed to interpret them.
Avoid saving live meshes, materials, or renderer references as product state. Those objects describe a particular view implementation and may not survive a reload or renderer replacement.
Use stable identifiers for selected features. An array index is a poor identity when objects can be reordered or deleted.
Build a narrow bridge
React can own forms, navigation, errors, and committed configuration changes. A scene controller can own camera motion, transient dragging, and rendering.
The bridge should carry explicit commands or state changes. For example, a drag preview moves a feature visually; committing the drag updates the product model only after validation.
That boundary also clarifies undo. The user should be able to undo a meaningful placement decision without stepping backward through hundreds of pointer movements.
Make setup and cleanup symmetrical
An effect that creates a controller should clean up the subscriptions and resources that controller owns. React’s useEffect reference describes setup, cleanup, and the additional development check that can expose missing cleanup.
Do not hide lifecycle bugs by suppressing development behavior. Repeated initialization should either be supported or prevented through a clear owner whose lifetime is longer than the component.
A singleton can be appropriate, but it moves responsibility elsewhere. It does not remove the need to define teardown.
Wait for usable geometry
A canvas mounted inside a hidden tab can initially have zero dimensions. Starting the renderer with an assumed size may produce a wrong aspect ratio, blurry output, or wasted allocations.
Observe the actual container and handle changes. Keep the loading interface usable while the scene is not ready. A component’s presence in the DOM is not evidence that a useful frame has rendered.
Avoid making the entire page depend on the canvas. Product details and saved configurations should remain readable during a graphics failure.
Coordinate asynchronous updates
Model generation, catalog loading, material decoding, and user input can finish in different orders. Give each result an identity and reject stale work at its destination.
A geometry update should know which configuration revision it represents. A material load should know which asset variant was requested. A saved quote should record the revision the server accepted.
These checks are more reliable than a global “loading” flag that cannot explain which operation is current.
Account for hidden views in React 19.2
React 19.2 introduced Activity, which can preserve component state while hiding a view and cleaning up its effects. Showing the view again reactivates those effects. A preserved form state therefore does not imply that a renderer created by an effect remains active.
Decide whether hiding the editor should release its graphics resources or retain them under a longer-lived owner. Both can be reasonable, but the choice must be explicit. Test hide, show, resize, and navigation while a material is still decoding.
The same release’s useEffectEvent helps separate non-reactive effect logic from reactive dependencies. It is not a general escape hatch for ignoring dependencies or a replacement for the scene’s update loop.
Keep frame work on the rendering clock
React Three Fiber’s performance guidance recommends direct transient updates inside the frame loop instead of scheduling React state on every frame. Use elapsed time for movement so animation speed does not depend on the display’s refresh rate.
For a drag, let the scene show a transient candidate position and commit one validated product command when the gesture ends. The command carries the starting revision, target entity, and intended placement. If validation rejects it, restore the accepted position and explain why.
Measure three separate costs: frame time during the drag, time to validate the committed change, and time to display the accepted result. A smooth camera cannot compensate for a commit that loses the user’s selection. This separation makes performance work support the actual editing flow.
Test the seams
Exercise rapid style changes, unmount during loading, undo after deletion, resize while hidden, and restoring a saved configuration with an older catalog.
Test the model without rendering, then test the bridge, then inspect the actual image and interaction. A component test that finds a canvas proves little about what the customer can see.
Choose a state library for its fit with these requirements. No particular library makes the boundary correct automatically, and none deserves a universal claim that competing approaches cannot work.
A useful architecture lets each part change at the pace it needs while preserving agreement about the product being configured.
Keep a good idea close.
Follow Signal Tower

