fix: resolve insertBefore DOM reconciliation error in React 19
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>
This commit is contained in:
parent
0012bc87b3
commit
949888ec63
3 changed files with 26 additions and 21 deletions
|
|
@ -62,17 +62,6 @@ export const App = () => {
|
|||
onSavePreferences={saveConsent}
|
||||
/>
|
||||
)}
|
||||
<style>
|
||||
{`
|
||||
@keyframes slide {
|
||||
0% { transform: translateX(-100vw); }
|
||||
100% { transform: translateX(100vw); }
|
||||
}
|
||||
.animate-slide {
|
||||
animation: slide 24s linear infinite;
|
||||
}
|
||||
`}
|
||||
</style>
|
||||
</div>
|
||||
</Router>
|
||||
</SessionProvider>
|
||||
|
|
|
|||
|
|
@ -1230,6 +1230,15 @@
|
|||
animation: scale-bounce 3s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes slide {
|
||||
0% { transform: translateX(-100vw); }
|
||||
100% { transform: translateX(100vw); }
|
||||
}
|
||||
|
||||
.animate-slide {
|
||||
animation: slide 24s linear infinite;
|
||||
}
|
||||
|
||||
/* Animated Border Light */
|
||||
@keyframes border-light {
|
||||
0% {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useSession } from "@xtablo/shared/contexts/SessionContext";
|
||||
import { Tables } from "@xtablo/shared/types/database.types";
|
||||
import React, { useEffect } from "react";
|
||||
import React, { useEffect, useRef } from "react";
|
||||
import { createStore, StoreApi, useStore } from "zustand";
|
||||
import { LoadingSpinner } from "../components/LoadingSpinner";
|
||||
import { api } from "../lib/api";
|
||||
|
|
@ -101,19 +101,26 @@ export const UserStoreProvider = ({ children }: { children: React.ReactNode }) =
|
|||
}
|
||||
}, [user]);
|
||||
|
||||
if (isPending && shouldFetchUser) {
|
||||
return <LoadingSpinner />;
|
||||
}
|
||||
// Use a stable store ref to avoid creating a new store on every render.
|
||||
// Always render the Context.Provider to keep a consistent fiber tree structure —
|
||||
// switching between `return children` and `return <Provider>{children}</Provider>`
|
||||
// causes a full fiber tree restructure in React 19 concurrent mode, which triggers
|
||||
// "insertBefore" DOM reconciliation errors.
|
||||
const storeRef = useRef<StoreApi<User> | null>(null);
|
||||
|
||||
if (!user) {
|
||||
return children;
|
||||
if (user) {
|
||||
if (!storeRef.current) {
|
||||
storeRef.current = createStore<User>()(() => user);
|
||||
} else {
|
||||
storeRef.current.setState(() => user, true);
|
||||
}
|
||||
} else {
|
||||
storeRef.current = null;
|
||||
}
|
||||
|
||||
const store = createStore<User>()(() => user);
|
||||
|
||||
return (
|
||||
<UserStoreContext.Provider value={store as StoreApi<User>}>
|
||||
{children}
|
||||
<UserStoreContext.Provider value={storeRef.current}>
|
||||
{isPending && shouldFetchUser ? <LoadingSpinner /> : children}
|
||||
</UserStoreContext.Provider>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue