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

Remove Webpack config and try test build #324

Merged
merged 6 commits into from
Oct 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.