60 lines
1.6 KiB
TypeScript
60 lines
1.6 KiB
TypeScript
|
|
import { useState, useCallback } from "react";
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Hook for handling mutations (create, update, delete operations).
|
||
|
|
* Manages loading state and error handling for async mutations.
|
||
|
|
*
|
||
|
|
* @param mutationFn - Function that performs the mutation and returns a Promise
|
||
|
|
* @param options - Configuration options
|
||
|
|
* @returns Object containing mutate function, loading state, and error
|
||
|
|
*/
|
||
|
|
export function useMutation<TArgs, TResponse = void>(
|
||
|
|
mutationFn: (args: TArgs) => Promise<TResponse>,
|
||
|
|
options: {
|
||
|
|
/** Callback called on successful mutation */
|
||
|
|
onSuccess?: (data: TResponse) => void;
|
||
|
|
/** Callback called on mutation error */
|
||
|
|
onError?: (err: unknown) => void;
|
||
|
|
} = {}
|
||
|
|
): {
|
||
|
|
mutate: (args: TArgs) => Promise<TResponse | undefined>;
|
||
|
|
isLoading: boolean;
|
||
|
|
error: string | null;
|
||
|
|
} {
|
||
|
|
const [isLoading, setIsLoading] = useState(false);
|
||
|
|
const [error, setError] = useState<string | null>(null);
|
||
|
|
|
||
|
|
const mutate = useCallback(
|
||
|
|
async (args: TArgs): Promise<TResponse | undefined> => {
|
||
|
|
setIsLoading(true);
|
||
|
|
setError(null);
|
||
|
|
|
||
|
|
try {
|
||
|
|
const result = await mutationFn(args);
|
||
|
|
if (options.onSuccess) {
|
||
|
|
options.onSuccess(result);
|
||
|
|
}
|
||
|
|
return result;
|
||
|
|
} catch (err) {
|
||
|
|
const errorMessage = err instanceof Error ? err.message : "Operation failed";
|
||
|
|
setError(errorMessage);
|
||
|
|
if (options.onError) {
|
||
|
|
options.onError(err);
|
||
|
|
} else {
|
||
|
|
console.error("Mutation failed:", err);
|
||
|
|
}
|
||
|
|
throw err;
|
||
|
|
} finally {
|
||
|
|
setIsLoading(false);
|
||
|
|
}
|
||
|
|
},
|
||
|
|
[mutationFn, options]
|
||
|
|
);
|
||
|
|
|
||
|
|
return {
|
||
|
|
mutate,
|
||
|
|
isLoading,
|
||
|
|
error,
|
||
|
|
};
|
||
|
|
}
|