diff --git a/packages/api-client-core/src/GadgetFunctions.ts b/packages/api-client-core/src/GadgetFunctions.ts index 0fd221b9d..147d51837 100644 --- a/packages/api-client-core/src/GadgetFunctions.ts +++ b/packages/api-client-core/src/GadgetFunctions.ts @@ -136,6 +136,21 @@ export interface ActionFunctionMetadata = StubbedActionFunctionMetadata & ActionWithNoIdAndNoVariables; + export type ActionFunction = ActionFunctionMetadata< OptionsT, VariablesT, diff --git a/packages/react/src/useAction.ts b/packages/react/src/useAction.ts index f84c11433..944d943e8 100644 --- a/packages/react/src/useAction.ts +++ b/packages/react/src/useAction.ts @@ -1,4 +1,11 @@ -import type { ActionFunction, DefaultSelection, GadgetRecord, LimitToKnownKeys, Select } from "@gadgetinc/api-client-core"; +import type { + ActionFunction, + DefaultSelection, + GadgetRecord, + LimitToKnownKeys, + Select, + StubbedActionFunction, +} from "@gadgetinc/api-client-core"; import { actionOperation, capitalizeIdentifier, @@ -7,7 +14,7 @@ import { hydrateRecord, namespaceDataPath, } from "@gadgetinc/api-client-core"; -import { useCallback, useContext, useMemo } from "react"; +import { useCallback, useContext, useEffect, useMemo } from "react"; import type { AnyVariables, OperationContext, UseMutationState } from "urql"; import { GadgetUrqlClientContext } from "./GadgetProvider.js"; import { useGadgetMutation } from "./useGadgetMutation.js"; @@ -62,6 +69,28 @@ export const useAction = < > => { if (!useContext(GadgetUrqlClientContext)) throw new Error(noProviderErrorMessage); + useEffect(() => { + if (action.type === ("stubbedAction" as string)) { + const stubbedAction = action as unknown as StubbedActionFunction; + if (!("reason" in stubbedAction) || !("dataPath" in stubbedAction)) { + // Don't dispatch an event if the generated client has not yet been updated with the updated parameters + return; + } + + const event = new CustomEvent("gadget:devharness:stubbedActionError", { + detail: { + reason: stubbedAction.reason, + action: { + actionApiIdentifier: stubbedAction.functionName, + modelApiIdentifier: stubbedAction.modelApiIdentifier, + dataPath: stubbedAction.dataPath, + }, + }, + }); + globalThis.dispatchEvent(event); + } + }, []); + const memoizedOptions = useStructuralMemo(options); const plan = useMemo(() => { return actionOperation( diff --git a/packages/react/src/useGlobalAction.ts b/packages/react/src/useGlobalAction.ts index 515303ff7..2b16a31fd 100644 --- a/packages/react/src/useGlobalAction.ts +++ b/packages/react/src/useGlobalAction.ts @@ -1,6 +1,6 @@ -import type { GlobalActionFunction } from "@gadgetinc/api-client-core"; +import type { GlobalActionFunction, StubbedActionFunction } from "@gadgetinc/api-client-core"; import { get, globalActionOperation, namespaceDataPath } from "@gadgetinc/api-client-core"; -import { useCallback, useMemo } from "react"; +import { useCallback, useEffect, useMemo } from "react"; import type { OperationContext, UseMutationState } from "urql"; import { useGadgetMutation } from "./useGadgetMutation.js"; import type { ActionHookResult } from "./utils.js"; @@ -30,6 +30,27 @@ import { ErrorWrapper } from "./utils.js"; export const useGlobalAction = >( action: F ): ActionHookResult> => { + useEffect(() => { + if (action.type === ("stubbedAction" as string)) { + const stubbedAction = action as unknown as StubbedActionFunction; + if (!("reason" in stubbedAction) || !("dataPath" in stubbedAction)) { + // Don't dispatch an event if the generated client has not yet been updated with the updated parameters + return; + } + + const event = new CustomEvent("gadget:devharness:stubbedActionError", { + detail: { + reason: stubbedAction.reason, + action: { + actionApiIdentifier: stubbedAction.functionName, + dataPath: stubbedAction.dataPath, + }, + }, + }); + globalThis.dispatchEvent(event); + } + }, []); + const plan = useMemo(() => { return globalActionOperation(action.operationName, action.variables, action.namespace); }, [action]);