diff --git a/apps/dashboard/app/(app)/layout.tsx b/apps/dashboard/app/(app)/layout.tsx
index 91d36a224d..c90af8707f 100644
--- a/apps/dashboard/app/(app)/layout.tsx
+++ b/apps/dashboard/app/(app)/layout.tsx
@@ -6,6 +6,7 @@ import { db } from "@/lib/db";
import { Empty } from "@unkey/ui";
import Link from "next/link";
import { redirect } from "next/navigation";
+import { QueryTimeProvider } from "../../providers/query-time-provider";
interface LayoutProps {
children: React.ReactNode;
@@ -49,7 +50,7 @@ export default async function Layout({ children }: LayoutProps) {
{workspace.enabled ? (
- children
+
{children}
) : (
diff --git a/apps/dashboard/app/(app)/ratelimits/[namespaceId]/_overview/components/charts/bar-chart/hooks/use-fetch-timeseries.ts b/apps/dashboard/app/(app)/ratelimits/[namespaceId]/_overview/components/charts/bar-chart/hooks/use-fetch-timeseries.ts
index d834c0054d..381e63a0da 100644
--- a/apps/dashboard/app/(app)/ratelimits/[namespaceId]/_overview/components/charts/bar-chart/hooks/use-fetch-timeseries.ts
+++ b/apps/dashboard/app/(app)/ratelimits/[namespaceId]/_overview/components/charts/bar-chart/hooks/use-fetch-timeseries.ts
@@ -1,19 +1,20 @@
import { formatTimestampForChart } from "@/components/logs/chart/utils/format-timestamp";
import { TIMESERIES_DATA_WINDOW } from "@/components/logs/constants";
import { trpc } from "@/lib/trpc/client";
+import { useQueryTime } from "@/providers/query-time-provider";
import { useMemo } from "react";
import { useFilters } from "../../../../hooks/use-filters";
import type { RatelimitOverviewQueryTimeseriesPayload } from "../query-timeseries.schema";
export const useFetchRatelimitOverviewTimeseries = (namespaceId: string) => {
const { filters } = useFilters();
- const dateNow = useMemo(() => Date.now(), []);
+ const { queryTime: timestamp } = useQueryTime();
const queryParams = useMemo(() => {
const params: RatelimitOverviewQueryTimeseriesPayload = {
namespaceId,
- startTime: dateNow - TIMESERIES_DATA_WINDOW,
- endTime: dateNow,
+ startTime: timestamp - TIMESERIES_DATA_WINDOW,
+ endTime: timestamp,
identifiers: { filters: [] },
since: "",
};
@@ -52,7 +53,7 @@ export const useFetchRatelimitOverviewTimeseries = (namespaceId: string) => {
});
return params;
- }, [filters, dateNow, namespaceId]);
+ }, [filters, timestamp, namespaceId]);
const { data, isLoading, isError } = trpc.ratelimit.logs.queryRatelimitTimeseries.useQuery(
queryParams,
diff --git a/apps/dashboard/app/(app)/ratelimits/[namespaceId]/_overview/components/controls/components/logs-refresh.tsx b/apps/dashboard/app/(app)/ratelimits/[namespaceId]/_overview/components/controls/components/logs-refresh.tsx
index 25e6f6dcdc..8ab9cd22b3 100644
--- a/apps/dashboard/app/(app)/ratelimits/[namespaceId]/_overview/components/controls/components/logs-refresh.tsx
+++ b/apps/dashboard/app/(app)/ratelimits/[namespaceId]/_overview/components/controls/components/logs-refresh.tsx
@@ -1,13 +1,17 @@
import { RefreshButton } from "@/components/logs/refresh-button";
import { trpc } from "@/lib/trpc/client";
+import { useQueryTime } from "@/providers/query-time-provider";
import { useFilters } from "../../../hooks/use-filters";
export const LogsRefresh = () => {
const { filters } = useFilters();
+ const { refreshQueryTime } = useQueryTime();
+
const { ratelimit } = trpc.useUtils();
const hasRelativeFilter = filters.find((f) => f.field === "since");
const handleRefresh = () => {
+ refreshQueryTime();
ratelimit.overview.logs.query.invalidate();
ratelimit.overview.logs.queryRatelimitLatencyTimeseries.invalidate();
ratelimit.logs.queryRatelimitTimeseries.invalidate();
diff --git a/apps/dashboard/app/(app)/ratelimits/[namespaceId]/_overview/components/table/hooks/use-logs-query.ts b/apps/dashboard/app/(app)/ratelimits/[namespaceId]/_overview/components/table/hooks/use-logs-query.ts
index 256ee3ec1c..bd9db15a39 100644
--- a/apps/dashboard/app/(app)/ratelimits/[namespaceId]/_overview/components/table/hooks/use-logs-query.ts
+++ b/apps/dashboard/app/(app)/ratelimits/[namespaceId]/_overview/components/table/hooks/use-logs-query.ts
@@ -1,5 +1,6 @@
import { HISTORICAL_DATA_WINDOW } from "@/components/logs/constants";
import { trpc } from "@/lib/trpc/client";
+import { useQueryTime } from "@/providers/query-time-provider";
import type { RatelimitOverviewLog } from "@unkey/clickhouse/src/ratelimits";
import { useEffect, useMemo, useState } from "react";
import { useSort } from "../../../../../../../../components/logs/hooks/use-sort";
@@ -18,18 +19,19 @@ export function useRatelimitOverviewLogsQuery({ namespaceId, limit = 50 }: UseLo
);
const { filters } = useFilters();
+ const { queryTime: timestamp } = useQueryTime();
const historicalLogs = useMemo(() => Array.from(historicalLogsMap.values()), [historicalLogsMap]);
const { sorts } = useSort();
//Required for preventing double trpc call during initial render
- const dateNow = useMemo(() => Date.now(), []);
+
const queryParams = useMemo(() => {
const params: RatelimitQueryOverviewLogsPayload = {
limit,
- startTime: dateNow - HISTORICAL_DATA_WINDOW,
- endTime: dateNow,
+ startTime: timestamp - HISTORICAL_DATA_WINDOW,
+ endTime: timestamp,
identifiers: { filters: [] },
status: { filters: [] },
namespaceId,
@@ -84,7 +86,7 @@ export function useRatelimitOverviewLogsQuery({ namespaceId, limit = 50 }: UseLo
});
return params;
- }, [filters, limit, dateNow, namespaceId, sorts]);
+ }, [filters, limit, timestamp, namespaceId, sorts]);
// Main query for historical data
const {
diff --git a/apps/dashboard/app/(app)/ratelimits/[namespaceId]/logs/components/charts/hooks/use-fetch-timeseries.ts b/apps/dashboard/app/(app)/ratelimits/[namespaceId]/logs/components/charts/hooks/use-fetch-timeseries.ts
index 33602ca29a..a5205f2324 100644
--- a/apps/dashboard/app/(app)/ratelimits/[namespaceId]/logs/components/charts/hooks/use-fetch-timeseries.ts
+++ b/apps/dashboard/app/(app)/ratelimits/[namespaceId]/logs/components/charts/hooks/use-fetch-timeseries.ts
@@ -1,19 +1,20 @@
import { formatTimestampForChart } from "@/components/logs/chart/utils/format-timestamp";
import { TIMESERIES_DATA_WINDOW } from "@/components/logs/constants";
import { trpc } from "@/lib/trpc/client";
+import { useQueryTime } from "@/providers/query-time-provider";
import { useMemo } from "react";
import { useFilters } from "../../../hooks/use-filters";
import type { RatelimitQueryTimeseriesPayload } from "../query-timeseries.schema";
export const useFetchRatelimitTimeseries = (namespaceId: string) => {
const { filters } = useFilters();
- const dateNow = useMemo(() => Date.now(), []);
+ const { queryTime: timestamp } = useQueryTime();
const queryParams = useMemo(() => {
const params: RatelimitQueryTimeseriesPayload = {
namespaceId,
- startTime: dateNow - TIMESERIES_DATA_WINDOW,
- endTime: dateNow,
+ startTime: timestamp - TIMESERIES_DATA_WINDOW,
+ endTime: timestamp,
identifiers: { filters: [] },
since: "",
};
@@ -52,7 +53,7 @@ export const useFetchRatelimitTimeseries = (namespaceId: string) => {
});
return params;
- }, [filters, dateNow, namespaceId]);
+ }, [filters, namespaceId, timestamp]);
const { data, isLoading, isError } = trpc.ratelimit.logs.queryRatelimitTimeseries.useQuery(
queryParams,
diff --git a/apps/dashboard/app/(app)/ratelimits/[namespaceId]/logs/components/controls/components/logs-refresh.tsx b/apps/dashboard/app/(app)/ratelimits/[namespaceId]/logs/components/controls/components/logs-refresh.tsx
index ba114836ad..3bf0b5cf5b 100644
--- a/apps/dashboard/app/(app)/ratelimits/[namespaceId]/logs/components/controls/components/logs-refresh.tsx
+++ b/apps/dashboard/app/(app)/ratelimits/[namespaceId]/logs/components/controls/components/logs-refresh.tsx
@@ -1,15 +1,18 @@
import { RefreshButton } from "@/components/logs/refresh-button";
import { trpc } from "@/lib/trpc/client";
+import { useQueryTime } from "@/providers/query-time-provider";
import { useRatelimitLogsContext } from "../../../context/logs";
import { useFilters } from "../../../hooks/use-filters";
export const LogsRefresh = () => {
const { toggleLive, isLive } = useRatelimitLogsContext();
+ const { refreshQueryTime } = useQueryTime();
const { filters } = useFilters();
const { ratelimit } = trpc.useUtils();
const hasRelativeFilter = filters.find((f) => f.field === "since");
const handleRefresh = () => {
+ refreshQueryTime();
ratelimit.logs.query.invalidate();
ratelimit.logs.queryRatelimitTimeseries.invalidate();
};
diff --git a/apps/dashboard/app/(app)/ratelimits/[namespaceId]/logs/components/table/hooks/use-logs-query.ts b/apps/dashboard/app/(app)/ratelimits/[namespaceId]/logs/components/table/hooks/use-logs-query.ts
index 73ead945b5..b2f9c6ba09 100644
--- a/apps/dashboard/app/(app)/ratelimits/[namespaceId]/logs/components/table/hooks/use-logs-query.ts
+++ b/apps/dashboard/app/(app)/ratelimits/[namespaceId]/logs/components/table/hooks/use-logs-query.ts
@@ -1,5 +1,6 @@
import { HISTORICAL_DATA_WINDOW } from "@/components/logs/constants";
import { trpc } from "@/lib/trpc/client";
+import { useQueryTime } from "@/providers/query-time-provider";
import type { RatelimitLog } from "@unkey/clickhouse/src/ratelimits";
import { useCallback, useEffect, useMemo, useState } from "react";
import { useFilters } from "../../../hooks/use-filters";
@@ -23,6 +24,8 @@ export function useRatelimitLogsQuery({
const [realtimeLogsMap, setRealtimeLogsMap] = useState(() => new Map());
const [totalCount, setTotalCount] = useState(0);
+ const { queryTime: timestamp } = useQueryTime();
+
const { filters } = useFilters();
const queryClient = trpc.useUtils();
@@ -32,13 +35,11 @@ export function useRatelimitLogsQuery({
const historicalLogs = useMemo(() => Array.from(historicalLogsMap.values()), [historicalLogsMap]);
- //Required for preventing double trpc call during initial render
- const dateNow = useMemo(() => Date.now(), []);
const queryParams = useMemo(() => {
const params: RatelimitQueryLogsPayload = {
limit,
- startTime: dateNow - HISTORICAL_DATA_WINDOW,
- endTime: dateNow,
+ startTime: timestamp - HISTORICAL_DATA_WINDOW,
+ endTime: timestamp,
requestIds: { filters: [] },
identifiers: { filters: [] },
status: { filters: [] },
@@ -105,7 +106,7 @@ export function useRatelimitLogsQuery({
});
return params;
- }, [filters, limit, dateNow, namespaceId]);
+ }, [filters, limit, namespaceId, timestamp]);
// Main query for historical data
const {
diff --git a/apps/dashboard/providers/query-time-provider.tsx b/apps/dashboard/providers/query-time-provider.tsx
new file mode 100644
index 0000000000..2aa760ef0b
--- /dev/null
+++ b/apps/dashboard/providers/query-time-provider.tsx
@@ -0,0 +1,80 @@
+"use client";
+
+import { type ReactNode, createContext, useContext, useEffect, useState } from "react";
+
+let queryTimestamp = Date.now();
+let subscribers: ((timestamp: number) => void)[] = [];
+
+export const refreshQueryTime = () => {
+ queryTimestamp = Date.now();
+ subscribers.forEach((callback) => callback(queryTimestamp));
+ return queryTimestamp;
+};
+
+type QueryTimeContextType = {
+ queryTime: number;
+ refreshQueryTime: () => number;
+};
+
+const QueryTimeContext = createContext(undefined);
+
+/**
+ * Provides a shared timestamp reference for data fetching operations.
+ *
+ * Prevents React Query and other data fetching mechanisms from triggering
+ * unnecessary refetches due to components generating new timestamps on each render.
+ *
+ * Without this utility, each component would:
+ * - Create its own `Date.now()` when mounted or re-rendered
+ * - Cause React Query to treat it as a new dependency and refetch
+ * - Result in different components showing data from inconsistent time windows
+ *
+ *
+ * ```tsx
+ * // In a data fetching hook
+ * const { queryTime } = useQueryTime();
+ *
+ * // Use in query parameters
+ * const params = {
+ * startTime: queryTime - TIME_WINDOW,
+ * endTime: queryTime
+ * };
+ *
+ * // Only refetches when explicitly refreshed
+ * const { data } = useQuery(['data', params], fetchData);
+ * ```
+ *
+ * When you need to refresh all queries simultaneously:
+ * ```tsx
+ * const { refreshQueryTime } = useQueryTime();
+ *
+ * // Updates timestamp for ALL components
+ * const handleRefresh = () => refreshQueryTime();
+ * ```
+ */
+export const QueryTimeProvider = ({ children }: { children: ReactNode }) => {
+ const [queryTime, setQueryTime] = useState(queryTimestamp);
+
+ useEffect(() => {
+ const callback = (newTimestamp: number) => setQueryTime(newTimestamp);
+ subscribers.push(callback);
+
+ return () => {
+ subscribers = subscribers.filter((cb) => cb !== callback);
+ };
+ }, []);
+
+ return (
+
+ {children}
+
+ );
+};
+
+export const useQueryTime = () => {
+ const context = useContext(QueryTimeContext);
+ if (context === undefined) {
+ throw new Error("useQueryTime must be used within a QueryTimeProvider");
+ }
+ return context;
+};