Skip to content

Commit

Permalink
add autoBatchEnhancer
Browse files Browse the repository at this point in the history
  • Loading branch information
phryneas committed Aug 21, 2022
1 parent ed3d162 commit 7384e3d
Show file tree
Hide file tree
Showing 8 changed files with 124 additions and 49 deletions.
45 changes: 45 additions & 0 deletions packages/toolkit/src/autoBatchEnhancer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import type { StoreEnhancer } from 'redux'

export const autoBatch = 'ReduxToolkit_autoBatch'

export const autoBatchEnhancer =
(batchTimeout: number = -1): StoreEnhancer =>
(next) =>
(...args) => {
const store = next(...args)

let notifying = true
let nextNotification: NodeJS.Timeout | undefined = undefined
const listeners = new Set<() => void>()
const notifyListeners = () => {
nextNotification = void listeners.forEach((l) => l())
}

const subscribe: typeof store.subscribe = (listener) => {
const wrappedListener: typeof listener = () => notifying && listener()
const unsubscribe = store.subscribe(wrappedListener)
listeners.add(listener)
return () => {
unsubscribe()
listeners.delete(listener)
}
}

const dispatch: typeof store.dispatch = (action: any) => {
try {
notifying = !action?.meta?.[autoBatch]
if (notifying) {
if (nextNotification) {
nextNotification = void clearTimeout(nextNotification)
}
} else {
nextNotification ||= setTimeout(notifyListeners, batchTimeout) as any
}
return store.dispatch(action)
} finally {
notifying = true
}
}

return Object.assign({}, store, { dispatch, subscribe })
}
6 changes: 3 additions & 3 deletions packages/toolkit/src/configureStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ const IS_PRODUCTION = process.env.NODE_ENV === 'production'
* @public
*/
export type ConfigureEnhancersCallback<E extends Enhancers = Enhancers> = (
defaultEnhancers: readonly StoreEnhancer[]
defaultEnhancers: readonly StoreEnhancer[]
) => [...E]

/**
Expand Down Expand Up @@ -104,10 +104,10 @@ type Middlewares<S> = ReadonlyArray<Middleware<{}, S>>

type Enhancers = ReadonlyArray<StoreEnhancer>

interface ToolkitStore<
export interface ToolkitStore<
S = any,
A extends Action = AnyAction,
M extends Middlewares<S> = Middlewares<S>,
M extends Middlewares<S> = Middlewares<S>
> extends Store<S, A> {
/**
* The `dispatch` method of your store, enhanced by all its middlewares.
Expand Down
1 change: 1 addition & 0 deletions packages/toolkit/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export type {
} from 'reselect'
export { createDraftSafeSelector } from './createDraftSafeSelector'
export type { ThunkAction, ThunkDispatch, ThunkMiddleware } from 'redux-thunk'
export { autoBatch, autoBatchEnhancer } from './autoBatchEnhancer'

// We deliberately enable Immer's ES5 support, on the grounds that
// we assume RTK will be used with React Native and other Proxy-less
Expand Down
2 changes: 1 addition & 1 deletion packages/toolkit/src/query/core/buildMiddleware/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export function buildMiddleware<
buildWindowEventHandling,
buildCacheLifecycle,
buildQueryLifecycle,
buildBatchActions,
// buildBatchActions,
].map((build) =>
build({
...(input as any as BuildMiddlewareInput<
Expand Down
89 changes: 53 additions & 36 deletions packages/toolkit/src/query/core/buildSlice.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { AnyAction, PayloadAction } from '@reduxjs/toolkit'
import {
autoBatch,
combineReducers,
createAction,
createSlice,
Expand Down Expand Up @@ -84,6 +85,12 @@ function updateMutationSubstateIfExists(
}

const initialState = {} as any
const prepareAutoBatched =
<T>() =>
(payload: T): { payload: T; meta: unknown } => ({
payload,
meta: { [autoBatch]: true },
})

export function buildSlice({
reducerPath,
Expand Down Expand Up @@ -113,11 +120,14 @@ export function buildSlice({
name: `${reducerPath}/queries`,
initialState: initialState as QueryState<any>,
reducers: {
removeQueryResult(
draft,
{ payload: { queryCacheKey } }: PayloadAction<QuerySubstateIdentifier>
) {
delete draft[queryCacheKey]
removeQueryResult: {
reducer(
draft,
{ payload: { queryCacheKey } }: PayloadAction<QuerySubstateIdentifier>
) {
delete draft[queryCacheKey]
},
prepare: prepareAutoBatched<QuerySubstateIdentifier>(),
},
queryResultPatched(
draft,
Expand Down Expand Up @@ -231,14 +241,14 @@ export function buildSlice({
name: `${reducerPath}/mutations`,
initialState: initialState as MutationState<any>,
reducers: {
removeMutationResult(
draft,
{ payload }: PayloadAction<MutationSubstateIdentifier>
) {
const cacheKey = getMutationCacheKey(payload)
if (cacheKey in draft) {
delete draft[cacheKey]
}
removeMutationResult: {
reducer(draft, { payload }: PayloadAction<MutationSubstateIdentifier>) {
const cacheKey = getMutationCacheKey(payload)
if (cacheKey in draft) {
delete draft[cacheKey]
}
},
prepare: prepareAutoBatched<MutationSubstateIdentifier>(),
},
},
extraReducers(builder) {
Expand Down Expand Up @@ -357,35 +367,42 @@ export function buildSlice({
},
})

type SubscriptionUpdate = {
endpointName: string
requestId: string
options: Subscribers[number]
} & QuerySubstateIdentifier
const subscriptionSlice = createSlice({
name: `${reducerPath}/subscriptions`,
initialState: initialState as SubscriptionState,
reducers: {
updateSubscriptionOptions(
draft,
{
payload: { queryCacheKey, requestId, options },
}: PayloadAction<
updateSubscriptionOptions: {
reducer(
draft,
{
endpointName: string
requestId: string
options: Subscribers[number]
} & QuerySubstateIdentifier
>
) {
if (draft?.[queryCacheKey]?.[requestId]) {
draft[queryCacheKey]![requestId] = options
}
payload: { queryCacheKey, requestId, options },
}: PayloadAction<SubscriptionUpdate>
) {
if (draft?.[queryCacheKey]?.[requestId]) {
draft[queryCacheKey]![requestId] = options
}
},
prepare: prepareAutoBatched<SubscriptionUpdate>(),
},
unsubscribeQueryResult(
draft,
{
payload: { queryCacheKey, requestId },
}: PayloadAction<{ requestId: string } & QuerySubstateIdentifier>
) {
if (draft[queryCacheKey]) {
delete draft[queryCacheKey]![requestId]
}
unsubscribeQueryResult: {
reducer(
draft,
{
payload: { queryCacheKey, requestId },
}: PayloadAction<{ requestId: string } & QuerySubstateIdentifier>
) {
if (draft[queryCacheKey]) {
delete draft[queryCacheKey]![requestId]
}
},
prepare: prepareAutoBatched<
{ requestId: string } & QuerySubstateIdentifier
>(),
},
subscriptionRequestsRejected(
draft,
Expand Down
16 changes: 11 additions & 5 deletions packages/toolkit/src/query/core/buildThunks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import type {
ThunkDispatch,
AsyncThunk,
} from '@reduxjs/toolkit'
import { createAsyncThunk } from '@reduxjs/toolkit'
import { createAsyncThunk, autoBatch } from '@reduxjs/toolkit'

import { HandledError } from '../HandledError'

Expand Down Expand Up @@ -118,13 +118,18 @@ export interface MutationThunkArg {
export type ThunkResult = unknown

export type ThunkApiMetaConfig = {
pendingMeta: { startedTimeStamp: number }
pendingMeta: {
startedTimeStamp: number
[autoBatch]: true
}
fulfilledMeta: {
fulfilledTimeStamp: number
baseQueryMeta: unknown
[autoBatch]: true
}
rejectedMeta: {
baseQueryMeta: unknown
[autoBatch]: true
}
}
export type QueryThunk = AsyncThunk<
Expand Down Expand Up @@ -350,6 +355,7 @@ export function buildThunks<
{
fulfilledTimeStamp: Date.now(),
baseQueryMeta: result.meta,
[autoBatch]: true,
}
)
} catch (error) {
Expand All @@ -374,7 +380,7 @@ export function buildThunks<
catchedError.meta,
arg.originalArgs
),
{ baseQueryMeta: catchedError.meta }
{ baseQueryMeta: catchedError.meta, [autoBatch]: true }
)
} catch (e) {
catchedError = e
Expand Down Expand Up @@ -424,7 +430,7 @@ In the case of an unhandled error, no tags will be "provided" or "invalidated".`
ThunkApiMetaConfig & { state: RootState<any, string, ReducerPath> }
>(`${reducerPath}/executeQuery`, executeEndpoint, {
getPendingMeta() {
return { startedTimeStamp: Date.now() }
return { startedTimeStamp: Date.now(), [autoBatch]: true }
},
condition(arg, { getState }) {
const state = getState()
Expand Down Expand Up @@ -453,7 +459,7 @@ In the case of an unhandled error, no tags will be "provided" or "invalidated".`
ThunkApiMetaConfig & { state: RootState<any, string, ReducerPath> }
>(`${reducerPath}/executeMutation`, executeEndpoint, {
getPendingMeta() {
return { startedTimeStamp: Date.now() }
return { startedTimeStamp: Date.now(), [autoBatch]: true }
},
})

Expand Down
3 changes: 2 additions & 1 deletion packages/toolkit/src/query/react/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,8 @@ export const reactHooksModule = ({
context,
})
safeAssign(anyApi, { usePrefetch })
safeAssign(context, { batch })
// even with React batching completely out of the picture, we should get similar results now
// safeAssign(context, { batch })

return {
injectEndpoint(endpointName, definition) {
Expand Down
11 changes: 8 additions & 3 deletions packages/toolkit/src/query/tests/helpers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type {
Middleware,
Store,
} from '@reduxjs/toolkit'
import { configureStore } from '@reduxjs/toolkit'
import { configureStore, autoBatchEnhancer } from '@reduxjs/toolkit'
import { setupListeners } from '@reduxjs/toolkit/query'
import type { Reducer } from 'react'
import React, { useCallback } from 'react'
Expand Down Expand Up @@ -176,9 +176,13 @@ export function setupApiStore<
>(
api: A,
extraReducers?: R,
options: { withoutListeners?: boolean; withoutTestLifecycles?: boolean, middleware?: Middleware[] } = {}
options: {
withoutListeners?: boolean
withoutTestLifecycles?: boolean
middleware?: Middleware[]
} = {}
) {
const { middleware = [] } = options;
const { middleware = [] } = options
const getStore = () =>
configureStore({
reducer: { api: api.reducer, ...extraReducers },
Expand All @@ -187,6 +191,7 @@ export function setupApiStore<
api.middleware,
...middleware
),
enhancers: (e) => e.concat(autoBatchEnhancer(0)),
})

type StoreType = EnhancedStore<
Expand Down

0 comments on commit 7384e3d

Please sign in to comment.