mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-10-03 17:54:05 +00:00
fix(ui): read the persisted sidebar mode without a hydration mismatch (#12873)
This commit is contained in:
@@ -0,0 +1 @@
|
||||
Sidebar no longer throws a React hydration error on full page loads for users who last used the chat mode
|
||||
@@ -10,7 +10,7 @@ import { useRuntimeConfig } from "@/hooks/use-runtime-config";
|
||||
import { isCloud } from "@/lib/shared/env";
|
||||
import { useUIStore } from "@/store/ui/store";
|
||||
|
||||
import { useAppSidebarMode } from "./app-sidebar-mode-store";
|
||||
import { useHydratedAppSidebarMode } from "./app-sidebar-mode-store";
|
||||
import { AppSidebarModeToggle } from "./app-sidebar-mode-toggle";
|
||||
import { LaunchScanAction } from "./launch-scan-action";
|
||||
import { getNavigationConfig } from "./navigation-config";
|
||||
@@ -28,7 +28,7 @@ export function AppSidebarContent({ onSelect }: AppSidebarContentProps) {
|
||||
// One-time server decision per request, seeded by the root layout.
|
||||
const registryEligible = useUIStore((state) => state.registryEligible);
|
||||
const { apiDocsUrl, cloudBillingEnabled } = useRuntimeConfig();
|
||||
const mode = useAppSidebarMode((state) => state.mode);
|
||||
const mode = useHydratedAppSidebarMode();
|
||||
const isCloudEnvironment = isCloud();
|
||||
const sections = getNavigationConfig({
|
||||
pathname,
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { renderHook } from "@testing-library/react";
|
||||
import { beforeEach, describe, expect, it } from "vitest";
|
||||
|
||||
import {
|
||||
migrateAppSidebarState,
|
||||
useAppSidebarMode,
|
||||
useHydratedAppSidebarMode,
|
||||
} from "./app-sidebar-mode-store";
|
||||
import { APP_SIDEBAR_MODE } from "./types";
|
||||
|
||||
@@ -70,4 +72,21 @@ describe("app sidebar mode store", () => {
|
||||
// Then
|
||||
expect(useAppSidebarMode.getState().mode).toBe(APP_SIDEBAR_MODE.CHAT);
|
||||
});
|
||||
|
||||
it("answers browse on the first render and the persisted mode once mounted", () => {
|
||||
// Given — a tenant that last used the chat; the server rendered browse.
|
||||
useAppSidebarMode.setState({ mode: APP_SIDEBAR_MODE.CHAT });
|
||||
const renders: string[] = [];
|
||||
|
||||
// When
|
||||
renderHook(() => {
|
||||
const mode = useHydratedAppSidebarMode();
|
||||
renders.push(mode);
|
||||
return mode;
|
||||
});
|
||||
|
||||
// Then — the hydrating render matches the HTML, the next one the store.
|
||||
expect(renders[0]).toBe(APP_SIDEBAR_MODE.BROWSE);
|
||||
expect(renders[renders.length - 1]).toBe(APP_SIDEBAR_MODE.CHAT);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { create } from "zustand";
|
||||
import { createJSONStorage, persist } from "zustand/middleware";
|
||||
|
||||
import { useStore } from "@/hooks/use-store";
|
||||
|
||||
import { APP_SIDEBAR_MODE, type AppSidebarMode } from "./types";
|
||||
|
||||
interface PersistedAppSidebarState {
|
||||
@@ -52,3 +54,15 @@ export const useAppSidebarMode = create<AppSidebarModeStore>()(
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
// The persisted mode is only known in the browser: `persist` rehydrates from
|
||||
// localStorage before the first client render, while the server always
|
||||
// rendered `browse`. Reading the store directly makes a tenant that last used
|
||||
// the chat hydrate a different sidebar than the one in the HTML (React #418).
|
||||
// This read answers `browse` until mounted, then the persisted value.
|
||||
export function useHydratedAppSidebarMode(): AppSidebarMode {
|
||||
return (
|
||||
useStore(useAppSidebarMode, (state) => state.mode) ??
|
||||
APP_SIDEBAR_MODE.BROWSE
|
||||
);
|
||||
}
|
||||
|
||||
@@ -18,7 +18,10 @@ import {
|
||||
import { useCloudUpgradeStore } from "@/store";
|
||||
import { CLOUD_UPGRADE_FEATURE } from "@/types/cloud-upgrade";
|
||||
|
||||
import { useAppSidebarMode } from "./app-sidebar-mode-store";
|
||||
import {
|
||||
useAppSidebarMode,
|
||||
useHydratedAppSidebarMode,
|
||||
} from "./app-sidebar-mode-store";
|
||||
import {
|
||||
APP_SIDEBAR_MODE,
|
||||
type AppSidebarMode,
|
||||
@@ -49,7 +52,7 @@ export function AppSidebarModeToggle({
|
||||
}: AppSidebarModeToggleProps) {
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
const mode = useAppSidebarMode((state) => state.mode);
|
||||
const mode = useHydratedAppSidebarMode();
|
||||
const setMode = useAppSidebarMode((state) => state.setMode);
|
||||
const openCloudUpgrade = useCloudUpgradeStore(
|
||||
(state) => state.openCloudUpgrade,
|
||||
|
||||
Reference in New Issue
Block a user