next-safe-action
Version:
Type safe and validated Server Actions in your Next.js project.
257 lines (253 loc) • 6.43 kB
JavaScript
"use client";
// src/hooks.ts
import * as React2 from "react";
// src/hooks-utils.ts
import * as React from "react";
var getActionStatus = ({ isIdle, isExecuting, result }) => {
if (isIdle) {
return "idle";
} else if (isExecuting) {
return "executing";
} else if (
typeof result.validationErrors !== "undefined" ||
typeof result.bindArgsValidationErrors !== "undefined" ||
typeof result.serverError !== "undefined"
) {
return "hasErrored";
} else {
return "hasSucceeded";
}
};
var getActionShorthandStatusObject = ({ status, isTransitioning }) => {
return {
isIdle: status === "idle",
isExecuting: status === "executing",
isTransitioning,
isPending: status === "executing" || isTransitioning,
hasSucceeded: status === "hasSucceeded",
hasErrored: status === "hasErrored",
};
};
var useExecuteOnMount = (args) => {
const mounted = React.useRef(false);
React.useEffect(() => {
const t = setTimeout(() => {
if (args.executeOnMount && !mounted.current) {
args.executeFn(args.executeOnMount.input);
mounted.current = true;
}
}, args.executeOnMount?.delayMs ?? 0);
return () => {
clearTimeout(t);
};
}, [args]);
};
var useActionCallbacks = ({ result, input, status, cb }) => {
const onExecuteRef = React.useRef(cb?.onExecute);
const onSuccessRef = React.useRef(cb?.onSuccess);
const onErrorRef = React.useRef(cb?.onError);
const onSettledRef = React.useRef(cb?.onSettled);
React.useEffect(() => {
const onExecute = onExecuteRef.current;
const onSuccess = onSuccessRef.current;
const onError = onErrorRef.current;
const onSettled = onSettledRef.current;
const executeCallbacks = async () => {
switch (status) {
case "executing":
await Promise.resolve(onExecute?.({ input }));
break;
case "hasSucceeded":
await Promise.all([
Promise.resolve(onSuccess?.({ data: result?.data, input })),
Promise.resolve(onSettled?.({ result, input })),
]);
break;
case "hasErrored":
await Promise.all([
Promise.resolve(onError?.({ error: result, input })),
Promise.resolve(onSettled?.({ result, input })),
]);
break;
}
};
executeCallbacks().catch(console.error);
}, [status, result, input]);
};
// src/hooks.ts
var useAction = (safeActionFn, utils) => {
const [isTransitioning, startTransition] = React2.useTransition();
const [result, setResult] = React2.useState({});
const [clientInput, setClientInput] = React2.useState();
const [isExecuting, setIsExecuting] = React2.useState(false);
const [isIdle, setIsIdle] = React2.useState(true);
const status = getActionStatus({ isExecuting, result, isIdle });
const execute = React2.useCallback(
(input) => {
setTimeout(() => {
setIsIdle(false);
setClientInput(input);
setIsExecuting(true);
}, 0);
startTransition(() => {
safeActionFn(input)
.then((res) => setResult(res ?? {}))
.catch((e) => {
setResult({});
throw e;
})
.finally(() => {
setIsExecuting(false);
});
});
},
[safeActionFn]
);
const executeAsync = React2.useCallback(
(input) => {
const fn = new Promise((resolve, reject) => {
setTimeout(() => {
setIsIdle(false);
setClientInput(input);
setIsExecuting(true);
}, 0);
startTransition(() => {
safeActionFn(input)
.then((res) => {
setResult(res ?? {});
resolve(res);
})
.catch((e) => {
setResult({});
reject(e);
})
.finally(() => {
setIsExecuting(false);
});
});
});
return fn;
},
[safeActionFn]
);
const reset = React2.useCallback(() => {
setIsIdle(true);
setClientInput(void 0);
setResult({});
}, []);
useExecuteOnMount({
executeOnMount: utils?.executeOnMount,
executeFn: execute,
});
useActionCallbacks({
result: result ?? {},
input: clientInput,
status,
cb: {
onExecute: utils?.onExecute,
onSuccess: utils?.onSuccess,
onError: utils?.onError,
onSettled: utils?.onSettled,
},
});
return {
execute,
executeAsync,
input: clientInput,
result,
reset,
status,
...getActionShorthandStatusObject({ status, isTransitioning }),
};
};
var useOptimisticAction = (safeActionFn, utils) => {
const [isTransitioning, startTransition] = React2.useTransition();
const [result, setResult] = React2.useState({});
const [clientInput, setClientInput] = React2.useState();
const [isExecuting, setIsExecuting] = React2.useState(false);
const [isIdle, setIsIdle] = React2.useState(true);
const [optimisticState, setOptimisticValue] = React2.useOptimistic(utils.currentState, utils.updateFn);
const status = getActionStatus({ isExecuting, result, isIdle });
const execute = React2.useCallback(
(input) => {
setTimeout(() => {
setIsIdle(false);
setClientInput(input);
setIsExecuting(true);
}, 0);
startTransition(() => {
setOptimisticValue(input);
safeActionFn(input)
.then((res) => setResult(res ?? {}))
.catch((e) => {
setResult({});
throw e;
})
.finally(() => {
setIsExecuting(false);
});
});
},
[safeActionFn, setOptimisticValue]
);
const executeAsync = React2.useCallback(
(input) => {
const fn = new Promise((resolve, reject) => {
setTimeout(() => {
setIsIdle(false);
setClientInput(input);
setIsExecuting(true);
}, 0);
startTransition(() => {
setOptimisticValue(input);
safeActionFn(input)
.then((res) => {
setResult(res ?? {});
resolve(res);
})
.catch((e) => {
setResult({});
reject(e);
})
.finally(() => {
setIsExecuting(false);
});
});
});
return fn;
},
[safeActionFn, setOptimisticValue]
);
const reset = React2.useCallback(() => {
setIsIdle(true);
setClientInput(void 0);
setResult({});
}, []);
useExecuteOnMount({
executeOnMount: utils?.executeOnMount,
executeFn: execute,
});
useActionCallbacks({
result: result ?? {},
input: clientInput,
status,
cb: {
onExecute: utils.onExecute,
onSuccess: utils.onSuccess,
onError: utils.onError,
onSettled: utils.onSettled,
},
});
return {
execute,
executeAsync,
input: clientInput,
result,
optimisticState,
reset,
status,
...getActionShorthandStatusObject({ status, isTransitioning }),
};
};
export { useAction, useOptimisticAction };
//# sourceMappingURL=hooks.mjs.map