source: node_modules/@reduxjs/toolkit/src/mapBuilders.ts@ a762898

Last change on this file since a762898 was a762898, checked in by istevanoska <ilinastevanoska@…>, 5 months ago

Added visualizations

  • Property mode set to 100644
File size: 10.2 KB
Line 
1import type { Action } from 'redux'
2import type {
3 CaseReducer,
4 CaseReducers,
5 ActionMatcherDescriptionCollection,
6} from './createReducer'
7import type { TypeGuard } from './tsHelpers'
8import type { AsyncThunk, AsyncThunkConfig } from './createAsyncThunk'
9
10export type AsyncThunkReducers<
11 State,
12 ThunkArg extends any,
13 Returned = unknown,
14 ThunkApiConfig extends AsyncThunkConfig = {},
15> = {
16 pending?: CaseReducer<
17 State,
18 ReturnType<AsyncThunk<Returned, ThunkArg, ThunkApiConfig>['pending']>
19 >
20 rejected?: CaseReducer<
21 State,
22 ReturnType<AsyncThunk<Returned, ThunkArg, ThunkApiConfig>['rejected']>
23 >
24 fulfilled?: CaseReducer<
25 State,
26 ReturnType<AsyncThunk<Returned, ThunkArg, ThunkApiConfig>['fulfilled']>
27 >
28 settled?: CaseReducer<
29 State,
30 ReturnType<
31 AsyncThunk<Returned, ThunkArg, ThunkApiConfig>['rejected' | 'fulfilled']
32 >
33 >
34}
35
36export type TypedActionCreator<Type extends string> = {
37 (...args: any[]): Action<Type>
38 type: Type
39}
40
41/**
42 * A builder for an action <-> reducer map.
43 *
44 * @public
45 */
46export interface ActionReducerMapBuilder<State> {
47 /**
48 * Adds a case reducer to handle a single exact action type.
49 * @remarks
50 * All calls to `builder.addCase` must come before any calls to `builder.addMatcher` or `builder.addDefaultCase`.
51 * @param actionCreator - Either a plain action type string, or an action creator generated by [`createAction`](./createAction) that can be used to determine the action type.
52 * @param reducer - The actual case reducer function.
53 */
54 addCase<ActionCreator extends TypedActionCreator<string>>(
55 actionCreator: ActionCreator,
56 reducer: CaseReducer<State, ReturnType<ActionCreator>>,
57 ): ActionReducerMapBuilder<State>
58 /**
59 * Adds a case reducer to handle a single exact action type.
60 * @remarks
61 * All calls to `builder.addCase` must come before any calls to `builder.addAsyncThunk`, `builder.addMatcher` or `builder.addDefaultCase`.
62 * @param actionCreator - Either a plain action type string, or an action creator generated by [`createAction`](./createAction) that can be used to determine the action type.
63 * @param reducer - The actual case reducer function.
64 */
65 addCase<Type extends string, A extends Action<Type>>(
66 type: Type,
67 reducer: CaseReducer<State, A>,
68 ): ActionReducerMapBuilder<State>
69
70 /**
71 * Adds case reducers to handle actions based on a `AsyncThunk` action creator.
72 * @remarks
73 * All calls to `builder.addAsyncThunk` must come before after any calls to `builder.addCase` and before any calls to `builder.addMatcher` or `builder.addDefaultCase`.
74 * @param asyncThunk - The async thunk action creator itself.
75 * @param reducers - A mapping from each of the `AsyncThunk` action types to the case reducer that should handle those actions.
76 * @example
77```ts no-transpile
78import { createAsyncThunk, createReducer } from '@reduxjs/toolkit'
79
80const fetchUserById = createAsyncThunk('users/fetchUser', async (id) => {
81 const response = await fetch(`https://reqres.in/api/users/${id}`)
82 return (await response.json()).data
83})
84
85const reducer = createReducer(initialState, (builder) => {
86 builder.addAsyncThunk(fetchUserById, {
87 pending: (state, action) => {
88 state.fetchUserById.loading = 'pending'
89 },
90 fulfilled: (state, action) => {
91 state.fetchUserById.data = action.payload
92 },
93 rejected: (state, action) => {
94 state.fetchUserById.error = action.error
95 },
96 settled: (state, action) => {
97 state.fetchUserById.loading = action.meta.requestStatus
98 },
99 })
100})
101 */
102 addAsyncThunk<
103 Returned,
104 ThunkArg,
105 ThunkApiConfig extends AsyncThunkConfig = {},
106 >(
107 asyncThunk: AsyncThunk<Returned, ThunkArg, ThunkApiConfig>,
108 reducers: AsyncThunkReducers<State, ThunkArg, Returned, ThunkApiConfig>,
109 ): Omit<ActionReducerMapBuilder<State>, 'addCase'>
110
111 /**
112 * Allows you to match your incoming actions against your own filter function instead of only the `action.type` property.
113 * @remarks
114 * If multiple matcher reducers match, all of them will be executed in the order
115 * they were defined in - even if a case reducer already matched.
116 * All calls to `builder.addMatcher` must come after any calls to `builder.addCase` and `builder.addAsyncThunk` and before any calls to `builder.addDefaultCase`.
117 * @param matcher - A matcher function. In TypeScript, this should be a [type predicate](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates)
118 * function
119 * @param reducer - The actual case reducer function.
120 *
121 * @example
122```ts
123import {
124 createAction,
125 createReducer,
126 AsyncThunk,
127 UnknownAction,
128} from "@reduxjs/toolkit";
129
130type GenericAsyncThunk = AsyncThunk<unknown, unknown, any>;
131
132type PendingAction = ReturnType<GenericAsyncThunk["pending"]>;
133type RejectedAction = ReturnType<GenericAsyncThunk["rejected"]>;
134type FulfilledAction = ReturnType<GenericAsyncThunk["fulfilled"]>;
135
136const initialState: Record<string, string> = {};
137const resetAction = createAction("reset-tracked-loading-state");
138
139function isPendingAction(action: UnknownAction): action is PendingAction {
140 return typeof action.type === "string" && action.type.endsWith("/pending");
141}
142
143const reducer = createReducer(initialState, (builder) => {
144 builder
145 .addCase(resetAction, () => initialState)
146 // matcher can be defined outside as a type predicate function
147 .addMatcher(isPendingAction, (state, action) => {
148 state[action.meta.requestId] = "pending";
149 })
150 .addMatcher(
151 // matcher can be defined inline as a type predicate function
152 (action): action is RejectedAction => action.type.endsWith("/rejected"),
153 (state, action) => {
154 state[action.meta.requestId] = "rejected";
155 }
156 )
157 // matcher can just return boolean and the matcher can receive a generic argument
158 .addMatcher<FulfilledAction>(
159 (action) => action.type.endsWith("/fulfilled"),
160 (state, action) => {
161 state[action.meta.requestId] = "fulfilled";
162 }
163 );
164});
165```
166 */
167 addMatcher<A>(
168 matcher: TypeGuard<A> | ((action: any) => boolean),
169 reducer: CaseReducer<State, A extends Action ? A : A & Action>,
170 ): Omit<ActionReducerMapBuilder<State>, 'addCase' | 'addAsyncThunk'>
171
172 /**
173 * Adds a "default case" reducer that is executed if no case reducer and no matcher
174 * reducer was executed for this action.
175 * @param reducer - The fallback "default case" reducer function.
176 *
177 * @example
178```ts
179import { createReducer } from '@reduxjs/toolkit'
180const initialState = { otherActions: 0 }
181const reducer = createReducer(initialState, builder => {
182 builder
183 // .addCase(...)
184 // .addMatcher(...)
185 .addDefaultCase((state, action) => {
186 state.otherActions++
187 })
188})
189```
190 */
191 addDefaultCase(reducer: CaseReducer<State, Action>): {}
192}
193
194export function executeReducerBuilderCallback<S>(
195 builderCallback: (builder: ActionReducerMapBuilder<S>) => void,
196): [
197 CaseReducers<S, any>,
198 ActionMatcherDescriptionCollection<S>,
199 CaseReducer<S, Action> | undefined,
200] {
201 const actionsMap: CaseReducers<S, any> = {}
202 const actionMatchers: ActionMatcherDescriptionCollection<S> = []
203 let defaultCaseReducer: CaseReducer<S, Action> | undefined
204 const builder = {
205 addCase(
206 typeOrActionCreator: string | TypedActionCreator<any>,
207 reducer: CaseReducer<S>,
208 ) {
209 if (process.env.NODE_ENV !== 'production') {
210 /*
211 to keep the definition by the user in line with actual behavior,
212 we enforce `addCase` to always be called before calling `addMatcher`
213 as matching cases take precedence over matchers
214 */
215 if (actionMatchers.length > 0) {
216 throw new Error(
217 '`builder.addCase` should only be called before calling `builder.addMatcher`',
218 )
219 }
220 if (defaultCaseReducer) {
221 throw new Error(
222 '`builder.addCase` should only be called before calling `builder.addDefaultCase`',
223 )
224 }
225 }
226 const type =
227 typeof typeOrActionCreator === 'string'
228 ? typeOrActionCreator
229 : typeOrActionCreator.type
230 if (!type) {
231 throw new Error(
232 '`builder.addCase` cannot be called with an empty action type',
233 )
234 }
235 if (type in actionsMap) {
236 throw new Error(
237 '`builder.addCase` cannot be called with two reducers for the same action type ' +
238 `'${type}'`,
239 )
240 }
241 actionsMap[type] = reducer
242 return builder
243 },
244 addAsyncThunk<
245 Returned,
246 ThunkArg,
247 ThunkApiConfig extends AsyncThunkConfig = {},
248 >(
249 asyncThunk: AsyncThunk<Returned, ThunkArg, ThunkApiConfig>,
250 reducers: AsyncThunkReducers<S, ThunkArg, Returned, ThunkApiConfig>,
251 ) {
252 if (process.env.NODE_ENV !== 'production') {
253 // since this uses both action cases and matchers, we can't enforce the order in runtime other than checking for default case
254 if (defaultCaseReducer) {
255 throw new Error(
256 '`builder.addAsyncThunk` should only be called before calling `builder.addDefaultCase`',
257 )
258 }
259 }
260 if (reducers.pending)
261 actionsMap[asyncThunk.pending.type] = reducers.pending
262 if (reducers.rejected)
263 actionsMap[asyncThunk.rejected.type] = reducers.rejected
264 if (reducers.fulfilled)
265 actionsMap[asyncThunk.fulfilled.type] = reducers.fulfilled
266 if (reducers.settled)
267 actionMatchers.push({
268 matcher: asyncThunk.settled,
269 reducer: reducers.settled,
270 })
271 return builder
272 },
273 addMatcher<A>(
274 matcher: TypeGuard<A>,
275 reducer: CaseReducer<S, A extends Action ? A : A & Action>,
276 ) {
277 if (process.env.NODE_ENV !== 'production') {
278 if (defaultCaseReducer) {
279 throw new Error(
280 '`builder.addMatcher` should only be called before calling `builder.addDefaultCase`',
281 )
282 }
283 }
284 actionMatchers.push({ matcher, reducer })
285 return builder
286 },
287 addDefaultCase(reducer: CaseReducer<S, Action>) {
288 if (process.env.NODE_ENV !== 'production') {
289 if (defaultCaseReducer) {
290 throw new Error('`builder.addDefaultCase` can only be called once')
291 }
292 }
293 defaultCaseReducer = reducer
294 return builder
295 },
296 }
297 builderCallback(builder)
298 return [actionsMap, actionMatchers, defaultCaseReducer]
299}
Note: See TracBrowser for help on using the repository browser.