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 middleware array option #3760

Merged
merged 6 commits into from Oct 1, 2023
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/api/actionCreatorMiddleware.mdx
Expand Up @@ -63,6 +63,6 @@ const actionCreatorMiddleware = createActionCreatorInvariantMiddleware({

const store = configureStore({
reducer,
middleware: new Tuple(actionCreatorMiddleware),
middleware: () => new Tuple(actionCreatorMiddleware),
})
```
28 changes: 11 additions & 17 deletions docs/api/configureStore.mdx
Expand Up @@ -100,18 +100,15 @@ If it is an object of slice reducers, like `{users : usersReducer, posts : posts

### `middleware`

An optional array of Redux middleware functions, or a callback to customise the array of middleware.
A callback which will receive `getDefaultMiddleware` as its argument,
and should return a middleware array.

If this option is provided, it should contain all the middleware functions you
If this option is provided, it should return all the middleware functions you
want added to the store. `configureStore` will automatically pass those to `applyMiddleware`.

If not provided, `configureStore` will call `getDefaultMiddleware` and use the
array of middleware functions it returns.

Where you wish to add onto or customize the default middleware,
you may pass a callback function that will receive `getDefaultMiddleware` as its argument,
and should return a middleware array.

For more details on how the `middleware` parameter works and the list of middleware that are added by default, see the
[`getDefaultMiddleware` docs page](./getDefaultMiddleware.mdx).

Expand All @@ -123,7 +120,7 @@ import { configureStore, Tuple } from '@reduxjs/toolkit'

configureStore({
reducer: rootReducer,
middleware: new Tuple(additionalMiddleware, logger),
middleware: () => new Tuple(additionalMiddleware, logger),
})
```

Expand Down Expand Up @@ -178,11 +175,6 @@ If you provide an array, this `applyMiddleware` enhancer will _not_ be used.
`configureStore` will warn in console if any middleware are provided (or left as default) but not included in the final list of enhancers.

```ts no-transpile
// warns - middleware left as default but not included in final enhancers
configureStore({
reducer,
enhancers: [offline(offlineConfig)],
})
// warns - middleware customised but not included in final enhancers
configureStore({
reducer,
Expand All @@ -199,8 +191,8 @@ configureStore({
// also allowed
configureStore({
reducer,
middleware: [],
enhancers: [offline(offlineConfig)],
middleware: () => [],
enhancers: () => [offline(offlineConfig)],
})
```

Expand All @@ -209,20 +201,22 @@ configureStore({
:::note Tuple
Typescript users are required to use a `Tuple` instance (if not using a `getDefaultEnhancer` result, which is already a `Tuple`), for better inference.

```
import { configureStore, Tuple } from '@reduxjs/toolkit'

configureStore({
reducer: rootReducer,
enhancers: new Tuple(offline),
enhancers: () => new Tuple(offline),
})

````
```

Javascript-only users are free to use a plain array if preferred.

:::

## Usage

### Basic Example

```ts
Expand All @@ -238,7 +232,7 @@ import rootReducer from './reducers'

const store = configureStore({ reducer: rootReducer })
// The store now has redux-thunk added and the Redux DevTools Extension is turned on
````
```

### Full Example

Expand Down
2 changes: 1 addition & 1 deletion docs/api/getDefaultMiddleware.mdx
Expand Up @@ -28,7 +28,7 @@ If you want to customize the list of middleware, you can supply an array of midd
```js
const store = configureStore({
reducer: rootReducer,
middleware: new Tuple(thunk, logger),
middleware: () => new Tuple(thunk, logger),
})

// Store specifically has the thunk and logger middleware applied
Expand Down
2 changes: 1 addition & 1 deletion docs/api/immutabilityMiddleware.mdx
Expand Up @@ -86,7 +86,7 @@ const immutableInvariantMiddleware = createImmutableStateInvariantMiddleware({
const store = configureStore({
reducer: exampleSliceReducer,
// Note that this will replace all default middleware
middleware: new Tuple(immutableInvariantMiddleware),
middleware: () => new Tuple(immutableInvariantMiddleware),
})
```

Expand Down
2 changes: 1 addition & 1 deletion docs/api/serializabilityMiddleware.mdx
Expand Up @@ -111,7 +111,7 @@ const serializableMiddleware = createSerializableStateInvariantMiddleware({

const store = configureStore({
reducer,
middleware: new Tuple(serializableMiddleware),
middleware: () => new Tuple(serializableMiddleware),
})
```

Expand Down
2 changes: 1 addition & 1 deletion docs/usage/usage-with-typescript.md
Expand Up @@ -145,7 +145,7 @@ import { configureStore, Tuple } from '@reduxjs/toolkit'

configureStore({
reducer: rootReducer,
middleware: new Tuple(additionalMiddleware, logger),
middleware: () => new Tuple(additionalMiddleware, logger),
})
```

Expand Down
3 changes: 2 additions & 1 deletion errors.json
Expand Up @@ -29,5 +29,6 @@
"27": "Warning: Middleware for RTK-Query API at reducerPath \"\" has not been added to the store.\n You must add the middleware for RTK-Query to function correctly!",
"28": "Cannot refetch a query that has not been started yet.",
"29": "`builder.addCase` cannot be called with an empty action type",
"30": "`builder.addCase` cannot be called with two reducers for the same action type"
"30": "`builder.addCase` cannot be called with two reducers for the same action type",
"31": "\"middleware\" field must be a callback"
}
16 changes: 11 additions & 5 deletions packages/toolkit/src/configureStore.ts
Expand Up @@ -53,7 +53,7 @@ export interface ConfigureStoreOptions<
* @example `middleware: (gDM) => gDM().concat(logger, apiMiddleware, yourCustomMiddleware)`
* @see https://redux-toolkit.js.org/api/getDefaultMiddleware#intended-usage
*/
middleware?: ((getDefaultMiddleware: GetDefaultMiddleware<S>) => M) | M
middleware?: (getDefaultMiddleware: GetDefaultMiddleware<S>) => M

/**
* Whether to enable Redux DevTools integration. Defaults to `true`.
Expand Down Expand Up @@ -121,7 +121,7 @@ export function configureStore<

const {
reducer = undefined,
middleware = getDefaultMiddleware(),
middleware,
devTools = true,
preloadedState = undefined,
enhancers = undefined,
Expand All @@ -139,15 +139,21 @@ export function configureStore<
)
}

let finalMiddleware = middleware
if (typeof finalMiddleware === 'function') {
finalMiddleware = finalMiddleware(getDefaultMiddleware)
if (!IS_PRODUCTION && middleware && typeof middleware !== 'function') {
throw new Error('"middleware" field must be a callback')
}

let finalMiddleware: Tuple<Middlewares<S>>
if (typeof middleware === 'function') {
finalMiddleware = middleware(getDefaultMiddleware)

if (!IS_PRODUCTION && !Array.isArray(finalMiddleware)) {
throw new Error(
'when using a middleware builder function, an array of middleware must be returned'
)
}
} else {
finalMiddleware = getDefaultMiddleware()
}
if (
!IS_PRODUCTION &&
Expand Down
23 changes: 12 additions & 11 deletions packages/toolkit/src/tests/configureStore.test.ts
Expand Up @@ -110,7 +110,7 @@ describe('configureStore', async () => {
describe('given no middleware', () => {
it('calls createStore without any middleware', () => {
expect(
configureStore({ middleware: new Tuple(), reducer })
configureStore({ middleware: () => new Tuple(), reducer })
).toBeInstanceOf(Object)
expect(redux.applyMiddleware).toHaveBeenCalledWith()
expect(mockDevtoolsCompose).toHaveBeenCalled() // @remap-prod-remove-line-line
Expand All @@ -122,6 +122,15 @@ describe('configureStore', async () => {
})
})

describe('given an array of middleware', () => {
it('throws an error requiring a callback', () => {
// @ts-expect-error
expect(() => configureStore({ middleware: [], reducer })).toThrow(
'"middleware" field must be a callback'
)
})
})

describe('given undefined middleware', () => {
it('calls createStore with default middleware', () => {
expect(configureStore({ middleware: undefined, reducer })).toBeInstanceOf(
Expand Down Expand Up @@ -162,20 +171,12 @@ describe('configureStore', async () => {
})
})

describe('given custom middleware that contains non-functions', () => {
it('throws an error', () => {
expect(() =>
configureStore({ middleware: [true] as any, reducer })
).toThrow('each middleware provided to configureStore must be a function')
})
})

describe('given custom middleware', () => {
it('calls createStore with custom middleware and without default middleware', () => {
const thank: Redux.Middleware = (_store) => (next) => (action) =>
next(action)
expect(
configureStore({ middleware: new Tuple(thank), reducer })
configureStore({ middleware: () => new Tuple(thank), reducer })
).toBeInstanceOf(Object)
expect(redux.applyMiddleware).toHaveBeenCalledWith(thank)
expect(mockDevtoolsCompose).toHaveBeenCalled() // @remap-prod-remove-line-line
Expand Down Expand Up @@ -330,7 +331,7 @@ describe('configureStore', async () => {
it("doesn't warn when middleware enhancer is excluded if no middlewares provided", () => {
const store = configureStore({
reducer,
middleware: new Tuple(),
middleware: () => new Tuple(),
enhancers: () => new Tuple(dummyEnhancer),
})

Expand Down
17 changes: 8 additions & 9 deletions packages/toolkit/src/tests/configureStore.typetest.ts
Expand Up @@ -74,19 +74,19 @@ const _anyMiddleware: any = () => () => () => {}

configureStore({
reducer: () => 0,
middleware: new Tuple(middleware),
middleware: () => new Tuple(middleware),
})

configureStore({
reducer: () => 0,
// @ts-expect-error
middleware: [middleware],
middleware: () => [middleware],
})

configureStore({
reducer: () => 0,
// @ts-expect-error
middleware: new Tuple('not middleware'),
middleware: () => new Tuple('not middleware'),
})
}

Expand Down Expand Up @@ -520,7 +520,7 @@ const _anyMiddleware: any = () => () => () => {}
{
const store = configureStore({
reducer: reducerA,
middleware: new Tuple(),
middleware: () => new Tuple(),
})
// @ts-expect-error
store.dispatch(thunkA())
Expand All @@ -533,7 +533,7 @@ const _anyMiddleware: any = () => () => () => {}
{
const store = configureStore({
reducer: reducerA,
middleware: new Tuple(thunk as ThunkMiddleware<StateA>),
middleware: () => new Tuple(thunk as ThunkMiddleware<StateA>),
})
store.dispatch(thunkA())
// @ts-expect-error
Expand All @@ -545,9 +545,8 @@ const _anyMiddleware: any = () => () => () => {}
{
const store = configureStore({
reducer: reducerA,
middleware: new Tuple(
0 as unknown as Middleware<(a: StateA) => boolean, StateA>
),
middleware: () =>
new Tuple(0 as unknown as Middleware<(a: StateA) => boolean, StateA>),
})
const result: boolean = store.dispatch(5)
// @ts-expect-error
Expand All @@ -566,7 +565,7 @@ const _anyMiddleware: any = () => () => () => {}
>
const store = configureStore({
reducer: reducerA,
middleware,
middleware: () => middleware,
})

const result: 'A' = store.dispatch('a')
Expand Down
63 changes: 33 additions & 30 deletions packages/toolkit/src/tests/getDefaultMiddleware.test.ts
Expand Up @@ -147,17 +147,18 @@ describe('getDefaultMiddleware', () => {
it('allows passing options to immutableCheck', () => {
let immutableCheckWasCalled = false

const middleware = getDefaultMiddleware({
thunk: false,
immutableCheck: {
isImmutable: () => {
immutableCheckWasCalled = true
return true
const middleware = () =>
getDefaultMiddleware({
thunk: false,
immutableCheck: {
isImmutable: () => {
immutableCheckWasCalled = true
return true
},
},
},
serializableCheck: false,
actionCreatorCheck: false,
})
serializableCheck: false,
actionCreatorCheck: false,
})

const reducer = () => ({})

Expand All @@ -172,17 +173,18 @@ describe('getDefaultMiddleware', () => {
it('allows passing options to serializableCheck', () => {
let serializableCheckWasCalled = false

const middleware = getDefaultMiddleware({
thunk: false,
immutableCheck: false,
serializableCheck: {
isSerializable: () => {
serializableCheckWasCalled = true
return true
const middleware = () =>
getDefaultMiddleware({
thunk: false,
immutableCheck: false,
serializableCheck: {
isSerializable: () => {
serializableCheckWasCalled = true
return true
},
},
},
actionCreatorCheck: false,
})
actionCreatorCheck: false,
})

const reducer = () => ({})

Expand All @@ -200,17 +202,18 @@ describe('getDefaultMiddleware', () => {
it('allows passing options to actionCreatorCheck', () => {
let actionCreatorCheckWasCalled = false

const middleware = getDefaultMiddleware({
thunk: false,
immutableCheck: false,
serializableCheck: false,
actionCreatorCheck: {
isActionCreator: (action: unknown): action is Function => {
actionCreatorCheckWasCalled = true
return false
const middleware = () =>
getDefaultMiddleware({
thunk: false,
immutableCheck: false,
serializableCheck: false,
actionCreatorCheck: {
isActionCreator: (action: unknown): action is Function => {
actionCreatorCheckWasCalled = true
return false
},
},
},
})
})

const reducer = () => ({})

Expand Down