Fix sidebar hydration flash

This commit is contained in:
2026-03-12 16:29:28 -04:00
parent b39bc9eccd
commit b9a1d8ba40
4 changed files with 74 additions and 12 deletions

View File

@@ -0,0 +1,32 @@
'use client';
import { createContext, useContext } from 'react';
type SidebarPreferenceContextValue = {
initialSidebarCollapsed: boolean;
};
const SidebarPreferenceContext =
createContext<SidebarPreferenceContextValue>({
initialSidebarCollapsed: false
});
type SidebarPreferenceProviderProps = {
children: React.ReactNode;
initialSidebarCollapsed: boolean;
};
export function SidebarPreferenceProvider({
children,
initialSidebarCollapsed
}: SidebarPreferenceProviderProps) {
return (
<SidebarPreferenceContext.Provider value={{ initialSidebarCollapsed }}>
{children}
</SidebarPreferenceContext.Provider>
);
}
export function useSidebarPreference() {
return useContext(SidebarPreferenceContext);
}

View File

@@ -23,6 +23,7 @@ import { useEffect, useMemo, useState } from "react";
import { authClient } from "@/lib/auth-client";
import { TaskDetailModal } from "@/components/notifications/task-detail-modal";
import { TaskNotificationsTrigger } from "@/components/notifications/task-notifications-trigger";
import { useSidebarPreference } from "@/components/providers/sidebar-preference-provider";
import {
companyAnalysisQueryOptions,
companyFinancialStatementsQueryOptions,
@@ -37,6 +38,7 @@ import { buildGraphingHref } from "@/lib/graphing/catalog";
import type { ActiveContext, NavGroup, NavItem } from "@/lib/types";
import { Button } from "@/components/ui/button";
import { useTaskNotificationsCenter } from "@/hooks/use-task-notifications-center";
import { SIDEBAR_PREFERENCE_KEY } from "@/lib/sidebar-preference";
import { cn } from "@/lib/utils";
type AppShellProps = {
@@ -148,8 +150,6 @@ const GROUP_LABELS: Record<NavGroup, string> = {
portfolio: "Portfolio",
};
const SIDEBAR_PREFERENCE_KEY = "fiscal-shell-sidebar-collapsed";
function normalizeTicker(value: string | null | undefined) {
const normalized = value?.trim().toUpperCase() ?? "";
return normalized.length > 0 ? normalized : null;
@@ -260,12 +260,16 @@ export function AppShell({
const router = useRouter();
const searchParams = useSearchParams();
const queryClient = useQueryClient();
const { initialSidebarCollapsed } = useSidebarPreference();
const [isSigningOut, setIsSigningOut] = useState(false);
const [isMoreOpen, setIsMoreOpen] = useState(false);
const [isSidebarCollapsed, setIsSidebarCollapsed] = useState(false);
const [hasLoadedSidebarPreference, setHasLoadedSidebarPreference] =
const [isSidebarCollapsed, setIsSidebarCollapsed] = useState(
initialSidebarCollapsed,
);
const [hasResolvedSidebarPreference, setHasResolvedSidebarPreference] =
useState(false);
const [hasMounted, setHasMounted] = useState(false);
const notifications = useTaskNotificationsCenter();
const { data: session } = authClient.useSession();
const sessionUser = (session?.user ?? null) as {
@@ -433,14 +437,14 @@ export function AppShell({
const storedPreference = window.localStorage.getItem(
SIDEBAR_PREFERENCE_KEY,
);
if (storedPreference === "true") {
setIsSidebarCollapsed(true);
if (storedPreference === "true" || storedPreference === "false") {
setIsSidebarCollapsed(storedPreference === "true");
}
setHasLoadedSidebarPreference(true);
setHasResolvedSidebarPreference(true);
}, []);
useEffect(() => {
if (!hasLoadedSidebarPreference) {
if (!hasResolvedSidebarPreference) {
return;
}
@@ -448,7 +452,12 @@ export function AppShell({
SIDEBAR_PREFERENCE_KEY,
String(isSidebarCollapsed),
);
}, [hasLoadedSidebarPreference, isSidebarCollapsed]);
document.cookie = `${SIDEBAR_PREFERENCE_KEY}=${String(isSidebarCollapsed)}; path=/; max-age=31536000; samesite=lax`;
}, [hasResolvedSidebarPreference, isSidebarCollapsed]);
useEffect(() => {
setHasMounted(true);
}, []);
useEffect(() => {
const browserWindow = window as Window & {
@@ -520,7 +529,8 @@ export function AppShell({
>
<aside
className={cn(
"hidden shrink-0 flex-col gap-4 border-r border-[color:var(--line-weak)] transition-[width,padding] duration-200 lg:flex",
"hidden shrink-0 flex-col gap-4 border-r border-[color:var(--line-weak)] lg:flex",
hasMounted ? "transition-[width,padding] duration-200" : "",
isSidebarCollapsed ? "w-16 pr-1" : "w-72 pr-4",
)}
>