-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
add option to update provided tags #3255
Changes from all commits
6274ec8
53ce9e2
ee0cf74
3b9bef1
bd7f3ba
ca0b28e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -29,6 +29,7 @@ import { calculateProvidedByThunk } from './buildThunks' | |
import type { | ||
AssertTagTypes, | ||
EndpointDefinitions, | ||
FullTagDescription, | ||
QueryDefinition, | ||
} from '../endpointDefinitions' | ||
import type { Patch } from 'immer' | ||
|
@@ -125,17 +126,22 @@ export function buildSlice({ | |
}, | ||
prepare: prepareAutoBatched<QuerySubstateIdentifier>(), | ||
}, | ||
queryResultPatched( | ||
draft, | ||
{ | ||
payload: { queryCacheKey, patches }, | ||
}: PayloadAction< | ||
queryResultPatched: { | ||
reducer( | ||
draft, | ||
{ | ||
payload: { queryCacheKey, patches }, | ||
}: PayloadAction< | ||
QuerySubstateIdentifier & { patches: readonly Patch[] } | ||
> | ||
) { | ||
updateQuerySubstateIfExists(draft, queryCacheKey, (substate) => { | ||
substate.data = applyPatches(substate.data as any, patches.concat()) | ||
}) | ||
}, | ||
prepare: prepareAutoBatched< | ||
QuerySubstateIdentifier & { patches: readonly Patch[] } | ||
> | ||
) { | ||
updateQuerySubstateIfExists(draft, queryCacheKey, (substate) => { | ||
substate.data = applyPatches(substate.data as any, patches.concat()) | ||
}) | ||
>(), | ||
}, | ||
}, | ||
extraReducers(builder) { | ||
|
@@ -325,7 +331,42 @@ export function buildSlice({ | |
const invalidationSlice = createSlice({ | ||
name: `${reducerPath}/invalidation`, | ||
initialState: initialState as InvalidationState<string>, | ||
reducers: {}, | ||
reducers: { | ||
updateProvidedBy: { | ||
reducer( | ||
draft, | ||
action: PayloadAction<{ | ||
queryCacheKey: QueryCacheKey | ||
providedTags: readonly FullTagDescription<string>[] | ||
}> | ||
) { | ||
const { queryCacheKey, providedTags } = action.payload | ||
|
||
for (const tagTypeSubscriptions of Object.values(draft)) { | ||
for (const idSubscriptions of Object.values(tagTypeSubscriptions)) { | ||
const foundAt = idSubscriptions.indexOf(queryCacheKey) | ||
if (foundAt !== -1) { | ||
idSubscriptions.splice(foundAt, 1) | ||
} | ||
} | ||
} | ||
|
||
for (const { type, id } of providedTags) { | ||
const subscribedQueries = ((draft[type] ??= {})[ | ||
id || '__internal_without_id' | ||
] ??= []) | ||
const alreadySubscribed = subscribedQueries.includes(queryCacheKey) | ||
if (!alreadySubscribed) { | ||
subscribedQueries.push(queryCacheKey) | ||
} | ||
} | ||
}, | ||
prepare: prepareAutoBatched<{ | ||
queryCacheKey: QueryCacheKey | ||
providedTags: readonly FullTagDescription<string>[] | ||
}>(), | ||
}, | ||
}, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is basically a copy & paste of the extra-reducer below There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, was re-reviewing this and was about to ask about copy-pasted code, but calling the case reducer again is a nice trick. |
||
extraReducers(builder) { | ||
builder | ||
.addCase( | ||
|
@@ -371,27 +412,13 @@ export function buildSlice({ | |
) | ||
const { queryCacheKey } = action.meta.arg | ||
|
||
for (const tagTypeSubscriptions of Object.values(draft)) { | ||
for (const idSubscriptions of Object.values( | ||
tagTypeSubscriptions | ||
)) { | ||
const foundAt = idSubscriptions.indexOf(queryCacheKey) | ||
if (foundAt !== -1) { | ||
idSubscriptions.splice(foundAt, 1) | ||
} | ||
} | ||
} | ||
|
||
for (const { type, id } of providedTags) { | ||
const subscribedQueries = ((draft[type] ??= {})[ | ||
id || '__internal_without_id' | ||
] ??= []) | ||
const alreadySubscribed = | ||
subscribedQueries.includes(queryCacheKey) | ||
if (!alreadySubscribed) { | ||
subscribedQueries.push(queryCacheKey) | ||
} | ||
} | ||
invalidationSlice.caseReducers.updateProvidedBy( | ||
draft, | ||
invalidationSlice.actions.updateProvidedBy({ | ||
queryCacheKey, | ||
providedTags, | ||
}) | ||
) | ||
} | ||
) | ||
}, | ||
|
@@ -497,6 +524,7 @@ export function buildSlice({ | |
...subscriptionSlice.actions, | ||
...internalSubscriptionsSlice.actions, | ||
...mutationSlice.actions, | ||
...invalidationSlice.actions, | ||
/** @deprecated has been renamed to `removeMutationResult` */ | ||
unsubscribeMutationResult: mutationSlice.actions.removeMutationResult, | ||
resetApiState, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,6 +20,7 @@ import type { | |
QueryArgFrom, | ||
QueryDefinition, | ||
ResultTypeFrom, | ||
FullTagDescription, | ||
} from '../endpointDefinitions' | ||
import { isQueryDefinition } from '../endpointDefinitions' | ||
import { calculateProvidedBy } from '../endpointDefinitions' | ||
|
@@ -164,7 +165,8 @@ export type PatchQueryDataThunk< | |
> = <EndpointName extends QueryKeys<Definitions>>( | ||
endpointName: EndpointName, | ||
args: QueryArgFrom<Definitions[EndpointName]>, | ||
patches: readonly Patch[] | ||
patches: readonly Patch[], | ||
updateProvided?: boolean | ||
) => ThunkAction<void, PartialState, any, AnyAction> | ||
|
||
export type UpdateQueryDataThunk< | ||
|
@@ -173,7 +175,8 @@ export type UpdateQueryDataThunk< | |
> = <EndpointName extends QueryKeys<Definitions>>( | ||
endpointName: EndpointName, | ||
args: QueryArgFrom<Definitions[EndpointName]>, | ||
updateRecipe: Recipe<ResultTypeFrom<Definitions[EndpointName]>> | ||
updateRecipe: Recipe<ResultTypeFrom<Definitions[EndpointName]>>, | ||
updateProvided?: boolean | ||
) => ThunkAction<PatchCollection, PartialState, any, AnyAction> | ||
|
||
export type UpsertQueryDataThunk< | ||
|
@@ -222,57 +225,87 @@ export function buildThunks< | |
context: { endpointDefinitions }, | ||
serializeQueryArgs, | ||
api, | ||
assertTagType, | ||
}: { | ||
baseQuery: BaseQuery | ||
reducerPath: ReducerPath | ||
context: ApiContext<Definitions> | ||
serializeQueryArgs: InternalSerializeQueryArgs | ||
api: Api<BaseQuery, Definitions, ReducerPath, any> | ||
assertTagType: AssertTagTypes | ||
}) { | ||
type State = RootState<any, string, ReducerPath> | ||
|
||
const patchQueryData: PatchQueryDataThunk<EndpointDefinitions, State> = | ||
(endpointName, args, patches) => (dispatch) => { | ||
(endpointName, args, patches, updateProvided) => (dispatch, getState) => { | ||
const endpointDefinition = endpointDefinitions[endpointName] | ||
|
||
const queryCacheKey = serializeQueryArgs({ | ||
queryArgs: args, | ||
endpointDefinition, | ||
endpointName, | ||
}) | ||
|
||
dispatch( | ||
api.internalActions.queryResultPatched({ | ||
queryCacheKey: serializeQueryArgs({ | ||
queryArgs: args, | ||
endpointDefinition, | ||
endpointName, | ||
}), | ||
patches, | ||
}) | ||
api.internalActions.queryResultPatched({ queryCacheKey, patches }) | ||
) | ||
|
||
if (!updateProvided) { | ||
return | ||
} | ||
|
||
const newValue = api.endpoints[endpointName].select(args)(getState()) | ||
|
||
const providedTags = calculateProvidedBy( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, I see the merit of "we have only one code path" for moving this into
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That was a brainfart, of course we can mark them batchable - batching will not delay reducer execution, but only the resulting rerender. In that case, could you please make sure that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, cool! I did not know of that mechanism. Marked both as |
||
endpointDefinition.providesTags, | ||
newValue.data, | ||
undefined, | ||
args, | ||
{}, | ||
assertTagType | ||
) | ||
|
||
dispatch( | ||
api.internalActions.updateProvidedBy({ queryCacheKey, providedTags }) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we dispatch i went with that approach because otherwise i would have to |
||
) | ||
} | ||
|
||
const updateQueryData: UpdateQueryDataThunk<EndpointDefinitions, State> = | ||
(endpointName, args, updateRecipe) => (dispatch, getState) => { | ||
const currentState = ( | ||
api.endpoints[endpointName] as ApiEndpointQuery<any, any> | ||
).select(args)(getState()) | ||
(endpointName, args, updateRecipe, updateProvided = true) => | ||
(dispatch, getState) => { | ||
const endpointDefinition = api.endpoints[endpointName] | ||
|
||
const currentState = endpointDefinition.select(args)(getState()) | ||
|
||
let ret: PatchCollection = { | ||
patches: [], | ||
inversePatches: [], | ||
undo: () => | ||
dispatch( | ||
api.util.patchQueryData(endpointName, args, ret.inversePatches) | ||
api.util.patchQueryData( | ||
endpointName, | ||
args, | ||
ret.inversePatches, | ||
updateProvided | ||
) | ||
), | ||
} | ||
if (currentState.status === QueryStatus.uninitialized) { | ||
return ret | ||
} | ||
let newValue | ||
if ('data' in currentState) { | ||
if (isDraftable(currentState.data)) { | ||
const [, patches, inversePatches] = produceWithPatches( | ||
const [value, patches, inversePatches] = produceWithPatches( | ||
currentState.data, | ||
updateRecipe | ||
) | ||
ret.patches.push(...patches) | ||
ret.inversePatches.push(...inversePatches) | ||
newValue = value | ||
} else { | ||
const value = updateRecipe(currentState.data) | ||
ret.patches.push({ op: 'replace', path: [], value }) | ||
newValue = updateRecipe(currentState.data) | ||
ret.patches.push({ op: 'replace', path: [], value: newValue }) | ||
ret.inversePatches.push({ | ||
op: 'replace', | ||
path: [], | ||
|
@@ -281,7 +314,9 @@ export function buildThunks< | |
} | ||
} | ||
|
||
dispatch(api.util.patchQueryData(endpointName, args, ret.patches)) | ||
dispatch( | ||
api.util.patchQueryData(endpointName, args, ret.patches, updateProvided) | ||
) | ||
|
||
return ret | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Big ❤️ for the docs commit!