| author | |
| committer | |
| log | 4591d794aff944fbb510d589770021c9ccb60806 |
| tree | 189aadc63be13ce6975bbd45a18a6363728664a5 |
| parent | bcb14cf5bd2f877987a158c4e40556f070172435 |
| signature |
3 files changed, 34 insertions(+), 8 deletions(-)
jsr.json+1-1| ... | @@ -1,6 +1,6 @@ | ... | @@ -1,6 +1,6 @@ |
| 1 | { | 1 | { |
| 2 | "name": "@clo/react-mutation", | 2 | "name": "@clo/react-mutation", |
| 3 | "version": "1.0.0-beta.9", | 3 | "version": "1.0.0-beta.10", |
| 4 | "exports": { | 4 | "exports": { |
| 5 | ".": "./src/mod.ts", | 5 | ".": "./src/mod.ts", |
| 6 | "./tanstack-query.ts": "./src/tanstack-query.ts", | 6 | "./tanstack-query.ts": "./src/tanstack-query.ts", |
readme.md+26-3| ... | @@ -120,7 +120,6 @@ export function Example({ id }: { id: string }) { | ... | @@ -120,7 +120,6 @@ export function Example({ id }: { id: string }) { |
| 120 | } | 120 | } |
| 121 | ``` | 121 | ``` |
| 122 | 122 | ||
| 123 | |||
| 124 | ## Optimistic Updates | 123 | ## Optimistic Updates |
| 125 | 124 | ||
| 126 | The `optimistic` function is given an object with the following APIs | 125 | The `optimistic` function is given an object with the following APIs |
| ... | @@ -132,7 +131,7 @@ The `optimistic` function is given an object with the following APIs | ... | @@ -132,7 +131,7 @@ The `optimistic` function is given an object with the following APIs |
| 132 | - `onRestore` - add a callback to revert your optimistic update | 131 | - `onRestore` - add a callback to revert your optimistic update |
| 133 | - `onRefetch` - add a callback to fetch data after a success | 132 | - `onRefetch` - add a callback to fetch data after a success |
| 134 | 133 | ||
| 135 | ### React Query Optimistic Helpers | 134 | #### React Query Optimistic Helpers |
| 136 | 135 | ||
| 137 | When using React Query, you can opt into some incredible helpers for making it | 136 | When using React Query, you can opt into some incredible helpers for making it |
| 138 | very easy to write Optimistic Updates. Our setup at work is with this client | 137 | very easy to write Optimistic Updates. Our setup at work is with this client |
| ... | @@ -228,6 +227,30 @@ function Item({ id }: { id: string }) { | ... | @@ -228,6 +227,30 @@ function Item({ id }: { id: string }) { |
| 228 | } | 227 | } |
| 229 | ``` | 228 | ``` |
| 230 | 229 | ||
| 230 | ## Snapshotting to Skip No-Ops | ||
| 231 | |||
| 232 | For operations that might be passed a parameter that doesn't actually change | ||
| 233 | anything, `snapshot` can be used to detect no-op mutations. | ||
| 234 | |||
| 235 | ```tsx | ||
| 236 | const mutUpdateField = mutations.define({ | ||
| 237 | async mutate(id: string, value: string) { /* mutation */ }, | ||
| 238 | |||
| 239 | optimistic({ args: [id, value], helpers }) { | ||
| 240 | helpers.objSet(queryItem(id), ["value"], value); | ||
| 241 | }, | ||
| 242 | |||
| 243 | // called once before `optimistic` and once after. if the values are equal, | ||
| 244 | // then the mutation is cancelled (won't call `onSuccess`, but will `onSettled`) | ||
| 245 | // (defaulting to a json-based deep equal check, customize in MutationClient) | ||
| 246 | snapshot({ args: [id], get }) { | ||
| 247 | return get(queryItem(id))?.value; | ||
| 248 | } | ||
| 249 | |||
| 250 | // (...describe and optionally debounce stuff...) | ||
| 251 | }); | ||
| 252 | ``` | ||
| 253 | |||
| 231 | ## Calling Mutations | 254 | ## Calling Mutations |
| 232 | 255 | ||
| 233 | Three methods exist for calling mutations: | 256 | Three methods exist for calling mutations: |
| ... | @@ -319,7 +342,7 @@ It can now be used for easy mutations: | ... | @@ -319,7 +342,7 @@ It can now be used for easy mutations: |
| 319 | 342 | ||
| 320 | ## Batched Mutations | 343 | ## Batched Mutations |
| 321 | 344 | ||
| 322 | This is an advanced feature. Complete Documentation is pending. | 345 | This is an advanced feature. Complete Documentation is pending. It is not recommended to use this. |
| 323 | 346 | ||
| 324 | Each call to the mutation applies new optimistic state on top of the previous, | 347 | Each call to the mutation applies new optimistic state on top of the previous, |
| 325 | and after a debounce / throttle, the new optimistic state is committed to the | 348 | and after a debounce / throttle, the new optimistic state is committed to the |
src/blocking.ts+7-4| ... | @@ -292,7 +292,7 @@ export class BlockingMutation< | ... | @@ -292,7 +292,7 @@ export class BlockingMutation< |
| 292 | 292 | ||
| 293 | // Check if debouncing is enabled | 293 | // Check if debouncing is enabled |
| 294 | if (this.#options.debounceMs !== undefined) { | 294 | if (this.#options.debounceMs !== undefined) { |
| 295 | return this.#runDebouncedAndReturn(args, key, channel, userOnRestore); | 295 | return this.#runDebouncedAndReturn(args, key, channel, userOnRestore, true); |
| 296 | } | 296 | } |
| 297 | 297 | ||
| 298 | // Create shared optimistic helpers instance for the channel if it doesn't exist | 298 | // Create shared optimistic helpers instance for the channel if it doesn't exist |
| ... | @@ -466,6 +466,7 @@ export class BlockingMutation< | ... | @@ -466,6 +466,7 @@ export class BlockingMutation< |
| 466 | key: string, | 466 | key: string, |
| 467 | channel: Channel<Args, Result, Config["optimisticHelpers"]>, | 467 | channel: Channel<Args, Result, Config["optimisticHelpers"]>, |
| 468 | userOnRestore?: () => void, | 468 | userOnRestore?: () => void, |
| 469 | fromRunWithOptions = false, | ||
| 469 | ): Promise<Result> { | 470 | ): Promise<Result> { |
| 470 | // If there's a pending debounced call, roll it back | 471 | // If there's a pending debounced call, roll it back |
| 471 | if (channel.pendingDebounced) { | 472 | if (channel.pendingDebounced) { |
| ... | @@ -624,13 +625,14 @@ export class BlockingMutation< | ... | @@ -624,13 +625,14 @@ export class BlockingMutation< |
| 624 | reject: wrapperReject, | 625 | reject: wrapperReject, |
| 625 | } = Promise.withResolvers<Result>(); | 626 | } = Promise.withResolvers<Result>(); |
| 626 | 627 | ||
| 627 | // Add global handler logic for no-listener case | 628 | // Resolve/reject pending promises and add global handler logic for execution-time checks |
| 628 | wrapperPromise.then( | 629 | wrapperPromise.then( |
| 629 | (result) => { | 630 | (result) => { |
| 630 | // Resolve all pending promises | 631 | // Resolve all pending promises |
| 631 | pending.forEach((p) => p.resolve(result)); | 632 | pending.forEach((p) => p.resolve(result)); |
| 632 | 633 | ||
| 633 | // If no listeners, call global success handler | 634 | // Check if there are any listeners at execution time |
| 635 | const hasListeners = channel.listeners.size > 0; | ||
| 634 | if (!hasListeners) { | 636 | if (!hasListeners) { |
| 635 | const message = this.describeResult(args, result); | 637 | const message = this.describeResult(args, result); |
| 636 | if (message && this.#client.reportSuccess) { | 638 | if (message && this.#client.reportSuccess) { |
| ... | @@ -642,7 +644,8 @@ export class BlockingMutation< | ... | @@ -642,7 +644,8 @@ export class BlockingMutation< |
| 642 | // Reject all pending promises | 644 | // Reject all pending promises |
| 643 | pending.forEach((p) => p.reject(error)); | 645 | pending.forEach((p) => p.reject(error)); |
| 644 | 646 | ||
| 645 | // If no listeners, call global error handler | 647 | // Check if there are any listeners at execution time |
| 648 | const hasListeners = channel.listeners.size > 0; | ||
| 646 | if (!hasListeners) { | 649 | if (!hasListeners) { |
| 647 | const message = `Failed to ${this.describe(...args)}: ${ | 650 | const message = `Failed to ${this.describe(...args)}: ${ |
| 648 | errMessage(error) | 651 | errMessage(error) |