UNPKG

7.16 kBPlain TextView Raw
1import type {
2 Reducer,
3 ReducersMapObject,
4 Middleware,
5 Action,
6 StoreEnhancer,
7 Store,
8 UnknownAction,
9} from 'redux'
10import {
11 applyMiddleware,
12 createStore,
13 compose,
14 combineReducers,
15 isPlainObject,
16} from 'redux'
17import type { DevToolsEnhancerOptions as DevToolsOptions } from './devtoolsExtension'
18import { composeWithDevTools } from './devtoolsExtension'
19
20import type {
21 ThunkMiddlewareFor,
22 GetDefaultMiddleware,
23} from './getDefaultMiddleware'
24import { buildGetDefaultMiddleware } from './getDefaultMiddleware'
25import type {
26 ExtractDispatchExtensions,
27 ExtractStoreExtensions,
28 ExtractStateExtensions,
29 UnknownIfNonSpecific,
30} from './tsHelpers'
31import type { Tuple } from './utils'
32import type { GetDefaultEnhancers } from './getDefaultEnhancers'
33import { buildGetDefaultEnhancers } from './getDefaultEnhancers'
34
35const IS_PRODUCTION = process.env.NODE_ENV === 'production'
36
37/**
38 * Options for `configureStore()`.
39 *
40 * @public
41 */
42export interface ConfigureStoreOptions<
43 S = any,
44 A extends Action = UnknownAction,
45 M extends Tuple<Middlewares<S>> = Tuple<Middlewares<S>>,
46 E extends Tuple<Enhancers> = Tuple<Enhancers>,
47 P = S,
48> {
49 /**
50 * A single reducer function that will be used as the root reducer, or an
51 * object of slice reducers that will be passed to `combineReducers()`.
52 */
53 reducer: Reducer<S, A, P> | ReducersMapObject<S, A, P>
54
55 /**
56 * An array of Redux middleware to install, or a callback receiving `getDefaultMiddleware` and returning a Tuple of middleware.
57 * If not supplied, defaults to the set of middleware returned by `getDefaultMiddleware()`.
58 *
59 * @example `middleware: (gDM) => gDM().concat(logger, apiMiddleware, yourCustomMiddleware)`
60 * @see https://redux-toolkit.js.org/api/getDefaultMiddleware#intended-usage
61 */
62 middleware?: (getDefaultMiddleware: GetDefaultMiddleware<S>) => M
63
64 /**
65 * Whether to enable Redux DevTools integration. Defaults to `true`.
66 *
67 * Additional configuration can be done by passing Redux DevTools options
68 */
69 devTools?: boolean | DevToolsOptions
70
71 /**
72 * The initial state, same as Redux's createStore.
73 * You may optionally specify it to hydrate the state
74 * from the server in universal apps, or to restore a previously serialized
75 * user session. If you use `combineReducers()` to produce the root reducer
76 * function (either directly or indirectly by passing an object as `reducer`),
77 * this must be an object with the same shape as the reducer map keys.
78 */
79 // we infer here, and instead complain if the reducer doesn't match
80 preloadedState?: P
81
82 /**
83 * The store enhancers to apply. See Redux's `createStore()`.
84 * All enhancers will be included before the DevTools Extension enhancer.
85 * If you need to customize the order of enhancers, supply a callback
86 * function that will receive a `getDefaultEnhancers` function that returns a Tuple,
87 * and should return a Tuple of enhancers (such as `getDefaultEnhancers().concat(offline)`).
88 * If you only need to add middleware, you can use the `middleware` parameter instead.
89 */
90 enhancers?: (getDefaultEnhancers: GetDefaultEnhancers<M>) => E
91}
92
93export type Middlewares<S> = ReadonlyArray<Middleware<{}, S>>
94
95type Enhancers = ReadonlyArray<StoreEnhancer>
96
97/**
98 * A Redux store returned by `configureStore()`. Supports dispatching
99 * side-effectful _thunks_ in addition to plain actions.
100 *
101 * @public
102 */
103export type EnhancedStore<
104 S = any,
105 A extends Action = UnknownAction,
106 E extends Enhancers = Enhancers,
107> = ExtractStoreExtensions<E> &
108 Store<S, A, UnknownIfNonSpecific<ExtractStateExtensions<E>>>
109
110/**
111 * A friendly abstraction over the standard Redux `createStore()` function.
112 *
113 * @param options The store configuration.
114 * @returns A configured Redux store.
115 *
116 * @public
117 */
118export function configureStore<
119 S = any,
120 A extends Action = UnknownAction,
121 M extends Tuple<Middlewares<S>> = Tuple<[ThunkMiddlewareFor<S>]>,
122 E extends Tuple<Enhancers> = Tuple<
123 [StoreEnhancer<{ dispatch: ExtractDispatchExtensions<M> }>, StoreEnhancer]
124 >,
125 P = S,
126>(options: ConfigureStoreOptions<S, A, M, E, P>): EnhancedStore<S, A, E> {
127 const getDefaultMiddleware = buildGetDefaultMiddleware<S>()
128
129 const {
130 reducer = undefined,
131 middleware,
132 devTools = true,
133 preloadedState = undefined,
134 enhancers = undefined,
135 } = options || {}
136
137 let rootReducer: Reducer<S, A, P>
138
139 if (typeof reducer === 'function') {
140 rootReducer = reducer
141 } else if (isPlainObject(reducer)) {
142 rootReducer = combineReducers(reducer) as unknown as Reducer<S, A, P>
143 } else {
144 throw new Error(
145 '`reducer` is a required argument, and must be a function or an object of functions that can be passed to combineReducers',
146 )
147 }
148
149 if (!IS_PRODUCTION && middleware && typeof middleware !== 'function') {
150 throw new Error('`middleware` field must be a callback')
151 }
152
153 let finalMiddleware: Tuple<Middlewares<S>>
154 if (typeof middleware === 'function') {
155 finalMiddleware = middleware(getDefaultMiddleware)
156
157 if (!IS_PRODUCTION && !Array.isArray(finalMiddleware)) {
158 throw new Error(
159 'when using a middleware builder function, an array of middleware must be returned',
160 )
161 }
162 } else {
163 finalMiddleware = getDefaultMiddleware()
164 }
165 if (
166 !IS_PRODUCTION &&
167 finalMiddleware.some((item: any) => typeof item !== 'function')
168 ) {
169 throw new Error(
170 'each middleware provided to configureStore must be a function',
171 )
172 }
173
174 let finalCompose = compose
175
176 if (devTools) {
177 finalCompose = composeWithDevTools({
178 // Enable capture of stack traces for dispatched Redux actions
179 trace: !IS_PRODUCTION,
180 ...(typeof devTools === 'object' && devTools),
181 })
182 }
183
184 const middlewareEnhancer = applyMiddleware(...finalMiddleware)
185
186 const getDefaultEnhancers = buildGetDefaultEnhancers<M>(middlewareEnhancer)
187
188 if (!IS_PRODUCTION && enhancers && typeof enhancers !== 'function') {
189 throw new Error('`enhancers` field must be a callback')
190 }
191
192 let storeEnhancers =
193 typeof enhancers === 'function'
194 ? enhancers(getDefaultEnhancers)
195 : getDefaultEnhancers()
196
197 if (!IS_PRODUCTION && !Array.isArray(storeEnhancers)) {
198 throw new Error('`enhancers` callback must return an array')
199 }
200 if (
201 !IS_PRODUCTION &&
202 storeEnhancers.some((item: any) => typeof item !== 'function')
203 ) {
204 throw new Error(
205 'each enhancer provided to configureStore must be a function',
206 )
207 }
208 if (
209 !IS_PRODUCTION &&
210 finalMiddleware.length &&
211 !storeEnhancers.includes(middlewareEnhancer)
212 ) {
213 console.error(
214 'middlewares were provided, but middleware enhancer was not included in final enhancers - make sure to call `getDefaultEnhancers`',
215 )
216 }
217
218 const composedEnhancer: StoreEnhancer<any> = finalCompose(...storeEnhancers)
219
220 return createStore(rootReducer, preloadedState as P, composedEnhancer)
221}