Handle api.provided state changes in RTKQ 2.6.2 (#1848)

* Handle api.provided state changes in RTKQ 2.6.2

* Create little-melons-grow.md

---------

Co-authored-by: Nathan Bierema <nbierema@gmail.com>
This commit is contained in:
Mark Erikson 2025-04-02 11:46:52 -07:00 committed by GitHub
parent 73a01cc5a7
commit 17b55ef99f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 64 additions and 10 deletions

View File

@ -0,0 +1,5 @@
---
'@redux-devtools/rtk-query-monitor': minor
---
Handle api.provided state changes in RTKQ 2.6.2

View File

@ -6,9 +6,10 @@ import {
RtkQueryApiState, RtkQueryApiState,
RtkQueryTag, RtkQueryTag,
SelectorsSource, SelectorsSource,
RtkQueryProvided, RtkQueryProvidedTagsState,
QueryPreviewTabs, QueryPreviewTabs,
RtkResourceInfo, RtkResourceInfo,
RtkQuery262ProvidedState,
} from './types'; } from './types';
import { Comparator, queryComparators } from './utils/comparators'; import { Comparator, queryComparators } from './utils/comparators';
import { FilterList, queryListFilters } from './utils/filters'; import { FilterList, queryListFilters } from './utils/filters';
@ -216,7 +217,7 @@ export function createInspectorSelectors<S>(): InspectorSelectors<S> {
const selectProvidedOfCurrentQuery: InspectorSelector< const selectProvidedOfCurrentQuery: InspectorSelector<
S, S,
null | RtkQueryProvided null | RtkQueryProvidedTagsState | RtkQuery262ProvidedState
> = (selectorsSource: SelectorsSource<S>) => { > = (selectorsSource: SelectorsSource<S>) => {
return selectApiOfCurrentQuery(selectorsSource)?.provided ?? null; return selectApiOfCurrentQuery(selectorsSource)?.provided ?? null;
}; };

View File

@ -1,5 +1,9 @@
import type { LiftedAction, LiftedState } from '@redux-devtools/core'; import type { LiftedAction, LiftedState } from '@redux-devtools/core';
import type { createApi, QueryStatus } from '@reduxjs/toolkit/query'; import type {
createApi,
QueryCacheKey,
QueryStatus,
} from '@reduxjs/toolkit/query';
import type { Action, AnyAction, Dispatch } from '@reduxjs/toolkit'; import type { Action, AnyAction, Dispatch } from '@reduxjs/toolkit';
import type { ComponentType } from 'react'; import type { ComponentType } from 'react';
import { base16Themes } from 'react-base16-styling'; import { base16Themes } from 'react-base16-styling';
@ -52,7 +56,37 @@ export type RtkMutationState = NonNullable<
export type RtkQueryApiConfig = RtkQueryApiState['config']; export type RtkQueryApiConfig = RtkQueryApiState['config'];
export type RtkQueryProvided = RtkQueryApiState['provided']; export type FullTagDescription<TagType> = {
type: TagType;
id?: number | string;
};
// This is the actual tags structure, and was the entire `api.provided`
// field up through 2.6.1
export type RtkQueryProvidedTagsState = {
[x: string]: {
[id: string]: QueryCacheKey[];
[id: number]: QueryCacheKey[];
};
};
// As of 2.6.2, the `api.provided` field is split into `tags` and `keys` fields,
// with the old data nested in `tags`.
export type RtkQuery262ProvidedState = {
keys: Record<QueryCacheKey, FullTagDescription<any>[]>;
tags: RtkQueryProvidedTagsState;
};
export function isRtkQuery262Provided(
provided: Record<string, unknown>,
): provided is RtkQuery262ProvidedState {
return (
'tags' in provided &&
'keys' in provided &&
typeof provided.tags === 'object' &&
typeof provided.keys === 'object'
);
}
export interface ExternalProps<S, A extends Action<string>> { export interface ExternalProps<S, A extends Action<string>> {
dispatch: Dispatch<Action | LiftedAction<S, A, RtkQueryMonitorState>>; dispatch: Dispatch<Action | LiftedAction<S, A, RtkQueryMonitorState>>;

View File

@ -1,5 +1,5 @@
import { Action, AnyAction, isAllOf, isPlainObject } from '@reduxjs/toolkit'; import { Action, AnyAction, isAllOf, isPlainObject } from '@reduxjs/toolkit';
import { QueryStatus } from '@reduxjs/toolkit/query'; import { QueryCacheKey, QueryStatus } from '@reduxjs/toolkit/query';
import { import {
QueryInfo, QueryInfo,
RtkQueryMonitorState, RtkQueryMonitorState,
@ -11,7 +11,8 @@ import {
MutationInfo, MutationInfo,
ApiStats, ApiStats,
QueryTally, QueryTally,
RtkQueryProvided, RtkQueryProvidedTagsState,
RtkQuery262ProvidedState,
ApiTimings, ApiTimings,
QueryTimings, QueryTimings,
SelectorsSource, SelectorsSource,
@ -19,6 +20,7 @@ import {
RtkResourceInfo, RtkResourceInfo,
RtkRequest, RtkRequest,
RtkRequestTiming, RtkRequestTiming,
isRtkQuery262Provided,
} from '../types'; } from '../types';
import { missingTagId } from '../monitor-config'; import { missingTagId } from '../monitor-config';
import { Comparator, compareJSONPrimitive } from './comparators'; import { Comparator, compareJSONPrimitive } from './comparators';
@ -529,13 +531,25 @@ export function getProvidedOf(
export function getQueryTagsOf( export function getQueryTagsOf(
resInfo: RtkResourceInfo | null, resInfo: RtkResourceInfo | null,
provided: RtkQueryProvided | null, provided: RtkQueryProvidedTagsState | RtkQuery262ProvidedState | null,
): RtkQueryTag[] { ): RtkQueryTag[] {
if (!resInfo || resInfo.type === 'mutation' || !provided) { if (!resInfo || resInfo.type === 'mutation' || !provided) {
return emptyArray; return emptyArray;
} }
const tagTypes = Object.keys(provided); // Handle `api.provided` schema change with RTK Query tag handling.
// Originally, `api.provided` was a `Record<string, Record<string, string[]>>`,
// directly containing the tag names.
// With https://github.com/reduxjs/redux-toolkit/pull/4910 , that changes to
// change the top level to be `{tags, keys}`, with `tags` containing the tag names.
// Handle the newer structure by extracting the right field if it exists.
const actualProvided: RtkQueryProvidedTagsState = isRtkQuery262Provided(
provided,
)
? provided.tags
: provided;
const tagTypes = Object.keys(actualProvided);
if (tagTypes.length < 1) { if (tagTypes.length < 1) {
return emptyArray; return emptyArray;
@ -543,10 +557,10 @@ export function getQueryTagsOf(
const output: RtkQueryTag[] = []; const output: RtkQueryTag[] = [];
for (const [type, tagIds] of Object.entries(provided)) { for (const [type, tagIds] of Object.entries(actualProvided)) {
if (tagIds) { if (tagIds) {
for (const [id, queryKeys] of Object.entries(tagIds)) { for (const [id, queryKeys] of Object.entries(tagIds)) {
if ((queryKeys as unknown[]).includes(resInfo.queryKey)) { if (queryKeys.includes(resInfo.queryKey as QueryCacheKey)) {
const tag: RtkQueryTag = { type }; const tag: RtkQueryTag = { type };
if (id !== missingTagId) { if (id !== missingTagId) {