feat(query): sync QueryClient cache across browser tabs

This commit is contained in:
Arthur Bassi 2026-07-17 10:48:35 -03:00
parent bf61508fe4
commit e023ea3101
4 changed files with 70 additions and 0 deletions

View file

@ -52,6 +52,7 @@
"@hookform/resolvers": "^5.4.0",
"@mui/icons-material": "^9.2.0",
"@mui/material": "^9.2.0",
"@tanstack/query-broadcast-client-experimental": "^5.101.2",
"@tanstack/react-query": "^5.101.2",
"@tanstack/react-query-devtools": "^5.101.2",
"@testing-library/dom": "^10.4.1",

View file

@ -1,5 +1,6 @@
import { MutationCache, QueryCache, QueryClient } from "@tanstack/react-query";
import { toast } from "react-toastify";
import { setupQueryBroadcast } from "@/lib/query/setup-query-broadcast";
export const queryClient = new QueryClient({
queryCache: new QueryCache({
@ -30,4 +31,6 @@ export const queryClient = new QueryClient({
},
});
setupQueryBroadcast(queryClient);
export default queryClient;

View file

@ -0,0 +1,19 @@
import { broadcastQueryClient } from "@tanstack/query-broadcast-client-experimental";
import type { QueryClient } from "@tanstack/react-query";
/** Same-origin channel name for cross-tab QueryClient sync. */
export const QUERY_BROADCAST_CHANNEL = "seahaven-admin-query";
/**
* Syncs QueryClient cache (invalidations + data) across browser tabs via BroadcastChannel.
* No-op outside the browser and during Vitest runs.
*/
export function setupQueryBroadcast(queryClient: QueryClient): void {
if (typeof window === "undefined") return;
if (import.meta.env.MODE === "test") return;
broadcastQueryClient({
queryClient,
broadcastChannel: QUERY_BROADCAST_CHANNEL,
});
}

View file

@ -0,0 +1,47 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { QueryClient } from "@tanstack/react-query";
const broadcastQueryClient = vi.fn();
vi.mock("@tanstack/query-broadcast-client-experimental", () => ({
broadcastQueryClient: (...args: unknown[]) => broadcastQueryClient(...args),
}));
describe("setupQueryBroadcast", () => {
beforeEach(() => {
broadcastQueryClient.mockClear();
vi.unstubAllEnvs();
});
afterEach(() => {
vi.unstubAllEnvs();
});
it("does not call broadcastQueryClient when MODE is test", async () => {
vi.stubEnv("MODE", "test");
const { setupQueryBroadcast } = await import("@/lib/query/setup-query-broadcast");
const client = new QueryClient();
setupQueryBroadcast(client);
expect(broadcastQueryClient).not.toHaveBeenCalled();
});
it("calls broadcastQueryClient with the app channel when MODE is not test", async () => {
vi.stubEnv("MODE", "development");
vi.resetModules();
const { setupQueryBroadcast, QUERY_BROADCAST_CHANNEL } =
await import("@/lib/query/setup-query-broadcast");
const client = new QueryClient();
setupQueryBroadcast(client);
expect(broadcastQueryClient).toHaveBeenCalledTimes(1);
expect(broadcastQueryClient).toHaveBeenCalledWith({
queryClient: client,
broadcastChannel: QUERY_BROADCAST_CHANNEL,
});
expect(QUERY_BROADCAST_CHANNEL).toBe("seahaven-admin-query");
});
});