1import { createMutationButton, MutationClient, queryClientOptimisticHelpers, useMutate } from "@clo/react-mutation";
2import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
3import { queryOptions as queryOptions } from "@tanstack/react-query";
4import { useSuspenseQuery } from "@tanstack/react-query";
5import { QueryKeyAndFn } from "../../src/tanstack-query.ts";
6
7const queryClient = new QueryClient();
8const mutationClient = new MutationClient({
9 context: {
10 client: queryClient,
11 get<T>(q: QueryKeyAndFn<T>): T | undefined {
12 return queryClient.getQueryData<T>(q.queryKey);
13 },
14 },
15 getOptimisticHelpers: queryClientOptimisticHelpers(queryClient),
16 reportError(error: unknown) {
17 console.error("Mutation error:", error);
18 },
19});
20
21// React query stuff
22let backendValue = 0;
23const queryCounter = queryOptions({
24 queryKey: ["demo"],
25 queryFn: () => ({ count: backendValue }),
26 staleTime: Infinity,
27});
28
29// Define a simple mutation
30// const mutIncrement = mutationClient.queuedMutation({
31// async mutate(amount: number) {
32// // Simulate API call
33// await new Promise((resolve) => setTimeout(resolve, 500));
34// if (Math.random() < 0.1) throw new Error("Random error occurred!");
35// backendValue += amount;
36
37// return amount;
38// },
39// describe: "Increment counter",
40// optimistic({ helpers, args: [amount] }) {
41// helpers.objSet(queryCounter, ["count"], (n) => n + amount);
42// },
43// async refetch({ client }) {
44// await client.invalidateQueries(queryCounter);
45// },
46// });
47const mutIncrement = mutationClient.defineBatched({
48 mode: "debounce",
49 time: 200,
50
51 optimistic({ helpers }, amount: number) {
52 helpers.objIncrement(queryCounter, ["count"], amount);
53 },
54 key: () => "counter",
55 getValue: ({ get }) => get(queryCounter)?.count ?? 0,
56
57 async commit({ initial, current }) {
58 const delta = current - initial;
59 // Simulate API call
60 await new Promise((resolve) => setTimeout(resolve, 500));
61 if (Math.random() < 0.1) throw new Error("Random error occurred!");
62 backendValue += delta;
63 return delta;
64 },
65
66 describe: "update counter",
67 describeResult: ({ initial, current }) => {
68 const delta = current - initial;
69 return `Counter updated by ${delta > 0 ? "+" : ""}${delta}`;
70 },
71});
72
73function CustomButton(
74 { onClick, isPending, ...rest }: {
75 onClick: (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
76 isPending: boolean;
77 children: React.ReactNode;
78 className?: string;
79 },
80) {
81 return (
82 <button
83 {...rest}
84 onClick={onClick}
85 disabled={isPending}
86 />
87 );
88}
89
90const MutationButton = createMutationButton(CustomButton);
91
92function Counter() {
93 const { data: { count } } = useSuspenseQuery(queryCounter);
94 const mutation = useMutate(mutIncrement);
95
96 return (
97 <div className="counter-card">
98 <div className="counter-display">
99 <p className="count">Count: {count}</p>
100 </div>
101
102 <div className="mutation-info">
103 <pre><code>{JSON.stringify(mutation, null, 2)}</code></pre>
104 </div>
105
106 <div className="button-group">
107 <MutationButton
108 mutation={mutation}
109 args={[1]}
110 className="primary-button"
111 >
112 Increment +1
113 </MutationButton>
114 <MutationButton
115 mutation={mutation}
116 args={[-1]}
117 className="primary-button"
118 >
119 Decrement -1
120 </MutationButton>
121 </div>
122 </div>
123 );
124}
125
126function App() {
127 return (
128 <QueryClientProvider client={queryClient}>
129 <div className="app">
130 <Counter />
131 </div>
132 </QueryClientProvider>
133 );
134}
135
136export default App;