When UI depends on async readiness/reconnects, design components so state is preserved across transitions while still preventing focus/pointer-event traps and avoiding unintended re-initialization.
Apply these rules: 1) Mount once; hide/show on recoverable states.
2) Gate “bootstrap” effects separately from “refresh” effects.
3) Separate user intent from programmatic updates.
Example pattern:
function Home() {
const startup = useHubStartup();
const hubReady = startup.transport === 'connected' && startup.hub.state === 'ready';
const [mounted, setMounted] = useState(false);
useEffect(() => {
if (hubReady) setMounted(true);
}, [hubReady]);
// Preserve App state, but disable interaction/focus while startup screen is active.
return (
<>
{mounted ? (
<Activity mode={hubReady ? 'visible' : 'hidden'}>
<App />
</Activity>
) : null}
{!mounted || !hubReady ? <HubStartupScreen {...startup} /> : null}
</>
);
}
function useRemoteEnvironmentBootstrap(activeEnvId, firstSuccessAction) {
const didBootstrapRef = useRef(false);
useEffect(() => {
if (!activeEnvId) return;
if (!didBootstrapRef.current) {
didBootstrapRef.current = true;
firstSuccessAction(); // navigation/selection only once
}
// refresh other data on every successful load/reconnect
}, [activeEnvId, firstSuccessAction]);
}
Use this standard to prevent: lost UI context on retries, focus traps behind placeholder screens, and accidental re-navigation/selection changes when hidden components re-show after reconnects.