Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

useMutation types: variables are optional #1077

Closed
TkDodo opened this issue Sep 22, 2020 · 15 comments
Closed

useMutation types: variables are optional #1077

TkDodo opened this issue Sep 22, 2020 · 15 comments
Labels

Comments

@TkDodo
Copy link
Collaborator

TkDodo commented Sep 22, 2020

Describe the bug

in TypeScript, the mutation function created by the useMutation hook can always be called without passing arguments, because the type TVariables is set to optional:

https://github.com/tannerlinsley/react-query/blob/00b9e968a80ca7c51fd50479aee852bb41bf9bc6/src/react/useMutation.ts#L132

To Reproduce

I made a codeSandbox example: https://codesandbox.io/s/async-currying-wepni

Please look at the comment at line 54

I should be forced to call that function like this:

mutatePostTodo(text)

but I can also call it like this:

mutatePostTodo()

Expected behavior

The type of variables that you need to pass to that function should be equal to the type that the actual mutation function accepts, in other words, it should be variables: TVariables rather than variables?: TVariables

Desktop (please complete the following information):

  • OS: MacOS Catalina
  • Browser: Chrome
  • Version: 85

Additional context

All the usages of variables in that function are accessed with the bang operator, for example:

https://github.com/tannerlinsley/react-query/blob/93db5b61246dcd381d699054de48fca685bef73b/src/react/useMutation.ts#L146

or https://github.com/tannerlinsley/react-query/blob/00b9e968a80ca7c51fd50479aee852bb41bf9bc6/src/react/useMutation.ts#L174

So I don't really see why it should be optional to begin with 🤷 .

Happy to make a PR if you approve of this :)

@tannerlinsley
Copy link
Collaborator

Yeah a PR would be great. Be sure to include some tests too? :)

@TkDodo
Copy link
Collaborator Author

TkDodo commented Sep 22, 2020

Do you have tests of the type definitions in mind, e.g. with https://github.com/Microsoft/dtslint ?

@boschni
Copy link
Collaborator

boschni commented Sep 23, 2020

Think we'll just need to set it to required in V3. Doing so will prevent possible bugs, at the expense of having to call the function as mutate(undefined) when the mutation does not have any variables.

@TkDodo
Copy link
Collaborator Author

TkDodo commented Sep 23, 2020

@boschni right, i didn’t think about mutations with no variables... I’ve never done that, but it’s still a legit case.

I think this TS issue is on topic, but not sure if any of the provided workarounds to make a parameter optional if its undefined would help us here: microsoft/TypeScript#12400

I agree that that would be a breaking change and we would thus need to wait for v3

@TkDodo
Copy link
Collaborator Author

TkDodo commented Sep 23, 2020

I just found this discussion on the topic: #991 (comment)

Sorry for the duplicate. @tannerlinsley mentioned that type changes are not considered as “breaking”, so we could also just go ahead with making variables mandatory in v2?

@tannerlinsley
Copy link
Collaborator

I would be okay implementing this in v2 as non-breaking, but I'll leave that up to @boschni since he knows more about the type implications. Otherwise, expect it in v3.

@boschni
Copy link
Collaborator

boschni commented Sep 30, 2020

The typing issue has been fixed in V3

@boschni boschni closed this as completed Oct 6, 2020
@qoalu
Copy link

qoalu commented Dec 10, 2021

I am having an issue with the required parameter case.
I have a mutation function that has parameters which are all optional and there are defaults.

When I have no parameters typescript does not complain, as well as when I pass the parameters, however calling the function without parameters now causes an error

useMutation((param1?: string, param2?:string) => {})

image

@TkDodo
Copy link
Collaborator Author

TkDodo commented Dec 10, 2021

as you can see in the signature you posted, useMutation only takes one variables argument. If you want to pass multiple arguments, you have to use an object:

useMutation((variables: { param1?: string, param2?:string} | undefined) => ...)

@qoalu
Copy link

qoalu commented Dec 10, 2021

The above function also results in a typescript error when calling it without parameters

the variables parameter is required in the signature

Tried with an object as well:

useMutation(
     ({ param1, param2 }: { param1?: string, param2?: string } = {}) => {...}, 
     ... 
)

the call I am trying to do is without params

const { mutateAsync, isLoading } = useMutation();

mutateAsync()

@TkDodo
Copy link
Collaborator Author

TkDodo commented Dec 10, 2021

hmm this works fine for me, as variables are inferred to void:

      const { mutateAsync } = useMutation( () => Promise.resolve())
      mutateAsync()

if you have an optional parameter, you need to pass undefined at least I'm afraid, because that doesn't work:

      const { mutateAsync } = useMutation((foo?: string) =>
        Promise.resolve()
      )
      mutateAsync() //error here

but that does:

      const { mutateAsync } = useMutation((foo?: string) =>
        Promise.resolve()
      )
      mutateAsync(undefined)

@qoalu
Copy link

qoalu commented Dec 10, 2021

i see, I am passing an empty object as a workaround, but it does not feel elegant :)
thanks for the guidance here!

@andresgutgon
Copy link

This is what we do:

export function useMyThing () {
  const mutation = useMutation(...yourThing)
  const mutate = (variables: TVariables = {}) => mutation.mutate(variables)
  return { mutate, ...mutation }
}

This way when using this custom hook you don't need to pass variables if you don't want:

const { mutate } = useMyThing()
const onClick = () =>{  mutate() }

@soobing
Copy link

soobing commented Feb 15, 2023

This is what we do:

export function useMyThing () {
  const mutation = useMutation(...yourThing)
  const mutate = (variables: TVariables = {}) => mutation.mutate(variables)
  return { mutate, ...mutation }
}

This way when using this custom hook you don't need to pass variables if you don't want:

const { mutate } = useMyThing()
const onClick = () =>{  mutate() }

@andresgutgon I think { mutate, ...mutation } should be changed to { ...mutation, mutate }. Thanks anyway 😉

export function useMyThing () {
  const mutation = useMutation(...yourThing)
  const mutate = (variables: TVariables = {}) => mutation.mutate(variables)
  return { ...mutation, mutate }
}

@dalmocoop
Copy link

My workaround has been adding | void to the args type

      const { mutateAsync } = useMutation((foo?: string | void) =>
        Promise.resolve()
      )
      mutateAsync() //all good

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

7 participants