diff --git a/Changelog.md b/Changelog.md index 7188b40123..e5c0d8b9cd 100644 --- a/Changelog.md +++ b/Changelog.md @@ -10,6 +10,9 @@ - Adjust the `ApolloContext` definition to play a bit more nicely with `React.createContext` types.
[@JoviDeCroock](https://github.com/JoviDeCroock) in [#3018](https://github.com/apollographql/react-apollo/pull/3018) +- The result of a mutation is now made available to the wrapped component, + when using the `graphql` HOC.
+ [@andycarrell](https://github.com/andycarrell) in [#3008](https://github.com/apollographql/react-apollo/pull/3008) ### Bug Fixes diff --git a/src/mutation-hoc.tsx b/src/mutation-hoc.tsx index 6726b5cfa7..aaf73d0c7b 100644 --- a/src/mutation-hoc.tsx +++ b/src/mutation-hoc.tsx @@ -56,12 +56,19 @@ export function withMutation< return ( - {(mutate, _result) => { + {(mutate, { data, ...r }) => { + // the HOC's historically hoisted the data from the execution result + // up onto the result since it was passed as a nested prop + // we massage the Mutation component's shape here to replicate that + // this matches the query HoC + const result = Object.assign(r, data || {}); const name = operationOptions.name || 'mutate'; - let childProps = { [name]: mutate }; + const resultName = operationOptions.name ? `${name}Result` : 'result'; + let childProps = { [name]: mutate, [resultName]: result }; if (operationOptions.props) { const newResult: OptionProps = { [name]: mutate, + [resultName]: result, ownProps: props, }; childProps = operationOptions.props(newResult) as any; @@ -70,7 +77,7 @@ export function withMutation< return ( ); }} diff --git a/src/types.ts b/src/types.ts index a8b1d3e1b3..2d20e989a1 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,146 +1,147 @@ -import ApolloClient, { - ApolloQueryResult, - ApolloError, - FetchPolicy, - WatchQueryFetchPolicy, - ErrorPolicy, - FetchMoreOptions, - UpdateQueryOptions, - FetchMoreQueryOptions, - SubscribeToMoreOptions, - PureQueryOptions, - MutationUpdaterFn, -} from 'apollo-client'; -import { MutationFn } from './Mutation'; - -export interface Context { - [key: string]: any; -}; - -export type OperationVariables = { - [key: string]: any; -}; - -/** - * Function which returns an array of query names or query objects for refetchQueries option. - * Allows conditional refetches. - */ -export type RefetchQueriesProviderFn = (...args: any[]) => Array; - -export interface MutationOpts { - variables?: TGraphQLVariables; - optimisticResponse?: TData; - refetchQueries?: Array | RefetchQueriesProviderFn; - awaitRefetchQueries?: boolean; - errorPolicy?: ErrorPolicy; - update?: MutationUpdaterFn; - client?: ApolloClient; - notifyOnNetworkStatusChange?: boolean; - context?: Context; - onCompleted?: (data: TData) => void; - onError?: (error: ApolloError) => void; - fetchPolicy?: FetchPolicy; -} - -export interface QueryOpts { - ssr?: boolean; - variables?: TGraphQLVariables; - fetchPolicy?: WatchQueryFetchPolicy; - errorPolicy?: ErrorPolicy; - pollInterval?: number; - client?: ApolloClient; - notifyOnNetworkStatusChange?: boolean; - context?: Context; - partialRefetch?: boolean; - returnPartialData?: boolean; -} - -export interface QueryControls { - error?: ApolloError; - networkStatus: number; - loading: boolean; - variables: TGraphQLVariables; - fetchMore: ( - fetchMoreOptions: FetchMoreQueryOptions & - FetchMoreOptions, - ) => Promise>; - refetch: (variables?: TGraphQLVariables) => Promise>; - startPolling: (pollInterval: number) => void; - stopPolling: () => void; - subscribeToMore: (options: SubscribeToMoreOptions) => () => void; - updateQuery: (mapFn: (previousQueryResult: any, options: UpdateQueryOptions) => any) => void; -} - -// XXX remove in the next breaking semver change (3.0) -export interface GraphqlQueryControls - extends QueryControls {} - -// XXX remove in the next breaking semver change (3.0) -export type MutationFunc = MutationFn< - TData, - TVariables ->; - -export type DataValue = QueryControls< - TData, - TGraphQLVariables -> & - // data may not yet be loaded - Partial; - -// export to allow usage individually for simple components -export interface DataProps { - data: DataValue; -} - -// export to allow usage individually for simple components -export interface MutateProps { - mutate: MutationFn; -} - -export type ChildProps = TProps & - Partial> & - Partial>; - -export type ChildDataProps< - TProps = {}, - TData = {}, - TGraphQLVariables = OperationVariables -> = TProps & DataProps; - -export type ChildMutateProps< - TProps = {}, - TData = {}, - TGraphQLVariables = OperationVariables -> = TProps & MutateProps; - -export type NamedProps = TProps & { - ownProps: R; -}; - -export interface OptionProps - extends Partial>, - Partial> { - ownProps: TProps; -} - -export interface OperationOption< - TProps, - TData, - TGraphQLVariables = OperationVariables, - TChildProps = ChildProps -> { - options?: - | QueryOpts - | MutationOpts - | ((props: TProps) => QueryOpts | MutationOpts); - props?: ( - props: OptionProps, - lastProps?: TChildProps | void, - ) => TChildProps; - skip?: boolean | ((props: TProps) => boolean); - name?: string; - withRef?: boolean; - shouldResubscribe?: (props: TProps, nextProps: TProps) => boolean; - alias?: string; -} +import ApolloClient, { + ApolloQueryResult, + ApolloError, + FetchPolicy, + WatchQueryFetchPolicy, + ErrorPolicy, + FetchMoreOptions, + UpdateQueryOptions, + FetchMoreQueryOptions, + SubscribeToMoreOptions, + PureQueryOptions, + MutationUpdaterFn, +} from 'apollo-client'; +import { MutationFn, MutationResult } from './Mutation'; + +export interface Context { + [key: string]: any; +}; + +export type OperationVariables = { + [key: string]: any; +}; + +/** + * Function which returns an array of query names or query objects for refetchQueries option. + * Allows conditional refetches. + */ +export type RefetchQueriesProviderFn = (...args: any[]) => Array; + +export interface MutationOpts { + variables?: TGraphQLVariables; + optimisticResponse?: TData; + refetchQueries?: Array | RefetchQueriesProviderFn; + awaitRefetchQueries?: boolean; + errorPolicy?: ErrorPolicy; + update?: MutationUpdaterFn; + client?: ApolloClient; + notifyOnNetworkStatusChange?: boolean; + context?: Context; + onCompleted?: (data: TData) => void; + onError?: (error: ApolloError) => void; + fetchPolicy?: FetchPolicy; +} + +export interface QueryOpts { + ssr?: boolean; + variables?: TGraphQLVariables; + fetchPolicy?: WatchQueryFetchPolicy; + errorPolicy?: ErrorPolicy; + pollInterval?: number; + client?: ApolloClient; + notifyOnNetworkStatusChange?: boolean; + context?: Context; + partialRefetch?: boolean; + returnPartialData?: boolean; +} + +export interface QueryControls { + error?: ApolloError; + networkStatus: number; + loading: boolean; + variables: TGraphQLVariables; + fetchMore: ( + fetchMoreOptions: FetchMoreQueryOptions & + FetchMoreOptions, + ) => Promise>; + refetch: (variables?: TGraphQLVariables) => Promise>; + startPolling: (pollInterval: number) => void; + stopPolling: () => void; + subscribeToMore: (options: SubscribeToMoreOptions) => () => void; + updateQuery: (mapFn: (previousQueryResult: any, options: UpdateQueryOptions) => any) => void; +} + +// XXX remove in the next breaking semver change (3.0) +export interface GraphqlQueryControls + extends QueryControls {} + +// XXX remove in the next breaking semver change (3.0) +export type MutationFunc = MutationFn< + TData, + TVariables +>; + +export type DataValue = QueryControls< + TData, + TGraphQLVariables +> & + // data may not yet be loaded + Partial; + +// export to allow usage individually for simple components +export interface DataProps { + data: DataValue; +} + +// export to allow usage individually for simple components +export interface MutateProps { + mutate: MutationFn; + result: MutationResult; +} + +export type ChildProps = TProps & + Partial> & + Partial>; + +export type ChildDataProps< + TProps = {}, + TData = {}, + TGraphQLVariables = OperationVariables +> = TProps & DataProps; + +export type ChildMutateProps< + TProps = {}, + TData = {}, + TGraphQLVariables = OperationVariables +> = TProps & MutateProps; + +export type NamedProps = TProps & { + ownProps: R; +}; + +export interface OptionProps + extends Partial>, + Partial> { + ownProps: TProps; +} + +export interface OperationOption< + TProps, + TData, + TGraphQLVariables = OperationVariables, + TChildProps = ChildProps +> { + options?: + | QueryOpts + | MutationOpts + | ((props: TProps) => QueryOpts | MutationOpts); + props?: ( + props: OptionProps, + lastProps?: TChildProps | void, + ) => TChildProps; + skip?: boolean | ((props: TProps) => boolean); + name?: string; + withRef?: boolean; + shouldResubscribe?: (props: TProps, nextProps: TProps) => boolean; + alias?: string; +} diff --git a/test/client/graphql/mutations/index.test.tsx b/test/client/graphql/mutations/index.test.tsx index b29a4c2c6e..be1c34fa06 100644 --- a/test/client/graphql/mutations/index.test.tsx +++ b/test/client/graphql/mutations/index.test.tsx @@ -45,9 +45,55 @@ describe('graphql(mutation)', () => { }); it('binds a mutation to props', () => { - const ContainerWithData = graphql(query)(({ mutate }) => { + const ContainerWithData = graphql(query)(({ mutate, result }) => { expect(mutate).toBeTruthy(); + expect(result).toBeTruthy(); expect(typeof mutate).toBe('function'); + expect(typeof result).toBe('object'); + return null; + }); + + renderer.create( + + + , + ); + }); + + it('binds a mutation result to props', () => { + type InjectedProps = { + result: any; + }; + + const ContainerWithData = graphql<{}, Data, Variables, InjectedProps>(query)(({ result }) => { + const { loading, error } = result; + expect(result).toBeTruthy(); + expect(typeof loading).toBe('boolean'); + expect(error).toBeFalsy(); + + return null; + }); + + renderer.create( + + + , + ); + }); + + it('binds a mutation to props with a custom name', () => { + interface Props {}; + + type InjectedProps = { + customMutation: any; + customMutationResult: any; + }; + + const ContainerWithData = graphql(query, { name: 'customMutation' })(({ customMutation, customMutationResult }) => { + expect(customMutation).toBeTruthy(); + expect(customMutationResult).toBeTruthy(); + expect(typeof customMutation).toBe('function'); + expect(typeof customMutationResult).toBe('object'); return null; });