React-apollo: MutationFunc Typescript inconsistencies

Created on 8 Jun 2018  路  7Comments  路  Source: apollographql/react-apollo

Previously, I defined mutations as:

import { Mutation, MutationFn } from 'react-apollo'
interface ContainerProps {
  updateSaveStatus: MutationFn<updateSaveStatusMutation, updateSaveStatusMutationVariables>
}

class UpdateSaveStatusMutation extends Mutation<
  updateSaveStatusMutation,
  updateSaveStatusMutationVariables
> {}

render() {
  <UpdateSaveSatusMutation mutation={UPDATE_SAVE_STATUS}>
      {(updateSaveStatus) => (
         <Container updateSaveStatus={updateSaveStatus} />
      )}
  </UpdateSaveStatusMutation>

and all was good. However, it appears that MutationFn was recently removed (perhaps in response to this: https://github.com/apollographql/react-apollo/issues/1975 ?).

MutationFunc would seem to be the correct replacement, however it does not seem to be compatible with the child of the mutation function:

Types of property 'updateSaveStatus' are incompatible.
  Type '(options?: MutationOptions<updateSaveStatusMutation, updateSaveStatusMutationVariables> | undefin...' is not assignable to type 'MutationFunc<updateSaveStatusMutation, updateSaveStatusMutationVariables>'.
    Type 'Promise<void | FetchResult<Record<string, any>, Record<string, any>>>' is not assignable to type 'Promise<ApolloQueryResult<updateSaveStatusMutation>>'.
      Type 'void | FetchResult<Record<string, any>, Record<string, any>>' is not assignable to type 'ApolloQueryResult<updateSaveStatusMutation>'.
        Type 'void' is not assignable to type 'ApolloQueryResult<updateSaveStatusMutation>'.

It appears that what used to be called MutationFn is now defined implicitly as:

children: (mutateFn: (options?: MutationOptions<TData, TVariables>) => Promise<void | FetchResult>, result: MutationResult<TData>) => React.ReactNode

What is the new correct type to give to the children of a Mutation component? If there isn't one, can we have MutationFn back? Or, even better, make the types consistent (the possible void in MutationFn is superfluous from what I can tell and it would be good for it to go)?

Version

Most helpful comment

I'm confused: When can a MutationFn return a promise that is resolved with void?

This forces me to do things like this:

  private handleFormSubmit = async (
    login: MutationFn<ILoginMutation, ILoginMutationVariables>,
    values: ILoginFormValues,
    actions: FormikActions<ILoginFormValues>
  ) => {
    try {
      const result = await login({ variables: values })
      if (result) {
        localStorage.setItem(AUTH_TOKEN, result.data!.login.token)
        this.props.history.push('/')
      } else {
        // WTF. Help plx! When can this happen? What do i do?!?
      }
    } catch (error) {
      handleSubmitError(error, actions)
    }
  }

All 7 comments

I'm confused: When can a MutationFn return a promise that is resolved with void?

This forces me to do things like this:

  private handleFormSubmit = async (
    login: MutationFn<ILoginMutation, ILoginMutationVariables>,
    values: ILoginFormValues,
    actions: FormikActions<ILoginFormValues>
  ) => {
    try {
      const result = await login({ variables: values })
      if (result) {
        localStorage.setItem(AUTH_TOKEN, result.data!.login.token)
        this.props.history.push('/')
      } else {
        // WTF. Help plx! When can this happen? What do i do?!?
      }
    } catch (error) {
      handleSubmitError(error, actions)
    }
  }

+1 for void question

@codepunkt Did you find out a solution?

@sublimeye #2095 it seems it cannot be void if onError is not provided.

it remains a bug IMO since the typing is not optimal. However, we rewrite our MutationFn without void since we don't use onError

@eltonio450 mind sharing your new MutationFn implementation in a gist? Running into this exact issue today.

Ok, honestly not proud of our code, but we basically changed the type to OvrseaMutationFn

// we deleted the void: https://github.com/apollographql/react-apollo/issues/2095 (resp: Sinane)
export type OvrseaMutationFn<DataType = any, Variables = OperationVariables> = (options?: MutationOptions<DataType, Variables>) => Promise<FetchResult<DataType>>;

export type InjectedMutationResults<Name extends string, DataType> = {
  [x in Name]: {
    mutation: OvrseaMutationFn<DataType>,
    result: MutationResult<DataType>,
  }
}

Then, we use 'as' to type our injected elements in our HOC:

<Mutation
        {...config}
        context={context}
        optimisticResponse={optimisticResponse} //can be overwritten by mutate
      >
        {(mutation: MutationFn<DataType, Variables>, result: MutationResult<DataType>) => {

          const injectedElements = {
            [config.name as Name]: {
              mutation,
              result,
            },
          } as InjectedMutationResults<Name, DataType>;

          return (
            <WrappedComponent
              {...this.props}
              {...injectedElements}
            />
          );
        }
      }
      </Mutation>

Let me know if it's not clear. This part is not stable yet, so the whole file is still messy (+ we are waiting for the React hooks to really deep dive this topic). If you are really interested, I can also post it here. We kinda rebuild a HOC for queries from the Mutation component bc apollo is pushing for the Mutation component and doc of connected comp disappears (https://github.com/apollographql/apollo-client/issues/3253)

@eltonio450 thanks a lot for sharing this.

It's a shame it's such a mess to avoid that - I think I'll just have to do data! and trust that it's not void on the mutation promise result until the types are updated.

Closing as duplicate of #2095. The discussion there describes the issue more clearly.

Was this page helpful?
0 / 5 - 0 ratings