Skip to content

mutate doesn't update variables synchronously #9062

@mmkal

Description

@mmkal

Describe the bug

Hello! I looked for an existing issue and couldn't find so opening here.

I noticed a problem when trying to use myMutation.variables as the value prop in a textarea. My hope was that I could do that as a way to avoid synchronizing with a useState while using the mutation to send updates to the server. More or less this:

export function MyComponent() {
  const myMutation = useMutation({
    mutationFn: async (params: {text: string}) => {
      await new Promise(r => setTimeout(r, 500))
      return params.text.split(',')
    },
  })

  return <textarea
    value={myMutation.variables?.text || ''}
    onChange={ev => myMutation.mutate({text: ev.target.value})}
  />
}

This mostly works, but if you try to edit something at the beginning of the textarea, the cursor jumps to the end.

According to this blog this happens when the value prop is updated asynchronously: https://dev.to/kwirke/solving-caret-jumping-in-react-inputs-36ic

I made a demo, along with a workaround which wraps the mutation and uses useState to store a synchronous copy of variables: - on the left, the cursor "jumps" to the end when you type at the start of the textarea.

Your minimal, reproducible example

https://v0.dev/chat/react-query-demo-ec5BxFS85Rs?f=1

Steps to reproduce

  1. Go to the link above
  2. Type stuff in both boxes
  3. Move the cursor to the start and keep typing. It jumps to the end on the left, but not on the right

Expected behavior

I'd expect the cursor not to jump around.

The following helper can wrap a mutation at make it work that way:

function useMutationWithLocalVariables<Mutation extends { mutate: (args: any) => void; variables: any }>(
  mutation: Mutation,
): Mutation {
  const { variables: _v, mutate: _m, ...rest } = mutation
  const [variables, setVariables] = useState<typeof _v>()
  const mutate = useCallback<typeof _m>(
    mutateArgs => {
      setVariables(mutateArgs)
      return void _m(mutateArgs)
    },
    [_m, setVariables],
  )
  return { ...rest, mutate, variables } as Mutation
}

Usage

const myMutation = useMutationWithLocalVariables(useMutation(...))

How often does this bug happen?

Every time

Screenshots or Videos

No response

Platform

  • any platform

Tanstack Query adapter

None

TanStack Query version

5.74.3

TypeScript version

5

Additional context

No response

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions