Skip to content

Commit d2225c4

Browse files
committed
update README for Optimistic Updates
1 parent 614aa26 commit d2225c4

File tree

1 file changed

+167
-2
lines changed

1 file changed

+167
-2
lines changed

README.md

Lines changed: 167 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,24 @@ const Posts = () => {
340340

341341
> Unlike queries, mutations are typically used to create/update/delete data or perform server side-effects.
342342
343+
`atomWithMutation` supports all options from TanStack Query's [`useMutation`](https://tanstack.com/query/v5/docs/react/reference/useMutation), including:
344+
- `mutationKey` - A unique key for the mutation
345+
- `mutationFn` - The function that performs the mutation
346+
- `onMutate` - Called before the mutation is executed (useful for optimistic updates)
347+
- `onSuccess` - Called when the mutation succeeds
348+
- `onError` - Called when the mutation fails
349+
- `onSettled` - Called when the mutation is settled (either success or error)
350+
- `retry` - Number of retry attempts
351+
- `retryDelay` - Delay between retries
352+
- `gcTime` - Time until inactive mutations are garbage collected
353+
- And all other [MutationOptions](https://tanstack.com/query/v5/docs/react/reference/useMutation#options)
354+
355+
#### Basic usage
356+
343357
```tsx
358+
import { useAtom } from 'jotai/react'
359+
import { atomWithMutation } from 'jotai-tanstack-query'
360+
344361
const postAtom = atomWithMutation(() => ({
345362
mutationKey: ['posts'],
346363
mutationFn: async ({ title }: { title: string }) => {
@@ -361,16 +378,164 @@ const postAtom = atomWithMutation(() => ({
361378
}))
362379

363380
const Posts = () => {
364-
const [{ mutate, status }] = useAtom(postAtom)
381+
const [{ mutate, isPending, status }] = useAtom(postAtom)
365382
return (
366383
<div>
367-
<button onClick={() => mutate({ title: 'foo' })}>Click me</button>
384+
<button onClick={() => mutate({ title: 'foo' })} disabled={isPending}>
385+
{isPending ? 'Creating...' : 'Create Post'}
386+
</button>
368387
<pre>{JSON.stringify(status, null, 2)}</pre>
369388
</div>
370389
)
371390
}
372391
```
373392

393+
#### Optimistic Updates
394+
395+
`atomWithMutation` fully supports optimistic updates through the `onMutate`, `onError`, and `onSettled` callbacks. This allows you to update the UI immediately before the server responds, and roll back if the mutation fails.
396+
397+
```tsx
398+
import { Getter } from 'jotai'
399+
import { useAtom } from 'jotai/react'
400+
import { atomWithMutation, atomWithQuery, queryClientAtom } from 'jotai-tanstack-query'
401+
402+
interface Post {
403+
id: number
404+
title: string
405+
body: string
406+
userId: number
407+
}
408+
409+
interface NewPost {
410+
title: string
411+
}
412+
413+
interface OptimisticContext {
414+
previousPosts: Post[] | undefined
415+
}
416+
417+
// Query to fetch posts list
418+
const postsQueryAtom = atomWithQuery(() => ({
419+
queryKey: ['posts'],
420+
queryFn: async () => {
421+
const res = await fetch('https://jsonplaceholder.typicode.com/posts?_limit=5')
422+
return res.json() as Promise<Post[]>
423+
},
424+
}))
425+
426+
// Mutation with optimistic updates
427+
const postAtom = atomWithMutation<Post, NewPost, Error, OptimisticContext>(
428+
(get) => {
429+
const queryClient = get(queryClientAtom)
430+
return {
431+
mutationKey: ['addPost'],
432+
mutationFn: async ({ title }: NewPost) => {
433+
const res = await fetch(`https://jsonplaceholder.typicode.com/posts`, {
434+
method: 'POST',
435+
body: JSON.stringify({
436+
title,
437+
body: 'body',
438+
userId: 1,
439+
}),
440+
headers: {
441+
'Content-type': 'application/json; charset=UTF-8',
442+
},
443+
})
444+
const data = await res.json()
445+
return data as Post
446+
},
447+
// When mutate is called:
448+
onMutate: async (newPost: NewPost) => {
449+
// Cancel any outgoing refetches
450+
// (so they don't overwrite our optimistic update)
451+
await queryClient.cancelQueries({ queryKey: ['posts'] })
452+
453+
// Snapshot the previous value
454+
const previousPosts = queryClient.getQueryData<Post[]>(['posts'])
455+
456+
// Optimistically update to the new value
457+
queryClient.setQueryData<Post[]>(['posts'], (old) => {
458+
const optimisticPost: Post = {
459+
id: Date.now(), // Temporary ID
460+
title: newPost.title,
461+
body: 'body',
462+
userId: 1,
463+
}
464+
return old ? [...old, optimisticPost] : [optimisticPost]
465+
})
466+
467+
// Return a result with the snapshotted value
468+
return { previousPosts }
469+
},
470+
// If the mutation fails, use the result returned from onMutate to roll back
471+
onError: (
472+
_err: Error,
473+
_newPost: NewPost,
474+
onMutateResult: OptimisticContext | undefined
475+
) => {
476+
if (onMutateResult?.previousPosts) {
477+
queryClient.setQueryData(['posts'], onMutateResult.previousPosts)
478+
}
479+
},
480+
// Always refetch after error or success:
481+
onSettled: (
482+
_data: Post | undefined,
483+
_error: Error | null,
484+
_variables: NewPost,
485+
_onMutateResult: OptimisticContext | undefined
486+
) => {
487+
queryClient.invalidateQueries({ queryKey: ['posts'] })
488+
},
489+
}
490+
}
491+
)
492+
493+
const PostsList = () => {
494+
const [{ data: posts, isPending }] = useAtom(postsQueryAtom)
495+
496+
if (isPending) return <div>Loading posts...</div>
497+
498+
return (
499+
<div>
500+
<h3>Posts:</h3>
501+
<ul>
502+
{posts?.map((post: Post) => (
503+
<li key={post.id}>{post.title}</li>
504+
))}
505+
</ul>
506+
</div>
507+
)
508+
}
509+
510+
const AddPost = () => {
511+
const [{ mutate, isPending }] = useAtom(postAtom)
512+
const [title, setTitle] = React.useState('')
513+
514+
return (
515+
<div>
516+
<input
517+
value={title}
518+
onChange={(e) => setTitle(e.target.value)}
519+
placeholder="Enter post title"
520+
/>
521+
<button
522+
onClick={() => {
523+
if (title) {
524+
mutate({ title })
525+
setTitle('')
526+
}
527+
}}
528+
disabled={isPending}
529+
>
530+
{isPending ? 'Adding...' : 'Add Post'}
531+
</button>
532+
</div>
533+
)
534+
}
535+
```
536+
537+
For more details on optimistic updates, see the [TanStack Query Optimistic Updates guide](https://tanstack.com/query/v5/docs/framework/react/guides/optimistic-updates).
538+
374539
### atomWithMutationState usage
375540

376541
`atomWithMutationState` creates a new atom that gives you access to all mutations in the [`MutationCache`](https://tanstack.com/query/v5/docs/react/reference/useMutationState).

0 commit comments

Comments
 (0)