Skip to content

Commit

Permalink
Promisify action creator return type for WP data dispatch (#52530)
Browse files Browse the repository at this point in the history
* Promisify action creators return type
* Handle async/thunks in action creator return types
* Update plain dispatch function to handle string store descriptor
  • Loading branch information
noahtallen committed Jul 20, 2023
1 parent 6b1a268 commit 2f90b24
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 20 deletions.
13 changes: 9 additions & 4 deletions packages/data/CHANGELOG.md
Expand Up @@ -2,6 +2,11 @@

## Unreleased

### Bug Fix

- Update the type definitions for dispatched actions by accounting for Promisified return values and thunks. Previously, a dispatched action's return type was the same as the return type of the original action creator, which did not account for how dispatch works internally. (Plain actions get wrapped in a Promise, and thunk actions ultimately resolve to the innermost function's return type).
- Update the type definition for dispatch() to handle string store descriptors correctly.

## 9.8.0 (2023-07-20)

## 9.7.0 (2023-07-05)
Expand Down Expand Up @@ -62,7 +67,7 @@

### Breaking Changes

Add TypeScript types to the built package (via "types": "build-types" in the package.json)
– Add TypeScript types to the built package (via "types": "build-types" in the package.json)

### Bug Fix

Expand Down Expand Up @@ -100,9 +105,9 @@

### New Features

- Enabled thunks by default for all stores and removed the `__experimentalUseThunks` flag.
- Store the resolution errors in store metadata and expose them using `hasResolutionFailed` the `getResolutionError` meta-selectors ([#38669](https://github.com/WordPress/gutenberg/pull/38669)).
- Expose the resolution status (undefined, resolving, finished, error) via the `getResolutionState` meta-selector ([#38669](https://github.com/WordPress/gutenberg/pull/38669)).
- Enabled thunks by default for all stores and removed the `__experimentalUseThunks` flag.
- Store the resolution errors in store metadata and expose them using `hasResolutionFailed` the `getResolutionError` meta-selectors ([#38669](https://github.com/WordPress/gutenberg/pull/38669)).
- Expose the resolution status (undefined, resolving, finished, error) via the `getResolutionState` meta-selector ([#38669](https://github.com/WordPress/gutenberg/pull/38669)).

## 6.2.1 (2022-02-10)

Expand Down
4 changes: 2 additions & 2 deletions packages/data/README.md
Expand Up @@ -499,11 +499,11 @@ dispatch( myCustomStore ).setPrice( 'hammer', 9.75 );

_Parameters_

- _storeNameOrDescriptor_ `string | T`: The store descriptor. The legacy calling convention of passing the store name is also supported.
- _storeNameOrDescriptor_ `StoreNameOrDescriptor`: The store descriptor. The legacy calling convention of passing the store name is also supported.

_Returns_

- `ActionCreatorsOf< ConfigOf< T > >`: Object containing the action creators.
- `DispatchReturn< StoreNameOrDescriptor >`: Object containing the action creators.

### plugins

Expand Down
15 changes: 6 additions & 9 deletions packages/data/src/dispatch.ts
@@ -1,12 +1,7 @@
/**
* Internal dependencies
*/
import type {
ActionCreatorsOf,
AnyConfig,
ConfigOf,
StoreDescriptor,
} from './types';
import type { AnyConfig, StoreDescriptor, DispatchReturn } from './types';
import defaultRegistry from './default-registry';

/**
Expand All @@ -28,8 +23,10 @@ import defaultRegistry from './default-registry';
* ```
* @return Object containing the action creators.
*/
export function dispatch< T extends StoreDescriptor< AnyConfig > >(
storeNameOrDescriptor: string | T
): ActionCreatorsOf< ConfigOf< T > > {
export function dispatch<
StoreNameOrDescriptor extends StoreDescriptor< AnyConfig > | string
>(
storeNameOrDescriptor: StoreNameOrDescriptor
): DispatchReturn< StoreNameOrDescriptor > {
return defaultRegistry.dispatch( storeNameOrDescriptor );
}
43 changes: 38 additions & 5 deletions packages/data/src/types.ts
Expand Up @@ -6,7 +6,7 @@ import type { combineReducers as reduxCombineReducers } from 'redux';

type MapOf< T > = { [ name: string ]: T };

export type ActionCreator = Function | Generator;
export type ActionCreator = ( ...args: any[] ) => any | Generator;
export type Resolver = Function | Generator;
export type Selector = Function;

Expand Down Expand Up @@ -43,13 +43,15 @@ export interface ReduxStoreConfig<
controls?: MapOf< Function >;
}

// Return type for the useSelect() hook.
export type UseSelectReturn< F extends MapSelect | StoreDescriptor< any > > =
F extends MapSelect
? ReturnType< F >
: F extends StoreDescriptor< any >
? CurriedSelectorsOf< F >
: never;

// Return type for the useDispatch() hook.
export type UseDispatchReturn< StoreNameOrDescriptor > =
StoreNameOrDescriptor extends StoreDescriptor< any >
? ActionCreatorsOf< ConfigOf< StoreNameOrDescriptor > >
Expand All @@ -59,9 +61,12 @@ export type UseDispatchReturn< StoreNameOrDescriptor > =

export type DispatchFunction = < StoreNameOrDescriptor >(
store: StoreNameOrDescriptor
) => StoreNameOrDescriptor extends StoreDescriptor< any >
? ActionCreatorsOf< ConfigOf< StoreNameOrDescriptor > >
: any;
) => DispatchReturn< StoreNameOrDescriptor >;

export type DispatchReturn< StoreNameOrDescriptor > =
StoreNameOrDescriptor extends StoreDescriptor< any >
? ActionCreatorsOf< ConfigOf< StoreNameOrDescriptor > >
: unknown;

export type MapSelect = (
select: SelectFunction,
Expand Down Expand Up @@ -170,9 +175,37 @@ export type ConfigOf< S > = S extends StoreDescriptor< infer C > ? C : never;

export type ActionCreatorsOf< Config extends AnyConfig > =
Config extends ReduxStoreConfig< any, infer ActionCreators, any >
? ActionCreators
? PromisifiedActionCreators< ActionCreators >
: never;

// Takes an object containing all action creators for a store and updates the
// return type of each action creator to account for internal registry details --
// for example, dispatched actions are wrapped with a Promise.
export type PromisifiedActionCreators<
ActionCreators extends MapOf< ActionCreator >
> = {
[ Action in keyof ActionCreators ]: PromisifyActionCreator<
ActionCreators[ Action ]
>;
};

// Wraps action creator return types with a Promise and handles thunks.
export type PromisifyActionCreator< Action extends ActionCreator > = (
...args: Parameters< Action >
) => Promise<
ReturnType< Action > extends ( ..._args: any[] ) => any
? ThunkReturnType< Action >
: ReturnType< Action >
>;

// A thunk is an action creator which returns a function, which can optionally
// return a Promise. The double ReturnType unwraps the innermost function's
// return type, and Awaited gets the type the Promise resolves to. If the return
// type is not a Promise, Awaited returns that original type.
export type ThunkReturnType< Action extends ActionCreator > = Awaited<
ReturnType< ReturnType< Action > >
>;

type SelectorsOf< Config extends AnyConfig > = Config extends ReduxStoreConfig<
any,
any,
Expand Down

0 comments on commit 2f90b24

Please sign in to comment.