@dreamkit/solid
Version:
DreamKit tools for Solid.
111 lines (110 loc) • 3.58 kB
JavaScript
import { useActionManager } from "./useActionManager.js";
import { batch, createSignal, onCleanup, onMount, untrack } from "solid-js";
export function createAction(cb) {
const context = useActionManager();
const queue = new Map();
const asyncAction = async (args, abortController) => cb.bind({ abortController })(...args);
const [id, setId] = createSignal(0);
const [args, setArgs] = createSignal([]);
const [result, setResult] = createSignal();
const [state, setState] = createSignal("idle");
const [usedError, setUsedError] = createSignal(false);
const [error, setError] = createSignal();
const running = () => state() === "running";
const clear = () => set("idle", undefined, undefined);
const set = (state, result, error) => {
batch(() => {
setResult(result);
setError(error);
setState(state);
if (state === "idle")
setArgs([]);
});
};
const abort = () => {
const idValue = untrack(id);
const item = queue.get(idValue);
if (item) {
item.abortController.abort();
queue.delete(idValue);
}
clear();
};
const [lastArgs, setLastArgs] = createSignal();
const canRetry = () => !!lastArgs();
const retry = () => {
const args = untrack(lastArgs);
return action(...(args || []));
};
const action = (...args) => {
if (untrack(running))
return;
const selfId = untrack(id) + 1;
const item = { abortController: new AbortController() };
const next = () => queue.delete(selfId);
queue.set(selfId, item);
batch(() => {
setId(selfId);
setArgs(args);
setState("running");
});
setLastArgs(args);
asyncAction(args, item.abortController)
.then((result) => {
if (!next())
return;
set("success", result, undefined);
})
.catch((error) => {
if (!next())
return;
console.error(error);
set("error", undefined, error);
});
};
Object.defineProperties(action, {
ref: {
value: {
get errorWithoutUsing() {
return error();
},
},
},
id: { get: id },
args: { get: args },
result: { get: result },
running: { get: running },
error: {
get: () => {
setUsedError(true);
return error();
},
},
isErrorUsed: { get: usedError },
state: { get: state },
clear: { value: clear },
abort: { value: abort },
title: { get: () => cb.title },
params: { get: () => cb.params },
retry: { value: retry },
canRetry: { get: canRetry },
with: {
value: (...args) => {
return new Proxy(action, {
get: (target, p) => target[p],
apply: (target, self) => {
const targetArgs = args.length === 1 && typeof args[0] === "function"
? [args[0]()]
: args;
target.bind(self)(...targetArgs);
},
});
},
},
});
if (context) {
onMount(() => context?.add(action));
onCleanup(() => context.remove(action));
}
return action;
}