42 lines · 1.3 KB
import { useRouter } from "@tanstack/react-router";
import { useState } from "react";

type ActionState = { status: "idle" } | { status: "pending" } | { status: "error"; message: string };

function messageOf(error: unknown): string {
  if (error instanceof Error && error.message) return error.message;
  return "Something went wrong";
}

/**
 * Runs a server-function mutation, tracks its pending/error state and refreshes route loaders on success.
 */
export function useAction<TInput, TResult>(
  action: (input: TInput) => Promise<TResult>,
  options: { invalidate?: boolean } = {},
) {
  const router = useRouter();
  const [state, setState] = useState<ActionState>({ status: "idle" });
  const invalidate = options.invalidate ?? true;

  async function run(input: TInput): Promise<TResult | undefined> {
    setState({ status: "pending" });
    try {
      const result = await action(input);
      if (invalidate) await router.invalidate();
      setState({ status: "idle" });
      return result;
    } catch (error) {
      setState({ status: "error", message: messageOf(error) });
      return undefined;
    }
  }

  return {
    run,
    pending: state.status === "pending",
    error: state.status === "error" ? state.message : null,
    reset: () => setState({ status: "idle" }),
  };