TheEdoRan / next-safe-action

Type safe and validated Server Actions in your Next.js (App Router) project.

Home Page:https://next-safe-action.dev

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

[FEATURE] executeAsync

enisze opened this issue · comments

Are you using the latest version of this library?

  • I verified that the issue exists in the latest next-safe-action release

Is there an existing issue for this?

  • I have searched the existing issues and found nothing that matches

Suggest an idea

I would like to await my execute and get the result inline instead of using the callbacks. Like e.g. mutate and mutateAsync in react-query. Was this already thought about / why is this not possible?

I would like to do something like this:

const result = await executeAsync(input) 

Additional context

No response

You can await the Server Action by directly calling it. If you want to await execute from useAction/useOptimisticAction hooks this is currently not possible, due to a React limitation discussed in #72. This limitation will go away when useActionState will land in Next.js.

Ah I see thank you @TheEdoRan so its because useTransition is used under the hood and that currently cannot be awaited?

The thing is I would like to still keep the loading and error states of the useAction hook thats why I was asking. :)

so its because useTransition is used under the hood and that currently cannot be awaited?

Yes, exactly.

The thing is I would like to still keep the loading and error states of the useAction hook thats why I was asking.

Yeah, hopefully this will be fixed with useActionState hook, unfortunately React APIs for Server Actions are still pretty unstable (and undocumented) at this time.

Hey, I also miss this feature, if someone wants to have the execute async, there is my custom hook to handle it:

import { useRef } from 'react';

import { SafeAction } from 'next-safe-action/.';
import { HookCallbacks, useAction } from 'next-safe-action/hooks';
import { Schema, z } from 'zod';

export const useSafeAction = <const S extends Schema, const Data>(
  safeAction: SafeAction<S, Data>,
  callbacks?: HookCallbacks<S, Data> | undefined,
) => {
  const promiseResolveRef =
    useRef<(values: Data | PromiseLike<Data>) => void>();
  const promiseRejectRef = useRef<(reason?: any) => void>();

  const action = useAction(safeAction, {
    ...callbacks,
    onSettled: (result, input, reset) => {
      if (result.data) {
        promiseResolveRef.current?.(result.data);
      } else {
        const message =
          result.fetchError || result.serverError || 'Unknown error';
        promiseRejectRef.current?.(message);
      }

      promiseResolveRef.current = undefined;
      promiseRejectRef.current = undefined;

      callbacks?.onSettled?.(result, input, reset);
    },
  });

  const execute = async (input: z.infer<S>) =>
    new Promise<Data>((resolve, reject) => {
      promiseResolveRef.current = resolve;
      promiseRejectRef.current = reject;

      action.execute(input);
    });

  return {
    ...action,
    execute,
  };
};

@kriziu awesome, that works! But be aware that Next.js recommends to use useTransition or useOptimistic for non-form elements, and form related hooks for forms. This will be hopefully solved by useActionState once for all.