diff --git a/packages/react-hooks/src/hooks/useRealtime.ts b/packages/react-hooks/src/hooks/useRealtime.ts index 9492c085de..1b0d93169f 100644 --- a/packages/react-hooks/src/hooks/useRealtime.ts +++ b/packages/react-hooks/src/hooks/useRealtime.ts @@ -8,7 +8,7 @@ import { RealtimeRunSkipColumns, } from "@trigger.dev/core/v3"; import { useCallback, useEffect, useId, useRef, useState } from "react"; -import { KeyedMutator, useSWR } from "../utils/trigger-swr.js"; +import { KeyedMutator, useInternalSWR } from "../utils/trigger-swr.js"; import { useApiClient, UseApiClientOptions } from "./useApiClient.js"; import { createThrottledQueue } from "../utils/throttle.js"; @@ -78,15 +78,15 @@ export function useRealtimeRun( const idKey = options?.id ?? hookId; // Store the streams state in SWR, using the idKey as the key to share states. - const { data: run, mutate: mutateRun } = useSWR>([idKey, "run"], null); + const { data: run, mutate: mutateRun } = useInternalSWR>([idKey, "run"], null); - const { data: error = undefined, mutate: setError } = useSWR( + const { data: error = undefined, mutate: setError } = useInternalSWR( [idKey, "error"], null ); // Add state to track when the subscription is complete - const { data: isComplete = false, mutate: setIsComplete } = useSWR( + const { data: isComplete = false, mutate: setIsComplete } = useInternalSWR( [idKey, "complete"], null ); @@ -224,7 +224,7 @@ export function useRealtimeRunWithStreams< const [initialStreamsFallback] = useState({} as StreamResults); // Store the streams state in SWR, using the idKey as the key to share states. - const { data: streams, mutate: mutateStreams } = useSWR>( + const { data: streams, mutate: mutateStreams } = useInternalSWR>( [idKey, "streams"], null, { @@ -239,15 +239,15 @@ export function useRealtimeRunWithStreams< }, [streams]); // Store the streams state in SWR, using the idKey as the key to share states. - const { data: run, mutate: mutateRun } = useSWR>([idKey, "run"], null); + const { data: run, mutate: mutateRun } = useInternalSWR>([idKey, "run"], null); // Add state to track when the subscription is complete - const { data: isComplete = false, mutate: setIsComplete } = useSWR( + const { data: isComplete = false, mutate: setIsComplete } = useInternalSWR( [idKey, "complete"], null ); - const { data: error = undefined, mutate: setError } = useSWR( + const { data: error = undefined, mutate: setError } = useInternalSWR( [idKey, "error"], null ); @@ -401,7 +401,7 @@ export function useRealtimeRunsWithTag( const idKey = options?.id ?? hookId; // Store the streams state in SWR, using the idKey as the key to share states. - const { data: runs, mutate: mutateRuns } = useSWR[]>([idKey, "run"], null, { + const { data: runs, mutate: mutateRuns } = useInternalSWR[]>([idKey, "run"], null, { fallbackData: [], }); @@ -411,7 +411,7 @@ export function useRealtimeRunsWithTag( runsRef.current = runs ?? []; }, [runs]); - const { data: error = undefined, mutate: setError } = useSWR( + const { data: error = undefined, mutate: setError } = useInternalSWR( [idKey, "error"], null ); @@ -499,7 +499,7 @@ export function useRealtimeBatch( const idKey = options?.id ?? hookId; // Store the streams state in SWR, using the idKey as the key to share states. - const { data: runs, mutate: mutateRuns } = useSWR[]>([idKey, "run"], null, { + const { data: runs, mutate: mutateRuns } = useInternalSWR[]>([idKey, "run"], null, { fallbackData: [], }); @@ -509,7 +509,7 @@ export function useRealtimeBatch( runsRef.current = runs ?? []; }, [runs]); - const { data: error = undefined, mutate: setError } = useSWR( + const { data: error = undefined, mutate: setError } = useInternalSWR( [idKey, "error"], null ); diff --git a/packages/react-hooks/src/utils/trigger-swr.ts b/packages/react-hooks/src/utils/trigger-swr.ts index a5455e8a29..5a069b1519 100644 --- a/packages/react-hooks/src/utils/trigger-swr.ts +++ b/packages/react-hooks/src/utils/trigger-swr.ts @@ -6,6 +6,8 @@ import { ApiRequestOptions } from "@trigger.dev/core/v3"; export * from "swr"; // eslint-disable-next-line import/export export { default as useSWR, SWRConfig } from "swr"; +// Import the original useSWR separately for internal use +import { default as useSWROriginal } from "swr"; export type CommonTriggerHookOptions = { /** @@ -24,3 +26,38 @@ export type CommonTriggerHookOptions = { /** Optional additional request configuration */ requestOptions?: ApiRequestOptions; }; + +/** + * Internal isolated useSWR hook that prevents global SWRConfig interference. + * This should only be used by internal Trigger.dev hooks for state management. + * + * For realtime hooks, this ensures that: + * 1. No global fetcher will be invoked accidentally + * 2. Internal state management remains isolated + * 3. Manual mutate() calls work as expected + * + * @param key - SWR key for caching + * @param fetcher - Fetcher function (should be null for internal state management) + * @param config - SWR configuration options + * @returns SWR hook result with isolated configuration + */ +export function useInternalSWR( + key: any, + fetcher: ((key: any) => Data | Promise) | null = null, + config: any = {} +) { + // Always override fetcher to null and disable auto-revalidation for internal state management + // This prevents global SWRConfig fetchers from being invoked + const internalConfig = { + // Disable automatic revalidation for internal state management + revalidateOnFocus: false, + revalidateOnReconnect: false, + revalidateIfStale: false, + // Override any config that might cause global interference + ...config, + // Ensure fetcher remains null even if passed in config to prevent global fetcher usage + fetcher: null, + }; + + return useSWROriginal(key, fetcher, internalConfig); +}