Skip to content

Commit

Permalink
Remove Webpack config and try test build (#324)
Browse files Browse the repository at this point in the history
Co-authored-by: Tim Dorr <git@timdorr.com>
  • Loading branch information
markerikson and timdorr authored Oct 25, 2021
1 parent 2289e99 commit 7fb5a23
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 96 deletions.
4 changes: 3 additions & 1 deletion .codesandbox/ci.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
{
"sandboxes": ["vanilla", "vanilla-ts"],
"node": "14"
"node": "14",
"buildCommand": "build",
"packages": ["."]
}
35 changes: 24 additions & 11 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,36 +9,49 @@ export type {
ThunkMiddleware
} from './types'

/** A function that accepts a potential "extra argument" value to be injected later,
* and returns an instance of the thunk middleware that uses that value
*/
function createThunkMiddleware<
TState = any,
TBasicAction extends Action = AnyAction,
TExtraThunkArg = undefined
>(extraArgument?: TExtraThunkArg) {
const middleware: ThunkMiddleware<TState, TBasicAction, TExtraThunkArg> =
State = any,
BasicAction extends Action = AnyAction,
ExtraThunkArg = undefined
>(extraArgument?: ExtraThunkArg) {
// Standard Redux middleware definition pattern:
// See: https://redux.js.org/tutorials/fundamentals/part-4-store#writing-custom-middleware
const middleware: ThunkMiddleware<State, BasicAction, ExtraThunkArg> =
({ dispatch, getState }) =>
next =>
action => {
// The thunk middleware looks for any functions that were passed to `store.dispatch`.
// If this "action" is really a function, call it and return the result.
if (typeof action === 'function') {
// Inject the store's `dispatch` and `getState` methods, as well as any "extra arg"
return action(dispatch, getState, extraArgument)
}

// Otherwise, pass the action down the middleware chain as usual
return next(action)
}
return middleware
}

/** The standard thunk middleware, with no extra argument included */
const thunk = createThunkMiddleware()
// Attach the factory function so users can create a customized version
// with whatever "extra arg" they want to inject into their thunks
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
// @ts-ignore
thunk.withExtraArgument = createThunkMiddleware

// Convince TS that the default export has the right type for `withExtraArgument`
export default thunk as typeof thunk &
ThunkMiddleware & {
withExtraArgument<
TExtraThunkArg,
TState = any,
TBasicAction extends Action<any> = AnyAction
ExtraThunkArg,
State = any,
BasicAction extends Action<any> = AnyAction
>(
extraArgument: TExtraThunkArg
): ThunkMiddleware<TState, TBasicAction, TExtraThunkArg>
extraArgument: ExtraThunkArg
): ThunkMiddleware<State, BasicAction, ExtraThunkArg>
}
92 changes: 48 additions & 44 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,35 @@
import { Action, AnyAction, Middleware } from 'redux'
import { Action, AnyAction, Middleware, Dispatch } from 'redux'

/**
* The dispatch method as modified by React-Thunk; overloaded so that you can
* dispatch:
* - standard (object) actions: `dispatch()` returns the action itself
* - thunk actions: `dispatch()` returns the thunk's return value
*
* @template TState The redux state
* @template TExtraThunkArg The extra argument passed to the inner function of
* @template State The redux state
* @template ExtraThunkArg The extra argument passed to the inner function of
* thunks (if specified when setting up the Thunk middleware)
* @template TBasicAction The (non-thunk) actions that can be dispatched.
* @template BasicAction The (non-thunk) actions that can be dispatched.
*/
export interface ThunkDispatch<
TState,
TExtraThunkArg,
TBasicAction extends Action
> {
<TReturnType>(
thunkAction: ThunkAction<TReturnType, TState, TExtraThunkArg, TBasicAction>
): TReturnType
<A extends TBasicAction>(action: A): A
// This overload is the union of the two above (see TS issue #14107).
<TReturnType, TAction extends TBasicAction>(
action:
| TAction
| ThunkAction<TReturnType, TState, TExtraThunkArg, TBasicAction>
): TAction | TReturnType
export interface ThunkDispatch<State, ExtraThunkArg, BasicAction extends Action>
extends Dispatch<BasicAction> {
// When the thunk middleware is added, `store.dispatch` now has three overloads:

// 1) The base overload, which accepts a standard action object, and returns that action object

// 2) The specific thunk function overload
/** Accepts a thunk function, runs it, and returns whatever the thunk itself returns */
<ReturnType>(
thunkAction: ThunkAction<ReturnType, State, ExtraThunkArg, BasicAction>
): ReturnType

// 3)
/** A union of the other two overloads. This overload exists to work around a problem
* with TS inference ( see https://github.com/microsoft/TypeScript/issues/14107 )
*/
<ReturnType, Action extends BasicAction>(
action: Action | ThunkAction<ReturnType, State, ExtraThunkArg, BasicAction>
): Action | ReturnType
}

/**
Expand All @@ -35,49 +39,49 @@ export interface ThunkDispatch<
* Also known as the "thunk inner function", when used with the typical pattern
* of an action creator function that returns a thunk action.
*
* @template TReturnType The return type of the thunk's inner function
* @template TState The redux state
* @template TExtraThunkARg Optional extra argument passed to the inner function
* @template ReturnType The return type of the thunk's inner function
* @template State The redux state
* @template ExtraThunkArg Optional extra argument passed to the inner function
* (if specified when setting up the Thunk middleware)
* @template TBasicAction The (non-thunk) actions that can be dispatched.
* @template BasicAction The (non-thunk) actions that can be dispatched.
*/
export type ThunkAction<
TReturnType,
TState,
TExtraThunkArg,
TBasicAction extends Action
ReturnType,
State,
ExtraThunkArg,
BasicAction extends Action
> = (
dispatch: ThunkDispatch<TState, TExtraThunkArg, TBasicAction>,
getState: () => TState,
extraArgument: TExtraThunkArg
) => TReturnType
dispatch: ThunkDispatch<State, ExtraThunkArg, BasicAction>,
getState: () => State,
extraArgument: ExtraThunkArg
) => ReturnType

/**
* A generic type that takes a thunk action creator and returns a function
* signature which matches how it would appear after being processed using
* bindActionCreators(): a function that takes the arguments of the outer
* function, and returns the return type of the inner "thunk" function.
*
* @template TActionCreator Thunk action creator to be wrapped
* @template ActionCreator Thunk action creator to be wrapped
*/
export type ThunkActionDispatch<
TActionCreator extends (...args: any[]) => ThunkAction<any, any, any, any>
ActionCreator extends (...args: any[]) => ThunkAction<any, any, any, any>
> = (
...args: Parameters<TActionCreator>
) => ReturnType<ReturnType<TActionCreator>>
...args: Parameters<ActionCreator>
) => ReturnType<ReturnType<ActionCreator>>

/**
* @template TState The redux state
* @template TBasicAction The (non-thunk) actions that can be dispatched
* @template TExtraThunkArg An optional extra argument to pass to a thunk's
* @template State The redux state
* @template BasicAction The (non-thunk) actions that can be dispatched
* @template ExtraThunkArg An optional extra argument to pass to a thunk's
* inner function. (Only used if you call `thunk.withExtraArgument()`)
*/
export type ThunkMiddleware<
TState = any,
TBasicAction extends Action = AnyAction,
TExtraThunkArg = undefined
State = any,
BasicAction extends Action = AnyAction,
ExtraThunkArg = undefined
> = Middleware<
ThunkDispatch<TState, TExtraThunkArg, TBasicAction>,
TState,
ThunkDispatch<TState, TExtraThunkArg, TBasicAction>
ThunkDispatch<State, ExtraThunkArg, BasicAction>,
State,
ThunkDispatch<State, ExtraThunkArg, BasicAction>
>
21 changes: 20 additions & 1 deletion typescript_test/typescript.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { applyMiddleware, bindActionCreators, createStore } from 'redux'
import {
applyMiddleware,
bindActionCreators,
createStore,
Action,
AnyAction
} from 'redux'

import thunk, {
ThunkAction,
Expand Down Expand Up @@ -71,6 +77,7 @@ const storeThunkArg = createStore(
thunk.withExtraArgument('bar') as ThunkMiddleware<State, Actions, string>
)
)
storeThunkArg.dispatch({ type: 'FOO' })

storeThunkArg.dispatch((dispatch, getState, extraArg) => {
const bar: string = extraArg
Expand Down Expand Up @@ -145,3 +152,15 @@ const untypedStore = createStore(fakeReducer, applyMiddleware(thunk))
untypedStore.dispatch(anotherThunkAction())
// @ts-expect-error
untypedStore.dispatch(promiseThunkAction()).then(() => Promise.resolve())

// #248: Need a union overload to handle generic dispatched types
function testIssue248() {
const dispatch: ThunkDispatch<any, unknown, AnyAction> = undefined as any

function dispatchWrap(
action: Action | ThunkAction<any, any, unknown, AnyAction>
) {
// Should not have an error here thanks to the extra union overload
dispatch(action)
}
}
39 changes: 0 additions & 39 deletions webpack.config.babel.js

This file was deleted.

0 comments on commit 7fb5a23

Please sign in to comment.