diff --git a/.gitignore b/.gitignore index ba2a97b57acafe13673b76a624a59ebceadb2849..85b73bbee851c61404b06f32a09def6bf0521725 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ node_modules coverage +*.tsbuildinfo diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000000000000000000000000000000000000..41583e36ca8d2eaf61c855a0500988bb163adc2a --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +@jsr:registry=https://npm.jsr.io diff --git a/example/index.html b/example/index.html new file mode 100644 index 0000000000000000000000000000000000000000..593e1a2c12d1829ed17215e75f627c2406f3dcd5 --- /dev/null +++ b/example/index.html @@ -0,0 +1,12 @@ + + + + + + React Mutation Demo + + +
+ + + diff --git a/example/package.json b/example/package.json new file mode 100644 index 0000000000000000000000000000000000000000..abe5c67893ac5a9a0cfb0ed2777534c9c6296029 --- /dev/null +++ b/example/package.json @@ -0,0 +1,19 @@ +{ + "private": true, + "type": "module", + "scripts": { + "dev": "vite" + }, + "dependencies": { + "@tanstack/react-query": "^5.90.20", + "react": "^19.2.4", + "react-dom": "^19.2.4" + }, + "devDependencies": { + "@types/node": "^24.10.1", + "@types/react": "^19.2.5", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^5.1.1", + "vite": "^7.2.4" + } +} diff --git a/example/src/App.tsx b/example/src/App.tsx new file mode 100644 index 0000000000000000000000000000000000000000..cf05c93031fb5e99848aa732c90c14134355eee4 --- /dev/null +++ b/example/src/App.tsx @@ -0,0 +1,137 @@ +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { + createMutationButton, + MutationClient, + queryClientOptimisticHelpers, + useMutation, +} from "@clo/react-mutation"; +import { queryOptions as queryOptions } from "@tanstack/react-query"; +import { useSuspenseQuery } from "@tanstack/react-query"; +import { QueryKeyAndFn } from "../../src/tanstack-query.ts"; + +const queryClient = new QueryClient(); +const mutationClient = new MutationClient({ + context: { + client: queryClient, + get(q: QueryKeyAndFn): T | undefined { + return queryClient.getQueryData(q.queryKey); + }, + }, + getOptimisticHelpers: queryClientOptimisticHelpers(queryClient), + reportError(error: unknown) { + console.error("Mutation error:", error); + }, +}); + +// React query stuff +let backendValue = 0; +const queryCounter = queryOptions({ + queryKey: ["demo"], + queryFn: () => ({ count: backendValue }), + staleTime: Infinity, +}); + +// Define a simple mutation +// const mutIncrement = mutationClient.queuedMutation({ +// async mutate(amount: number) { +// // Simulate API call +// await new Promise((resolve) => setTimeout(resolve, 500)); +// if (Math.random() < 0.1) throw new Error("Random error occurred!"); +// backendValue += amount; + +// return amount; +// }, +// describe: "Increment counter", +// optimistic({ helpers, args: [amount] }) { +// helpers.objSet(queryCounter, ["count"], (n) => n + amount); +// }, +// async refetch({ client }) { +// await client.invalidateQueries(queryCounter); +// }, +// }); +const mutIncrement = mutationClient.defineBatched({ + mode: "debounce", + time: 200, + + optimistic({ helpers }, amount: number) { + helpers.objIncrement(queryCounter, ["count"], amount); + }, + key: () => "counter", + getValue: ({ get }) => get(queryCounter)?.count ?? 0, + + async commit({ initial, current }) { + const delta = current - initial; + // Simulate API call + await new Promise((resolve) => setTimeout(resolve, 500)); + if (Math.random() < 0.1) throw new Error("Random error occurred!"); + backendValue += delta; + return delta; + }, + + describe: "update counter", +}); + +function CustomButton( + { onClick, isPending, ...rest }: { + onClick: (e: React.MouseEvent) => void; + isPending: boolean; + children: React.ReactNode; + className?: string; + }, +) { + return ( +