Skip to content

#2223 - dragonfruit #2362

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 12 additions & 12 deletions packages/react-hooks/src/hooks/useRealtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -78,15 +78,15 @@ export function useRealtimeRun<TTask extends AnyTask>(
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<RealtimeRun<TTask>>([idKey, "run"], null);
const { data: run, mutate: mutateRun } = useInternalSWR<RealtimeRun<TTask>>([idKey, "run"], null);

const { data: error = undefined, mutate: setError } = useSWR<undefined | Error>(
const { data: error = undefined, mutate: setError } = useInternalSWR<undefined | Error>(
[idKey, "error"],
null
);

// Add state to track when the subscription is complete
const { data: isComplete = false, mutate: setIsComplete } = useSWR<boolean>(
const { data: isComplete = false, mutate: setIsComplete } = useInternalSWR<boolean>(
[idKey, "complete"],
null
);
Expand Down Expand Up @@ -224,7 +224,7 @@ export function useRealtimeRunWithStreams<
const [initialStreamsFallback] = useState({} as StreamResults<TStreams>);

// Store the streams state in SWR, using the idKey as the key to share states.
const { data: streams, mutate: mutateStreams } = useSWR<StreamResults<TStreams>>(
const { data: streams, mutate: mutateStreams } = useInternalSWR<StreamResults<TStreams>>(
[idKey, "streams"],
null,
{
Expand All @@ -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<RealtimeRun<TTask>>([idKey, "run"], null);
const { data: run, mutate: mutateRun } = useInternalSWR<RealtimeRun<TTask>>([idKey, "run"], null);

// Add state to track when the subscription is complete
const { data: isComplete = false, mutate: setIsComplete } = useSWR<boolean>(
const { data: isComplete = false, mutate: setIsComplete } = useInternalSWR<boolean>(
[idKey, "complete"],
null
);

const { data: error = undefined, mutate: setError } = useSWR<undefined | Error>(
const { data: error = undefined, mutate: setError } = useInternalSWR<undefined | Error>(
[idKey, "error"],
null
);
Expand Down Expand Up @@ -401,7 +401,7 @@ export function useRealtimeRunsWithTag<TTask extends AnyTask>(
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<RealtimeRun<TTask>[]>([idKey, "run"], null, {
const { data: runs, mutate: mutateRuns } = useInternalSWR<RealtimeRun<TTask>[]>([idKey, "run"], null, {
fallbackData: [],
});

Expand All @@ -411,7 +411,7 @@ export function useRealtimeRunsWithTag<TTask extends AnyTask>(
runsRef.current = runs ?? [];
}, [runs]);

const { data: error = undefined, mutate: setError } = useSWR<undefined | Error>(
const { data: error = undefined, mutate: setError } = useInternalSWR<undefined | Error>(
[idKey, "error"],
null
);
Expand Down Expand Up @@ -499,7 +499,7 @@ export function useRealtimeBatch<TTask extends AnyTask>(
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<RealtimeRun<TTask>[]>([idKey, "run"], null, {
const { data: runs, mutate: mutateRuns } = useInternalSWR<RealtimeRun<TTask>[]>([idKey, "run"], null, {
fallbackData: [],
});

Expand All @@ -509,7 +509,7 @@ export function useRealtimeBatch<TTask extends AnyTask>(
runsRef.current = runs ?? [];
}, [runs]);

const { data: error = undefined, mutate: setError } = useSWR<undefined | Error>(
const { data: error = undefined, mutate: setError } = useInternalSWR<undefined | Error>(
[idKey, "error"],
null
);
Expand Down
37 changes: 37 additions & 0 deletions packages/react-hooks/src/utils/trigger-swr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
/**
Expand All @@ -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<Data = any, Error = any>(
key: any,
fetcher: ((key: any) => Data | Promise<Data>) | 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);
}