Skip to main content

mutations

Mutation render-prop component and mutationOptions for TanStack Query v4. Load when adding mutations inside list rows, loops, or conditionals where useMutation cannot be called, removing per-row wrapper components, or reusing mutation configs with mutationOptions.

소스 정보

저장소
toss/suspensive
최근 소스 활동
2026년 7월 4일 07:04
감지된 SKILL.md 언어
영어
스타
1,060
포크
100

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
name
mutations
description
Mutation render-prop component and mutationOptions for TanStack Query v4. Load when adding mutations inside list rows, loops, or conditionals where useMutation cannot be called, removing per-row wrapper components, or reusing mutation configs with mutationOptions.
metadata
{"type":"sub-skill","library":"@suspensive/react-query-4","library_version":"3.21.2","framework":"react"}
requires
["react-query"]
sources
["toss/suspensive:docs/suspensive.org/src/content/en/docs/react-query/Mutation.mdx","toss/suspensive:docs/suspensive.org/src/content/en/docs/react-query/mutationOptions.mdx","toss/suspensive:docs/suspensive.org/src/content/en/docs/react-query/migration/migrate-to-v3.mdx"]
This skill builds on react-query. Read ../SKILL.md first. `<Mutation/>` exposes a useMutation result as a render prop so each list row or conditional branch can own its mutation without extracting a wrapper component. The render prop receives the full v4 UseMutationResult (`mutate`, `mutateAsync`, `isLoading`, `isError`, `error`, `data`, `reset`). Note the v4 naming: the in-flight flag is `isLoading`, not v5's `isPending`. ## Setup ```bash npm install @suspensive/react-query-4 @tanstack/react-query@4 ``` Import `Mutation` from '@suspensive/react-query-4'; import `mutationOptions` from '@tanstack/react-query' (official since TSQ 4.44+, which the package's peer range requires; the Suspensive re-export is deprecated). ## Core Patterns ### One mutation per list row ```tsx 'use client' import { Mutation } from '@suspensive/react-query-4' import { useSuspenseQuery } from '@tanstack/react-query' import { api } from '~/api' export const PostsPage = () => { const { data: posts } = useSuspenseQuery({ queryKey: ['posts'], queryFn: () => api.getPosts(), }) return posts.map((post) => ( <Mutation key={post.id} mutationFn={({ content }: { content: string }) => api.editPost({ postId: post.id, content })} > {(postMutation) => ( <div> {postMutation.isLoading ? <Spinner /> : null} <div>{post.content}</div> <textarea disabled={postMutation.isLoading} onBlur={(e) => postMutation.mutate({ content: e.target.value })} /> </div> )} </Mutation> )) } ``` ### Reusable configs with mutationOptions ```tsx 'use client' import { Mutation } from '@suspensive/react-query-4' import { mutationOptions, useMutation } from '@tanstack/react-query' const editPostMutationOptions = (postId: number) => mutationOptions({ mutationFn: (content: string) => fetch(`/api/posts/${postId}`, { method: 'PATCH', body: JSON.stringify({ content }), }).then((res) => res.json()), }) // Same options work with the hook... const useEditPost = (postId: number) => useMutation(editPostMutationOptions(postId)) // ...and spread directly into the component export const EditPost = ({ postId }: { postId: number }) => ( <Mutation {...editPostMutationOptions(postId)}> {({ mutate, isLoading }) => ( <div> <p>{isLoading ? 'Updating...' : 'Latest updated'}</p> <textarea onBlur={(e) => mutate(e.target.value)} disabled={isLoading} /> </div> )} </Mutation> ) ``` ### Invalidating queries on success ```tsx 'use client' import { Mutation } from '@suspensive/react-query-4' import { useQueryClient } from '@tanstack/react-query' import { api } from '~/api' export const DeletePostButton = ({ postId }: { postId: number }) => { const queryClient = useQueryClient() return ( <Mutation mutationFn={() => api.deletePost(postId)} onSuccess={() => queryClient.invalidateQueries({ queryKey: ['posts'] })} > {({ mutate, isLoading }) => ( <button disabled={isLoading} onClick={() => mutate()}> {isLoading ? 'Deleting...' : 'Delete'} </button> )} </Mutation> ) } ``` ## Common Mistakes ### [MEDIUM] Wrapper component per row just for useMutation Wrong: ```tsx { posts.map((post) => <PostItemWithMutation key={post.id} post={post} />) } const PostItemWithMutation = ({ post }: { post: Post }) => { const deleteMutation = useMutation({ mutationFn: () => api.deletePost(post.id) }) return ( <button disabled={deleteMutation.isLoading} onClick={() => deleteMutation.mutate()}> delete </button> ) } ``` Correct: ```tsx { posts.map((post) => ( <Mutation key={post.id} mutationFn={() => api.deletePost(post.id)}> {({ mutate, isLoading }) => ( <button disabled={isLoading} onClick={() => mutate()}> delete </button> )} </Mutation> )) } ``` Hooks cannot be called in loops, so agents invent awkwardly named wrapper components; `<Mutation>` works inline per row with no extra depth. Source: docs/suspensive.org/src/content/en/docs/react-query/Mutation.mdx ### [MEDIUM] Using v5 isPending on v4 mutation results Wrong: ```tsx <Mutation mutationFn={() => api.deletePost(postId)}> {({ mutate, isPending }) => ( <button disabled={isPending} onClick={() => mutate()}> delete </button> )} </Mutation> ``` Correct: ```tsx <Mutation mutationFn={() => api.deletePost(postId)}> {({ mutate, isLoading }) => ( <button disabled={isLoading} onClick={() => mutate()}> delete </button> )} </Mutation> ``` The render prop receives TSQ v4's UseMutationResult, where the in-flight flag is `isLoading`; `isPending` only exists in TSQ v5 (and @suspensive/react-query-5). Source: docs/suspensive.org/src/content/en/docs/react-query/Mutation.mdx ### [MEDIUM] Importing mutationOptions from Suspensive Wrong: ```tsx import { mutationOptions } from '@suspensive/react-query-4' ``` Correct: ```tsx import { mutationOptions } from '@tanstack/react-query' ``` mutationOptions is official in TanStack Query since v4.44 (the package's peer range requires ^4.44.0); the Suspensive re-export exists only for compatibility and is deprecated. Source: docs/suspensive.org/src/content/en/docs/react-query/mutationOptions.mdx See also: ../declarative-queries/SKILL.md for the matching SuspenseQuery pattern, ../SKILL.md for which imports come from @tanstack/react-query vs Suspensive.
GitHub에서 보기