UserStoreProvider was switching between three different return structures (LoadingSpinner / children directly / Context.Provider wrapping children), causing full fiber tree restructures in React 19 concurrent mode. When batched with other state updates (e.g. UpgradeBlockContext loading), React called insertBefore with a reference node already detached from the DOM. - Always render UserStoreContext.Provider so tree structure is stable; spinner vs children toggle happens inside it - Use useRef to hold a stable Zustand store (update via setState instead of recreating on every render) - Move inline <style> from App.tsx JSX into main.css to avoid React 19 style-tag edge cases with conditional sibling rendering Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
70 lines
2.3 KiB
TypeScript
70 lines
2.3 KiB
TypeScript
import { SessionProvider } from "@xtablo/shared/contexts/SessionContext";
|
|
import { ThemeProvider } from "@xtablo/shared/contexts/ThemeContext";
|
|
import { Toaster } from "@xtablo/ui/components/sonner";
|
|
import { BrowserRouter as Router, useRoutes } from "react-router-dom";
|
|
import { CookieBanner } from "./components/CookieBanner";
|
|
import { PendingSignupCheckout } from "./components/PendingSignupCheckout";
|
|
import { PlanAnnouncement } from "./components/PlanAnnouncement";
|
|
import { TrialUpsellModal } from "./components/TrialUpsellModal";
|
|
import { UpgradePanel } from "./components/UpgradePanel";
|
|
import { UpgradeBlockProvider } from "./contexts/UpgradeBlockContext";
|
|
import { useCookieConsent } from "./hooks/useCookieConsent";
|
|
import { useDatadogRumViewName } from "./hooks/useDatadogRumViewName";
|
|
import { publicRoutes } from "./lib/publicRoutes";
|
|
import { routes } from "./lib/routes";
|
|
import { supabase } from "./lib/supabase";
|
|
import { NotFoundPage } from "./pages/NotFoundPage";
|
|
import { UserStoreProvider } from "./providers/UserStoreProvider";
|
|
|
|
const Routes = () => {
|
|
useDatadogRumViewName();
|
|
const publicElement = useRoutes(publicRoutes);
|
|
const appElement = useRoutes(routes);
|
|
|
|
// If public route matched, render it (outside UserStoreProvider)
|
|
if (publicElement) {
|
|
return publicElement;
|
|
}
|
|
|
|
// If app route matched, render it inside UserStoreProvider and UpgradeBlockProvider
|
|
if (appElement) {
|
|
return (
|
|
<UserStoreProvider>
|
|
<UpgradeBlockProvider>
|
|
<PendingSignupCheckout />
|
|
<PlanAnnouncement />
|
|
<UpgradePanel />
|
|
<TrialUpsellModal />
|
|
{appElement}
|
|
</UpgradeBlockProvider>
|
|
</UserStoreProvider>
|
|
);
|
|
}
|
|
|
|
// Neither matched, show 404
|
|
return <NotFoundPage />;
|
|
};
|
|
|
|
export const App = () => {
|
|
const { showBanner, acceptAll, rejectAll, saveConsent } = useCookieConsent();
|
|
|
|
return (
|
|
<ThemeProvider>
|
|
<SessionProvider supabase={supabase}>
|
|
<Toaster />
|
|
<Router>
|
|
<div className="min-h-screen bg-background">
|
|
<Routes />
|
|
{showBanner && (
|
|
<CookieBanner
|
|
onAcceptAll={acceptAll}
|
|
onRejectAll={rejectAll}
|
|
onSavePreferences={saveConsent}
|
|
/>
|
|
)}
|
|
</div>
|
|
</Router>
|
|
</SessionProvider>
|
|
</ThemeProvider>
|
|
);
|
|
};
|