diff --git a/packages/next-safe-action/src/hook.ts b/packages/next-safe-action/src/hook.ts index e32fb59d..27bcd0f5 100644 --- a/packages/next-safe-action/src/hook.ts +++ b/packages/next-safe-action/src/hook.ts @@ -1,9 +1,9 @@ "use client"; import { - experimental_useOptimistic, useCallback, useEffect, + experimental_useOptimistic as useOptimistic, useRef, useState, useTransition, @@ -53,7 +53,7 @@ const useActionCallbacks = ( const onErrorRef = useRef(cb?.onError); const onSettledRef = useRef(cb?.onSettled); - // Execute the callback on success or error, if provided. + // Execute the callback when the action status changes. useEffect(() => { const onExecute = onExecuteRef.current; const onSuccess = onSuccessRef.current; @@ -83,7 +83,7 @@ const useActionCallbacks = ( /** * Use the action from a Client Component via hook. * @param safeAction The typesafe action. - * @param callbacks Optional callbacks executed when the action succeeds or fails. + * @param callbacks Optional callbacks executed based on the action status. * * {@link https://next-safe-action.dev/docs/usage-from-client/hooks/useaction See an example} */ @@ -92,33 +92,34 @@ export const useAction = ( callbacks?: HookCallbacks ) => { const [, startTransition] = useTransition(); - const executor = useRef(safeAction); const [result, setResult] = useState>(DEFAULT_RESULT); const [input, setInput] = useState>(); const [isExecuting, setIsExecuting] = useState(false); const status = getActionStatus(isExecuting, result); - const execute = useCallback((input: z.input) => { - setIsExecuting(true); - setInput(input); - - return startTransition(() => { - return executor - .current(input) - .then((res) => setResult(res ?? DEFAULT_RESULT)) - .catch((e) => { - if (isNextRedirectError(e) || isNextNotFoundError(e)) { - throw e; - } - - setResult({ fetchError: isError(e) ? e.message : "Something went wrong" }); - }) - .finally(() => { - setIsExecuting(false); - }); - }); - }, []); + const execute = useCallback( + (input: z.input) => { + setInput(input); + setIsExecuting(true); + + return startTransition(() => { + return safeAction(input) + .then((res) => setResult(res ?? DEFAULT_RESULT)) + .catch((e) => { + if (isNextRedirectError(e) || isNextNotFoundError(e)) { + throw e; + } + + setResult({ fetchError: isError(e) ? e.message : "Something went wrong" }); + }) + .finally(() => { + setIsExecuting(false); + }); + }); + }, + [safeAction] + ); const reset = useCallback(() => { setResult(DEFAULT_RESULT); @@ -140,7 +141,8 @@ export const useAction = ( * **NOTE: This hook uses an experimental React feature.** * @param safeAction The typesafe action. * @param initialOptimisticData Initial optimistic data. - * @param callbacks Optional callbacks executed when the action succeeds or fails. + * @param reducer Optimistic state reducer. + * @param callbacks Optional callbacks executed based on the action status. * * {@link https://next-safe-action.dev/docs/usage-from-client/hooks/useoptimisticaction See an example} */ @@ -150,41 +152,40 @@ export const useOptimisticAction = ) => Data, callbacks?: HookCallbacks ) => { + const [, startTransition] = useTransition(); const [result, setResult] = useState>(DEFAULT_RESULT); const [input, setInput] = useState>(); + const [isExecuting, setIsExecuting] = useState(false); - const [optimisticData, setOptimisticState] = experimental_useOptimistic>( + const [optimisticData, setOptimisticState] = useOptimistic>( initialOptimisticData, reducer ); - const [isExecuting, setIsExecuting] = useState(false); - - const executor = useRef(safeAction); - const status = getActionStatus(isExecuting, result); const execute = useCallback( (input: z.input) => { - setIsExecuting(true); - setOptimisticState(input); setInput(input); + setIsExecuting(true); - return executor - .current(input) - .then((res) => setResult(res ?? DEFAULT_RESULT)) - .catch((e) => { - if (isNextRedirectError(e) || isNextNotFoundError(e)) { - throw e; - } - - setResult({ fetchError: isError(e) ? e.message : "Something went wrong" }); - }) - .finally(() => { - setIsExecuting(false); - }); + return startTransition(() => { + setOptimisticState(input); + return safeAction(input) + .then((res) => setResult(res ?? DEFAULT_RESULT)) + .catch((e) => { + if (isNextRedirectError(e) || isNextNotFoundError(e)) { + throw e; + } + + setResult({ fetchError: isError(e) ? e.message : "Something went wrong" }); + }) + .finally(() => { + setIsExecuting(false); + }); + }); }, - [setOptimisticState] + [setOptimisticState, safeAction] ); const reset = useCallback(() => {