UNPKG

@reduxjs/toolkit

Version:

The official, opinionated, batteries-included toolset for efficient Redux development

163 lines (142 loc) 5.36 kB
import type { QueryThunk, RejectedAction } from '../buildThunks' import type { InternalHandlerBuilder } from './types' import type { SubscriptionState, QuerySubstateIdentifier, Subscribers, } from '../apiState' import { produceWithPatches } from 'immer' import type { AnyAction } from '@reduxjs/toolkit'; import { createSlice, PayloadAction } from '@reduxjs/toolkit' // Copied from https://github.com/feross/queue-microtask let promise: Promise<any> const queueMicrotaskShim = typeof queueMicrotask === 'function' ? queueMicrotask.bind( typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : globalThis ) : // reuse resolved promise, and allocate it lazily (cb: () => void) => (promise || (promise = Promise.resolve())).then(cb).catch((err: any) => setTimeout(() => { throw err }, 0) ) export const buildBatchedActionsHandler: InternalHandlerBuilder< [actionShouldContinue: boolean, subscriptionExists: boolean] > = ({ api, queryThunk, internalState }) => { const subscriptionsPrefix = `${api.reducerPath}/subscriptions` let previousSubscriptions: SubscriptionState = null as unknown as SubscriptionState let dispatchQueued = false const { updateSubscriptionOptions, unsubscribeQueryResult } = api.internalActions // Actually intentionally mutate the subscriptions state used in the middleware // This is done to speed up perf when loading many components const actuallyMutateSubscriptions = ( mutableState: SubscriptionState, action: AnyAction ) => { if (updateSubscriptionOptions.match(action)) { const { queryCacheKey, requestId, options } = action.payload if (mutableState?.[queryCacheKey]?.[requestId]) { mutableState[queryCacheKey]![requestId] = options } return true } if (unsubscribeQueryResult.match(action)) { const { queryCacheKey, requestId } = action.payload if (mutableState[queryCacheKey]) { delete mutableState[queryCacheKey]![requestId] } return true } if (api.internalActions.removeQueryResult.match(action)) { delete mutableState[action.payload.queryCacheKey] return true } if (queryThunk.pending.match(action)) { const { meta: { arg, requestId }, } = action if (arg.subscribe) { const substate = (mutableState[arg.queryCacheKey] ??= {}) substate[requestId] = arg.subscriptionOptions ?? substate[requestId] ?? {} return true } } if (queryThunk.rejected.match(action)) { const { meta: { condition, arg, requestId }, } = action if (condition && arg.subscribe) { const substate = (mutableState[arg.queryCacheKey] ??= {}) substate[requestId] = arg.subscriptionOptions ?? substate[requestId] ?? {} return true } } return false } return (action, mwApi) => { if (!previousSubscriptions) { // Initialize it the first time this handler runs previousSubscriptions = JSON.parse( JSON.stringify(internalState.currentSubscriptions) ) } if (api.util.resetApiState.match(action)) { previousSubscriptions = internalState.currentSubscriptions = {} return [true, false] } // Intercept requests by hooks to see if they're subscribed // Necessary because we delay updating store state to the end of the tick if (api.internalActions.internal_probeSubscription.match(action)) { const { queryCacheKey, requestId } = action.payload const hasSubscription = !!internalState.currentSubscriptions[queryCacheKey]?.[requestId] return [false, hasSubscription] } // Update subscription data based on this action const didMutate = actuallyMutateSubscriptions( internalState.currentSubscriptions, action ) if (didMutate) { if (!dispatchQueued) { queueMicrotaskShim(() => { // Deep clone the current subscription data const newSubscriptions: SubscriptionState = JSON.parse( JSON.stringify(internalState.currentSubscriptions) ) // Figure out a smaller diff between original and current const [, patches] = produceWithPatches( previousSubscriptions, () => newSubscriptions ) // Sync the store state for visibility mwApi.next(api.internalActions.subscriptionsUpdated(patches)) // Save the cloned state for later reference previousSubscriptions = newSubscriptions dispatchQueued = false }) dispatchQueued = true } const isSubscriptionSliceAction = !!action.type?.startsWith(subscriptionsPrefix) const isAdditionalSubscriptionAction = queryThunk.rejected.match(action) && action.meta.condition && !!action.meta.arg.subscribe const actionShouldContinue = !isSubscriptionSliceAction && !isAdditionalSubscriptionAction return [actionShouldContinue, false] } return [true, false] } }