Repository: reduxjs/redux-toolkit
Pull request #498 (2020-04-12 15:02:27)

Fixed non working example in createEntityAdapter
You could not copy paste and run the code as there were missing interfaces.


Patch:
diff --git a/docs/api/createAsyncThunk.md b/docs/api/createAsyncThunk.md
index 9e6c7a2c..7294af03 100644
--- a/docs/api/createAsyncThunk.md
+++ b/docs/api/createAsyncThunk.md
@@ -9,10 +9,12 @@ hide_title: true
 
 ## Overview
 
-A function that accepts a Redux action type string and a callback function that should return a promise. It generates promise lifecycle action types based on the provided action type, and returns a thunk action creator that will run the promise callback and dispatch the lifecycle actions based on the returned promise.
+A function that accepts a Redux action type string and a callback function that should return a promise. It generates promise lifecycle action types based on the action type prefix that you pass in, and returns a thunk action creator that will run the promise callback and dispatch the lifecycle actions based on the returned promise.
 
 This abstracts the standard recommended approach for handling async request lifecycles.
 
+It does not generate any reducer functions, since it does not know what data you're fetching, how you want to track loading state, or how the data you return needs to be processed. You should write your own reducer logic that handles these actions, with whatever loading state and processing logic is appropriate for your own app.
+
 Sample usage:
 
 ```js {5-11,22-25,30}
@@ -50,7 +52,7 @@ dispatch(fetchUserById(123))
 
 ## Parameters
 
-`createAsyncThunk` accepts two parameters: a string action `type` value, and a `payloadCreator` callback.
+`createAsyncThunk` accepts three parameters: a string action `type` value, a `payloadCreator` callback, and an `options` object.
 
 ### `type`
 
@@ -81,10 +83,24 @@ The `payloadCreator` function will be called with two arguments:
 
 The logic in the `payloadCreator` function may use any of these values as needed to calculate the result.
 
+### Options
+
+An object with the following optional fields:
+
+- `condition`: a callback that can be used to skip execution of the payload creator and all action dispatches, if desired. See [Canceling Before Execution](#canceling-before-execution) for a complete description.
+- `dispatchConditionRejection`: if `condition()` returns `false`, the default behavior is that no actions will be dispatched at all. If you still want a "rejected" action to be dispatched when the thunk was canceled, set this flag to `true`.
+
 ## Return Value
 
 `createAsyncThunk` returns a standard Redux thunk action creator. The thunk action creator function will have plain action creators for the `pending`, `fulfilled`, and `rejected` cases attached as nested fields.
 
+Using the `fetchUserById` example above, `createAsyncThunk` will generate four functions:
+
+- `fetchUserById`, the thunk action creator that kicks off the async payload callback you wrote
+  - `fetchUserById.pending`, an action creator that dispatches an `'users/fetchByIdStatus/pending'` action
+  - `fetchUserById.fulfilled`, an action creator that dispatches an `'users/fetchByIdStatus/fulfilled'` action
+  - `fetchUserById.rejected`, an action creator that dispatches an `'users/fetchByIdStatus/rejected'` action
+
 When dispatched, the thunk will:
 
 - dispatch the `pending` action
@@ -135,6 +151,7 @@ interface RejectedAction<ThunkArg> {
     requestId: string
     arg: ThunkArg
     aborted: boolean
+    condition: bolean
   }
 }
 
@@ -231,6 +248,34 @@ const onClick = () => {
 
 ## Cancellation
 
+### Canceling Before Execution
+
+If you need to cancel a thunk before the payload creator is called, you may provide a `condition` callback as an option after the payload creator. The callback will receive the thunk argument and an object with `{getState, extra}` as parameters, and use those to decide whether to continue or not. If the execution should be canceled, the `condition` callback should return a literal `false` value:
+
+```js
+const fetchUserById = createAsyncThunk(
+  'users/fetchByIdStatus',
+  async (userId, thunkAPI) => {
+    const response = await userAPI.fetchById(userId)
+    return response.data
+  },
+  {
+    condition: (userId, { getState, extra }) => {
+      const { users } = getState()
+      const fetchStatus = users.requests[userId]
+      if (fetchStatus === 'fulfilled' || fetchStatus === 'loading') {
+        // Already fetched or in progress, don't need to re-fetch
+        return false
+      }
+    }
+  }
+)
+```
+
+If `condition()` returns `false`, the default behavior is that no actions will be dispatched at all. If you still want a "rejected" action to be dispatched when the thunk was canceled, pass in `{condition, dispatchConditionRejection: true}`.
+
+### Canceling While Running
+
 If you want to cancel your running thunk before it has finished, you can use the `abort` method of the promise returned by `dispatch(fetchUserById(userId))`.
 
 A real-life example of that would look like this:
diff --git a/docs/api/createEntityAdapter.md b/docs/api/createEntityAdapter.md
index 63d5ff67..cab1a62d 100644
--- a/docs/api/createEntityAdapter.md
+++ b/docs/api/createEntityAdapter.md
@@ -42,7 +42,9 @@ import {
   configureStore
 } from '@reduxjs/toolkit'
 
-const booksAdapter = createEntityAdapter({
+type Book = { bookId: string; title: string }
+
+const booksAdapter = createEntityAdapter<Book>({
   // Assume IDs are stored in a field other than `book.id`
   selectId: book => book.bookId,
   // Keep the "all IDs" array sorted based on book titles
@@ -69,11 +71,13 @@ const store = configureStore({
   }
 })
 
+type State = ReturnType<typeof store.getState>
+
 console.log(store.getState().books)
 // {ids: [], entities: {} }
 
 // Can create a set of memoized selectors based on the location of this entity state
-const booksSelectors = booksAdapter.getSelectors(state => state.books)
+const booksSelectors = booksAdapter.getSelectors<State>(state => state.books)
 
 // And then use the selectors to retrieve values
 const allBooks = booksSelectors.selectAll(store.getState())
diff --git a/docs/api/getDefaultMiddleware.md b/docs/api/getDefaultMiddleware.md
index ac51a106..bc9928a3 100644
--- a/docs/api/getDefaultMiddleware.md
+++ b/docs/api/getDefaultMiddleware.md
@@ -110,7 +110,8 @@ interface ThunkOptions<E = any> {
 
 interface ImmutableStateInvariantMiddlewareOptions {
   isImmutable?: (value: any) => boolean
-  ignore?: string[]
+  ignoredPaths?: string[]
+  warnAfter?: number
 }
 
 interface SerializableStateInvariantMiddlewareOptions {
@@ -132,10 +133,22 @@ interface SerializableStateInvariantMiddlewareOptions {
    */
   ignoredActions?: string[]
 
+  /**
+   * An array of dot-separated path strings to ignore when checking for serializability, Defaults to ['meta.arg']
+   * If you use this parameter, the default value 'meta.arg' will be removed, so we recommend re-adding it unless you
+   * specifically do not want to ignore it. Example: ['meta.arg', 'your.path', 'other.path', ...etc]
+   */
+  ignoredActionPaths?: string[]
+
   /**
    * An array of dot-separated path strings to ignore when checking for serializability, Defaults to []
    */
   ignoredPaths?: string[]
+
+  /**
+   * Execution time warning threshold. If the middleware takes longer than `warnAfter` ms, a warning will be displayed in the console. Defaults to 32
+   */
+  warnAfter?: number
 }
 
 interface GetDefaultMiddlewareOptions {
diff --git a/docs/api/otherExports.md b/docs/api/otherExports.md
index 4a5b5af9..8ef68663 100644
--- a/docs/api/otherExports.md
+++ b/docs/api/otherExports.md
@@ -80,9 +80,15 @@ function createSerializableStateInvariantMiddleware({
   // An array of action types to ignore when checking for serializability.
   // Defaults to []
   ignoredActions?: string[]
-  // An array of dot-separated path strings to ignore when checking for serializability.
-  // Defaults to []
+  // An array of dot-separated path strings to ignore when
+  // checking for serializability, Defaults to ['meta.arg']
+  ignoredActionPaths?: string[]
+  // An array of dot-separated path strings to ignore when
+  // checking for serializability, Defaults to []
   ignoredPaths?: string[]
+  // Execution time warning threshold. If the middleware takes longer
+  // than `warnAfter` ms, a warning will be displayed in the console. Defaults to 32
+  warnAfter?: number
 })
 ```
 
diff --git a/etc/redux-toolkit.api.md b/etc/redux-toolkit.api.md
index ee7be645..9b88eec2 100644
--- a/etc/redux-toolkit.api.md
+++ b/etc/redux-toolkit.api.md
@@ -59,6 +59,25 @@ export interface ActionReducerMapBuilder<State> {
 // @public @deprecated
 export type Actions<T extends keyof any = string> = Record<T, Action>;
 
+// @public
+export type AsyncThunkAction<Returned, ThunkArg, ThunkApiConfig extends AsyncThunkConfig> = (dispatch: GetDispatch<ThunkApiConfig>, getState: () => GetState<ThunkApiConfig>, extra: GetExtra<ThunkApiConfig>) => Promise<PayloadAction<Returned, string, {
+    arg: ThunkArg;
+    requestId: string;
+}> | PayloadAction<undefined | GetRejectValue<ThunkApiConfig>, string, {
+    arg: ThunkArg;
+    requestId: string;
+    aborted: boolean;
+    condition: boolean;
+}, SerializedError>> & {
+    abort(reason?: string): void;
+};
+
+// @public
+export type AsyncThunkPayloadCreator<Returned, ThunkArg = void, ThunkApiConfig extends AsyncThunkConfig = {}> = (arg: ThunkArg, thunkAPI: GetThunkAPI<ThunkApiConfig>) => AsyncThunkPayloadCreatorReturnValue<Returned, ThunkApiConfig>;
+
+// @public
+export type AsyncThunkPayloadCreatorReturnValue<Returned, ThunkApiConfig extends AsyncThunkConfig> = Promise<Returned | RejectWithValue<GetRejectValue<ThunkApiConfig>>> | Returned | RejectWithValue<GetRejectValue<ThunkApiConfig>>;
+
 // @public
 export type CaseReducer<S = any, A extends Action = AnyAction> = (state: Draft<S>, action: A) => S | void;
 
@@ -105,16 +124,7 @@ export function createAction<P = void, T extends string = string>(type: T): Payl
 export function createAction<PA extends PrepareAction<any>, T extends string = string>(type: T, prepareAction: PA): PayloadActionCreator<ReturnType<PA>['payload'], T, PA>;
 
 // @public (undocumented)
-export function createAsyncThunk<Returned, ThunkArg = void, ThunkApiConfig extends AsyncThunkConfig = {}>(type: string, payloadCreator: (arg: ThunkArg, thunkAPI: GetThunkAPI<ThunkApiConfig>) => Promise<Returned | RejectWithValue<GetRejectValue<ThunkApiConfig>>> | Returned | RejectWithValue<GetRejectValue<ThunkApiConfig>>): ((arg: ThunkArg) => (dispatch: GetDispatch<ThunkApiConfig>, getState: () => GetState<ThunkApiConfig>, extra: GetExtra<ThunkApiConfig>) => Promise<PayloadAction<Returned, string, {
-    arg: ThunkArg;
-    requestId: string;
-}, never> | PayloadAction<GetRejectValue<ThunkApiConfig> | undefined, string, {
-    arg: ThunkArg;
-    requestId: string;
-    aborted: boolean;
-}, SerializedError>> & {
-    abort: (reason?: string | undefined) => void;
-}) & {
+export function createAsyncThunk<Returned, ThunkArg = void, ThunkApiConfig extends AsyncThunkConfig = {}>(type: string, payloadCreator: (arg: ThunkArg, thunkAPI: GetThunkAPI<ThunkApiConfig>) => Promise<Returned | RejectWithValue<GetRejectValue<ThunkApiConfig>>> | Returned | RejectWithValue<GetRejectValue<ThunkApiConfig>>, options?: AsyncThunkOptions<ThunkArg, ThunkApiConfig>): IsAny<ThunkArg, (arg: ThunkArg) => AsyncThunkAction<Returned, ThunkArg, ThunkApiConfig>, unknown extends ThunkArg ? (arg: ThunkArg) => AsyncThunkAction<Returned, ThunkArg, ThunkApiConfig> : [ThunkArg] extends [void] | [undefined] ? () => AsyncThunkAction<Returned, ThunkArg, ThunkApiConfig> : [void] extends [ThunkArg] ? (arg?: ThunkArg | undefined) => AsyncThunkAction<Returned, ThunkArg, ThunkApiConfig> : [undefined] extends [ThunkArg] ? (arg?: ThunkArg | undefined) => AsyncThunkAction<Returned, ThunkArg, ThunkApiConfig> : (arg: ThunkArg) => AsyncThunkAction<Returned, ThunkArg, ThunkApiConfig>> & {
     pending: ActionCreatorWithPreparedPayload<[string, ThunkArg], undefined, string, never, {
         arg: ThunkArg;
         requestId: string;
@@ -123,6 +133,7 @@ export function createAsyncThunk<Returned, ThunkArg = void, ThunkApiConfig exten
         arg: ThunkArg;
         requestId: string;
         aborted: boolean;
+        condition: boolean;
     }>;
     fulfilled: ActionCreatorWithPreparedPayload<[Returned, string, ThunkArg], Returned, string, never, {
         arg: ThunkArg;
@@ -277,6 +288,8 @@ export type IdSelector<T> = (model: T) => EntityId;
 
 // @public
 export interface ImmutableStateInvariantMiddlewareOptions {
+    // (undocumented)
+    ignore?: string[];
     // (undocumented)
     ignoredPaths?: string[];
     // (undocumented)
diff --git a/package-lock.json b/package-lock.json
index 65522df6..9d1e2c95 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
 {
   "name": "@reduxjs/toolkit",
-  "version": "1.3.4",
+  "version": "1.3.5",
   "lockfileVersion": 1,
   "requires": true,
   "dependencies": {
diff --git a/package.json b/package.json
index 3a8dc2b7..6675ddfe 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@reduxjs/toolkit",
-  "version": "1.3.4",
+  "version": "1.3.5",
   "description": "The official, opinionated, batteries-included toolset for efficient Redux development",
   "repository": "https://github.com/reduxjs/redux-toolkit",
   "keywords": [
diff --git a/src/createAsyncThunk.ts b/src/createAsyncThunk.ts
index 42a6e100..ea0cc79c 100644
--- a/src/createAsyncThunk.ts
+++ b/src/createAsyncThunk.ts
@@ -5,7 +5,7 @@ import {
   ActionCreatorWithPreparedPayload
 } from './createAction'
 import { ThunkDispatch } from 'redux-thunk'
-import { FallbackIfUnknown } from './tsHelpers'
+import { FallbackIfUnknown, IsAny } from './tsHelpers'
 import { nanoid } from './nanoid'
 
 // @ts-ignore we need the import of these types due to a bundling issue.
@@ -102,11 +102,124 @@ type GetRejectValue<ThunkApiConfig> = ThunkApiConfig extends {
 }
   ? RejectValue
   : unknown
+/**
+ * A type describing the return value of the `payloadCreator` argument to `createAsyncThunk`.
+ * Might be useful for wrapping `createAsyncThunk` in custom abstractions.
+ *
+ * @public
+ */
+export type AsyncThunkPayloadCreatorReturnValue<
+  Returned,
+  ThunkApiConfig extends AsyncThunkConfig
+> =
+  | Promise<Returned | RejectWithValue<GetRejectValue<ThunkApiConfig>>>
+  | Returned
+  | RejectWithValue<GetRejectValue<ThunkApiConfig>>
+/**
+ * A type describing the `payloadCreator` argument to `createAsyncThunk`.
+ * Might be useful for wrapping `createAsyncThunk` in custom abstractions.
+ *
+ * @public
+ */
+export type AsyncThunkPayloadCreator<
+  Returned,
+  ThunkArg = void,
+  ThunkApiConfig extends AsyncThunkConfig = {}
+> = (
+  arg: ThunkArg,
+  thunkAPI: GetThunkAPI<ThunkApiConfig>
+) => AsyncThunkPayloadCreatorReturnValue<Returned, ThunkApiConfig>
+
+/**
+ * A ThunkAction created by `createAsyncThunk`.
+ * Dispatching it returns a Promise for either a
+ * fulfilled or rejected action.
+ * Also, the returned value contains a `abort()` method
+ * that allows the asyncAction to be cancelled from the outside.
+ *
+ * @public
+ */
+export type AsyncThunkAction<
+  Returned,
+  ThunkArg,
+  ThunkApiConfig extends AsyncThunkConfig
+> = (
+  dispatch: GetDispatch<ThunkApiConfig>,
+  getState: () => GetState<ThunkApiConfig>,
+  extra: GetExtra<ThunkApiConfig>
+) => Promise<
+  | PayloadAction<Returned, string, { arg: ThunkArg; requestId: string }>
+  | PayloadAction<
+      undefined | GetRejectValue<ThunkApiConfig>,
+      string,
+      {
+        arg: ThunkArg
+        requestId: string
+        aborted: boolean
+        condition: boolean
+      },
+      SerializedError
+    >
+> & {
+  abort(reason?: string): void
+}
+
+type AsyncThunkActionCreator<
+  Returned,
+  ThunkArg,
+  ThunkApiConfig extends AsyncThunkConfig
+> = IsAny<
+  ThunkArg,
+  // any handling
+  (arg: ThunkArg) => AsyncThunkAction<Returned, ThunkArg, ThunkApiConfig>,
+  // unknown handling
+  unknown extends ThunkArg
+    ? (arg: ThunkArg) => AsyncThunkAction<Returned, ThunkArg, ThunkApiConfig> // argument not specified or specified as void or undefined
+    : [ThunkArg] extends [void] | [undefined]
+    ? () => AsyncThunkAction<Returned, ThunkArg, ThunkApiConfig> // argument contains void
+    : [void] extends [ThunkArg] // make optional
+    ? (arg?: ThunkArg) => AsyncThunkAction<Returned, ThunkArg, ThunkApiConfig> // argument contains undefined
+    : [undefined] extends [ThunkArg]
+    ? WithStrictNullChecks<
+        // with strict nullChecks: make optional
+        (
+          arg?: ThunkArg
+        ) => AsyncThunkAction<Returned, ThunkArg, ThunkApiConfig>,
+        // without strict null checks this will match everything, so don't make it optional
+        (arg: ThunkArg) => AsyncThunkAction<Returned, ThunkArg, ThunkApiConfig>
+      > // default case: normal argument
+    : (arg: ThunkArg) => AsyncThunkAction<Returned, ThunkArg, ThunkApiConfig>
+>
+
+interface AsyncThunkOptions<
+  ThunkArg = void,
+  ThunkApiConfig extends AsyncThunkConfig = {}
+> {
+  /**
+   * A method to control whether the asyncThunk should be executed. Has access to the
+   * `arg`, `api.getState()` and `api.extra` arguments.
+   *
+   * @returns `true` if the asyncThunk should be executed, `false` if it should be skipped
+   */
+  condition?(
+    arg: ThunkArg,
+    api: Pick<GetThunkAPI<ThunkApiConfig>, 'getState' | 'extra'>
+  ): boolean
+  /**
+   * If `condition` returns `false`, the asyncThunk will be skipped.
+   * This option allows you to control whether a `rejected` action with `meta.condition == false`
+   * will be dispatched or not.
+   *
+   * @default `false`
+   */
+  dispatchConditionRejection?: boolean
+}
 
 /**
  *
  * @param type
  * @param payloadCreator
+ * @param options
  *
  * @public
  */
@@ -122,7 +235,8 @@ export function createAsyncThunk<
   ) =>
     | Promise<Returned | RejectWithValue<GetRejectValue<ThunkApiConfig>>>
     | Returned
-    | RejectWithValue<GetRejectValue<ThunkApiConfig>>
+    | RejectWithValue<GetRejectValue<ThunkApiConfig>>,
+  options?: AsyncThunkOptions<ThunkArg, ThunkApiConfig>
 ) {
   type RejectedValue = GetRejectValue<ThunkApiConfig>
 
@@ -155,13 +269,15 @@ export function createAsyncThunk<
       payload?: RejectedValue
     ) => {
       const aborted = !!error && error.name === 'AbortError'
+      const condition = !!error && error.name === 'ConditionError'
       return {
         payload,
         error: miniSerializeError(error || 'Rejected'),
         meta: {
           arg,
           requestId,
-          aborted
+          aborted,
+          condition
         }
       }
     }
@@ -195,12 +311,10 @@ If you want to use the AbortController to react to \`abort\` events, please cons
           }
         }
 
-  function actionCreator(arg: ThunkArg) {
-    return (
-      dispatch: GetDispatch<ThunkApiConfig>,
-      getState: () => GetState<ThunkApiConfig>,
-      extra: GetExtra<ThunkApiConfig>
-    ) => {
+  function actionCreator(
+    arg: ThunkArg
+  ): AsyncThunkAction<Returned, ThunkArg, ThunkApiConfig> {
+    return (dispatch, getState, extra) => {
       const requestId = nanoid()
 
       const abortController = new AC()
@@ -220,6 +334,16 @@ If you want to use the AbortController to react to \`abort\` events, please cons
       const promise = (async function() {
         let finalAction: ReturnType<typeof fulfilled | typeof rejected>
         try {
+          if (
+            options &&
+            options.condition &&
+            options.condition(arg, { getState, extra }) === false
+          ) {
+            throw {
+              name: 'ConditionError',
+              message: 'Aborted due to condition callback returning false.'
+            }
+          }
           dispatch(pending(requestId, arg))
           finalAction = await Promise.race([
             abortedPromise,
@@ -249,18 +373,33 @@ If you want to use the AbortController to react to \`abort\` events, please cons
         // per https://twitter.com/dan_abramov/status/770914221638942720
         // and https://redux-toolkit.js.org/tutorials/advanced-tutorial#async-error-handling-logic-in-thunks
 
-        dispatch(finalAction)
+        const skipDispatch =
+          options &&
+          !options.dispatchConditionRejection &&
+          rejected.match(finalAction) &&
+          finalAction.meta.condition
+
+        if (!skipDispatch) {
+          dispatch(finalAction)
+        }
         return finalAction
       })()
       return Object.assign(promise, { abort })
     }
   }
 
-  return Object.assign(actionCreator, {
-    pending,
-    rejected,
-    fulfilled
-  })
+  return Object.assign(
+    actionCreator as AsyncThunkActionCreator<
+      Returned,
+      ThunkArg,
+      ThunkApiConfig
+    >,
+    {
+      pending,
+      rejected,
+      fulfilled
+    }
+  )
 }
 
 type ActionTypesWithOptionalErrorAction =
@@ -283,3 +422,7 @@ export function unwrapResult<R extends ActionTypesWithOptionalErrorAction>(
   }
   return (returned as any).payload
 }
+
+type WithStrictNullChecks<True, False> = undefined extends boolean
+  ? False
+  : True
diff --git a/src/immutableStateInvariantMiddleware.ts b/src/immutableStateInvariantMiddleware.ts
index 2a0f1687..0455306b 100644
--- a/src/immutableStateInvariantMiddleware.ts
+++ b/src/immutableStateInvariantMiddleware.ts
@@ -187,6 +187,7 @@ export interface ImmutableStateInvariantMiddlewareOptions {
   isImmutable?: IsImmutableFunc
   ignoredPaths?: string[]
   warnAfter?: number
+  ignore?: string[] // @deprecated. Use ignoredPaths
 }
 
 /**
@@ -205,11 +206,16 @@ export function createImmutableStateInvariantMiddleware(
     return () => next => action => next(action)
   }
 
-  const {
+  let {
     isImmutable = isImmutableDefault,
     ignoredPaths,
-    warnAfter = 32
+    warnAfter = 32,
+    ignore
   } = options
+
+  // Alias ignore->ignoredPaths, but prefer ignoredPaths if present
+  ignoredPaths = ignoredPaths || ignore
+
   const track = trackForMutations.bind(null, isImmutable, ignoredPaths)
 
   return ({ getState }) => {
diff --git a/src/index.ts b/src/index.ts
index d4e1d849..f4a1843d 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -96,6 +96,9 @@ export {
 } from './entities/models'
 
 export {
+  AsyncThunkAction,
+  AsyncThunkPayloadCreatorReturnValue,
+  AsyncThunkPayloadCreator,
   createAsyncThunk,
   unwrapResult,
   SerializedError


Test patch:
diff --git a/src/createAsyncThunk.test.ts b/src/createAsyncThunk.test.ts
index dd50f78d..38644b83 100644
--- a/src/createAsyncThunk.test.ts
+++ b/src/createAsyncThunk.test.ts
@@ -476,3 +476,96 @@ test('non-serializable arguments are ignored by serializableStateInvariantMiddle
   expect(getLog().log).toMatchInlineSnapshot(`""`)
   restore()
 })
+
+describe('conditional skipping of asyncThunks', () => {
+  const arg = {}
+  const getState = jest.fn(() => ({}))
+  const dispatch = jest.fn((x: any) => x)
+  const payloadCreator = jest.fn((x: typeof arg) => 10)
+  const condition = jest.fn(() => false)
+  const extra = {}
+
+  beforeEach(() => {
+    getState.mockClear()
+    dispatch.mockClear()
+    payloadCreator.mockClear()
+    condition.mockClear()
+  })
+
+  test('returning false from condition skips payloadCreator and returns a rejected action', async () => {
+    const asyncThunk = createAsyncThunk('test', payloadCreator, { condition })
+    const result = await asyncThunk(arg)(dispatch, getState, extra)
+
+    expect(condition).toHaveBeenCalled()
+    expect(payloadCreator).not.toHaveBeenCalled()
+    expect(asyncThunk.rejected.match(result)).toBe(true)
+    expect((result as any).meta.condition).toBe(true)
+  })
+
+  test('return falsy from condition does not skip payload creator', async () => {
+    // Override TS's expectation that this is a boolean
+    condition.mockReturnValueOnce((undefined as unknown) as boolean)
+    const asyncThunk = createAsyncThunk('test', payloadCreator, { condition })
+    const result = await asyncThunk(arg)(dispatch, getState, extra)
+
+    expect(condition).toHaveBeenCalled()
+    expect(payloadCreator).toHaveBeenCalled()
+    expect(asyncThunk.fulfilled.match(result)).toBe(true)
+    expect(result.payload).toBe(10)
+  })
+
+  test('returning true from condition executes payloadCreator', async () => {
+    condition.mockReturnValueOnce(true)
+    const asyncThunk = createAsyncThunk('test', payloadCreator, { condition })
+    const result = await asyncThunk(arg)(dispatch, getState, extra)
+
+    expect(condition).toHaveBeenCalled()
+    expect(payloadCreator).toHaveBeenCalled()
+    expect(asyncThunk.fulfilled.match(result)).toBe(true)
+    expect(result.payload).toBe(10)
+  })
+
+  test('condition is called with arg, getState and extra', async () => {
+    const asyncThunk = createAsyncThunk('test', payloadCreator, { condition })
+    await asyncThunk(arg)(dispatch, getState, extra)
+
+    expect(condition).toHaveBeenCalledTimes(1)
+    expect(condition).toHaveBeenLastCalledWith(
+      arg,
+      expect.objectContaining({ getState, extra })
+    )
+  })
+
+  test('rejected action is not dispatched by default', async () => {
+    const asyncThunk = createAsyncThunk('test', payloadCreator, { condition })
+    await asyncThunk(arg)(dispatch, getState, extra)
+
+    expect(dispatch).toHaveBeenCalledTimes(0)
+  })
+
+  test('rejected action can be dispatched via option', async () => {
+    const asyncThunk = createAsyncThunk('test', payloadCreator, {
+      condition,
+      dispatchConditionRejection: true
+    })
+    await asyncThunk(arg)(dispatch, getState, extra)
+
+    expect(dispatch).toHaveBeenCalledTimes(1)
+    expect(dispatch).toHaveBeenLastCalledWith(
+      expect.objectContaining({
+        error: {
+          message: 'Aborted due to condition callback returning false.',
+          name: 'ConditionError'
+        },
+        meta: {
+          aborted: false,
+          arg: arg,
+          condition: true,
+          requestId: expect.stringContaining('')
+        },
+        payload: undefined,
+        type: 'test/rejected'
+      })
+    )
+  })
+})
diff --git a/src/immutableStateInvariantMiddleware.test.ts b/src/immutableStateInvariantMiddleware.test.ts
index 104e677d..ab89d71e 100644
--- a/src/immutableStateInvariantMiddleware.test.ts
+++ b/src/immutableStateInvariantMiddleware.test.ts
@@ -110,7 +110,7 @@ describe('createImmutableStateInvariantMiddleware', () => {
     }).not.toThrow()
   })
 
-  it('respects "ignore" option', () => {
+  it('respects "ignoredPaths" option', () => {
     const next: Dispatch = action => {
       state.foo.bar.push(5)
       return action
@@ -123,6 +123,19 @@ describe('createImmutableStateInvariantMiddleware', () => {
     }).not.toThrow()
   })
 
+  it('alias "ignore" to "ignoredPath" and respects option', () => {
+    const next: Dispatch = action => {
+      state.foo.bar.push(5)
+      return action
+    }
+
+    const dispatch = middleware({ ignore: ['foo.bar'] })(next)
+
+    expect(() => {
+      dispatch({ type: 'SOME_ACTION' })
+    }).not.toThrow()
+  })
+
   it('Should print a warning if execution takes too long', () => {
     state.foo.bar = new Array(10000).fill({ value: 'more' })
 
diff --git a/type-tests/files/createAsyncThunk.typetest.ts b/type-tests/files/createAsyncThunk.typetest.ts
index 261f459e..2695d6f7 100644
--- a/type-tests/files/createAsyncThunk.typetest.ts
+++ b/type-tests/files/createAsyncThunk.typetest.ts
@@ -3,7 +3,7 @@ import { ThunkDispatch } from 'redux-thunk'
 import { unwrapResult, SerializedError } from 'src/createAsyncThunk'
 
 import apiRequest, { AxiosError } from 'axios'
-import { IsAny } from 'src/tsHelpers'
+import { IsAny, IsUnknown } from 'src/tsHelpers'
 
 function expectType<T>(t: T) {
   return t
@@ -181,3 +181,164 @@ const defaultDispatch = (() => {}) as ThunkDispatch<{}, any, AnyAction>
     }
   })
 }
+
+/**
+ * payloadCreator first argument type has impact on asyncThunk argument
+ */
+{
+  // no argument: asyncThunk has no argument
+  {
+    const asyncThunk = createAsyncThunk('test', () => 0)
+    expectType<() => any>(asyncThunk)
+    // typings:expect-error cannot be called with an argument
+    asyncThunk(0 as any)
+  }
+
+  // one argument, specified as undefined: asyncThunk has no argument
+  {
+    const asyncThunk = createAsyncThunk('test', (arg: undefined) => 0)
+    expectType<() => any>(asyncThunk)
+    // typings:expect-error cannot be called with an argument
+    asyncThunk(0 as any)
+  }
+
+  // one argument, specified as void: asyncThunk has no argument
+  {
+    const asyncThunk = createAsyncThunk('test', (arg: void) => 0)
+    expectType<() => any>(asyncThunk)
+    // typings:expect-error cannot be called with an argument
+    asyncThunk(0 as any)
+  }
+
+  // one argument, specified as optional number: asyncThunk has optional number argument
+  // this test will fail with strictNullChecks: false, that is to be expected
+  // in that case, we have to forbid this behaviour or it will make arguments optional everywhere
+  {
+    const asyncThunk = createAsyncThunk('test', (arg?: number) => 0)
+    expectType<(arg?: number) => any>(asyncThunk)
+    asyncThunk()
+    asyncThunk(5)
+    // typings:expect-error
+    asyncThunk('string')
+  }
+
+  // one argument, specified as number|undefined: asyncThunk has optional number argument
+  // this test will fail with strictNullChecks: false, that is to be expected
+  // in that case, we have to forbid this behaviour or it will make arguments optional everywhere
+  {
+    const asyncThunk = createAsyncThunk('test', (arg: number | undefined) => 0)
+    expectType<(arg?: number) => any>(asyncThunk)
+    asyncThunk()
+    asyncThunk(5)
+    // typings:expect-error
+    asyncThunk('string')
+  }
+
+  // one argument, specified as number|void: asyncThunk has optional number argument
+  {
+    const asyncThunk = createAsyncThunk('test', (arg: number | void) => 0)
+    expectType<(arg?: number) => any>(asyncThunk)
+    asyncThunk()
+    asyncThunk(5)
+    // typings:expect-error
+    asyncThunk('string')
+  }
+
+  // one argument, specified as any: asyncThunk has required any argument
+  {
+    const asyncThunk = createAsyncThunk('test', (arg: any) => 0)
+    expectType<IsAny<Parameters<typeof asyncThunk>[0], true, false>>(true)
+    asyncThunk(5)
+    // typings:expect-error
+    asyncThunk()
+  }
+
+  // one argument, specified as unknown: asyncThunk has required unknown argument
+  {
+    const asyncThunk = createAsyncThunk('test', (arg: unknown) => 0)
+    expectType<IsUnknown<Parameters<typeof asyncThunk>[0], true, false>>(true)
+    asyncThunk(5)
+    // typings:expect-error
+    asyncThunk()
+  }
+
+  // one argument, specified as number: asyncThunk has required number argument
+  {
+    const asyncThunk = createAsyncThunk('test', (arg: number) => 0)
+    expectType<(arg: number) => any>(asyncThunk)
+    asyncThunk(5)
+    // typings:expect-error
+    asyncThunk()
+  }
+
+  // two arguments, first specified as undefined: asyncThunk has no argument
+  {
+    const asyncThunk = createAsyncThunk('test', (arg: undefined, thunkApi) => 0)
+    expectType<() => any>(asyncThunk)
+    // typings:expect-error cannot be called with an argument
+    asyncThunk(0 as any)
+  }
+
+  // two arguments, first specified as void: asyncThunk has no argument
+  {
+    const asyncThunk = createAsyncThunk('test', (arg: void, thunkApi) => 0)
+    expectType<() => any>(asyncThunk)
+    // typings:expect-error cannot be called with an argument
+    asyncThunk(0 as any)
+  }
+
+  // two arguments, first specified as number|undefined: asyncThunk has optional number argument
+  // this test will fail with strictNullChecks: false, that is to be expected
+  // in that case, we have to forbid this behaviour or it will make arguments optional everywhere
+  {
+    const asyncThunk = createAsyncThunk(
+      'test',
+      (arg: number | undefined, thunkApi) => 0
+    )
+    expectType<(arg?: number) => any>(asyncThunk)
+    asyncThunk()
+    asyncThunk(5)
+    // typings:expect-error
+    asyncThunk('string')
+  }
+
+  // two arguments, first specified as number|void: asyncThunk has optional number argument
+  {
+    const asyncThunk = createAsyncThunk(
+      'test',
+      (arg: number | void, thunkApi) => 0
+    )
+    expectType<(arg?: number) => any>(asyncThunk)
+    asyncThunk()
+    asyncThunk(5)
+    // typings:expect-error
+    asyncThunk('string')
+  }
+
+  // two arguments, first specified as any: asyncThunk has required any argument
+  {
+    const asyncThunk = createAsyncThunk('test', (arg: any, thunkApi) => 0)
+    expectType<IsAny<Parameters<typeof asyncThunk>[0], true, false>>(true)
+    asyncThunk(5)
+    // typings:expect-error
+    asyncThunk()
+  }
+
+  // two arguments, first specified as unknown: asyncThunk has required unknown argument
+  {
+    const asyncThunk = createAsyncThunk('test', (arg: unknown, thunkApi) => 0)
+    expectType<IsUnknown<Parameters<typeof asyncThunk>[0], true, false>>(true)
+    asyncThunk(5)
+    // typings:expect-error
+    asyncThunk()
+  }
+
+  // two arguments, first specified as number: asyncThunk has required number argument
+  {
+    const asyncThunk = createAsyncThunk('test', (arg: number, thunkApi) => 0)
+    expectType<(arg: number) => any>(asyncThunk)
+    asyncThunk(5)
+    // typings:expect-error
+    asyncThunk()
+  }
+}