From 949888ec630d80308fabb8aad1aaeec72f4198fd Mon Sep 17 00:00:00 2001 From: Arthur Belleville Date: Mon, 30 Mar 2026 23:07:47 +0200 Subject: [PATCH] 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 diff --git a/apps/main/src/main.css b/apps/main/src/main.css index 5584365..58ea181 100644 --- a/apps/main/src/main.css +++ b/apps/main/src/main.css @@ -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% { diff --git a/apps/main/src/providers/UserStoreProvider.tsx b/apps/main/src/providers/UserStoreProvider.tsx index 3e36fae..1e88f06 100644 --- a/apps/main/src/providers/UserStoreProvider.tsx +++ b/apps/main/src/providers/UserStoreProvider.tsx @@ -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 ; - } + // 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 {children}` + // causes a full fiber tree restructure in React 19 concurrent mode, which triggers + // "insertBefore" DOM reconciliation errors. + const storeRef = useRef | null>(null); - if (!user) { - return children; + if (user) { + if (!storeRef.current) { + storeRef.current = createStore()(() => user); + } else { + storeRef.current.setState(() => user, true); + } + } else { + storeRef.current = null; } - const store = createStore()(() => user); - return ( - }> - {children} + + {isPending && shouldFetchUser ? : children} ); };