feat(rtk-query-monitor): add query preview tabs

This commit is contained in:
FaberVitale 2021-06-14 15:25:54 +02:00
parent 1f189f3bdc
commit 5a485a47ab
16 changed files with 582 additions and 87 deletions

View File

@ -4,9 +4,14 @@ import type { PokemonName } from '../pokemon.data';
export const pokemonApi = createApi({
reducerPath: 'pokemonApi',
baseQuery: fetchBaseQuery({ baseUrl: 'https://pokeapi.co/api/v2/' }),
tagTypes: ['pokemon'],
endpoints: (builder) => ({
getPokemonByName: builder.query({
query: (name: PokemonName) => `pokemon/${name}`,
providesTags: (result, error, name: PokemonName) => [
{ type: 'pokemon' },
{ type: 'pokemon', id: name },
],
}),
}),
});

View File

@ -6,14 +6,20 @@ import { Base16Theme } from 'react-base16-styling';
import {
QueryFormValues,
QueryInfo,
QueryPreviewTabs,
RtkQueryInspectorMonitorState,
StyleUtils,
} from './types';
import { createInspectorSelectors, computeSelectorSource } from './selectors';
import { changeQueryFormValues, selectQueryKey } from './reducers';
import {
changeQueryFormValues,
selectedPreviewTab,
selectQueryKey,
} from './reducers';
import { QueryList } from './components/QueryList';
import { StyleUtils } from './styles/createStylingFromTheme';
import { QueryForm } from './components/QueryForm';
import { QueryPreview } from './components/QueryPreview';
import { getApiStateOf, getQuerySubscriptionsOf } from './utils/rtk-query';
type SelectorsSource<S> = {
userState: S | null;
@ -104,6 +110,10 @@ class RtkQueryInspector<S, A extends Action<unknown>> extends Component<
this.props.dispatch(selectQueryKey(queryInfo) as AnyAction);
};
handleTabChange = (tab: QueryPreviewTabs): void => {
this.props.dispatch(selectedPreviewTab(tab) as AnyAction);
};
render(): ReactNode {
const { selectorsSource, isWideLayout } = this.state;
const {
@ -118,11 +128,21 @@ class RtkQueryInspector<S, A extends Action<unknown>> extends Component<
selectorsSource
);
const currentRtkApi = getApiStateOf(currentQueryInfo, apiStates);
const currentQuerySubscriptions = getQuerySubscriptionsOf(
currentQueryInfo,
apiStates
);
const currentTags = this.selectors.selectCurrentQueryTags(selectorsSource);
console.log('inspector', {
apiStates,
allVisibleQueries,
selectorsSource,
currentQueryInfo,
currentRtkApi,
currentTags,
});
return (
@ -147,8 +167,13 @@ class RtkQueryInspector<S, A extends Action<unknown>> extends Component<
/>
</div>
<QueryPreview
selectedQueryInfo={currentQueryInfo}
queryInfo={currentQueryInfo}
selectedTab={selectorsSource.monitorState.selectedPreviewTab}
onTabChange={this.handleTabChange}
styling={styling}
tags={currentTags}
querySubscriptions={currentQuerySubscriptions}
apiConfig={currentRtkApi?.config ?? null}
isWideLayout={isWideLayout}
/>
</div>

View File

@ -1,20 +1,46 @@
import React, { ReactNode } from 'react';
import JSONTree from 'react-json-tree';
import { StylingFunction } from 'react-base16-styling';
import { DATA_TYPE_KEY } from '../monitor-config';
import { StyleUtilsContext } from '../styles/createStylingFromTheme';
import { createTreeItemLabelRenderer } from '../styles/tree';
import {
getJsonTreeTheme,
StyleUtilsContext,
} from '../styles/createStylingFromTheme';
import { createTreeItemLabelRenderer, getItemString } from '../styles/tree';
import { QueryInfo } from '../types';
QueryPreviewTabOption,
QueryPreviewTabs,
QueryPreviewTabProps,
} from '../types';
import { QueryPreviewHeader } from './QueryPreviewHeader';
import { QueryPreviewInfo } from './QueryPreviewInfo';
import { QueryPreviewApiConfig } from './QueryPreviewApiConfig';
import { QueryPreviewSubscriptions } from './QueryPreviewSubscriptions';
import { QueryPreviewTags } from './QueryPreviewTags';
export interface QueryPreviewProps {
selectedQueryInfo: QueryInfo | null;
styling: StylingFunction;
isWideLayout: boolean;
export interface QueryPreviewProps
extends Omit<QueryPreviewTabProps, 'base16Theme' | 'invertTheme'> {
selectedTab: QueryPreviewTabs;
onTabChange: (tab: QueryPreviewTabs) => void;
}
const tabs: ReadonlyArray<QueryPreviewTabOption> = [
{
label: 'query',
value: QueryPreviewTabs.queryinfo,
component: QueryPreviewInfo,
},
{
label: 'tags',
value: QueryPreviewTabs.queryTags,
component: QueryPreviewTags,
},
{
label: 'subs',
value: QueryPreviewTabs.querySubscriptions,
component: QueryPreviewSubscriptions,
},
{
label: 'api',
value: QueryPreviewTabs.apiConfig,
component: QueryPreviewApiConfig,
},
];
export class QueryPreview extends React.PureComponent<QueryPreviewProps> {
readonly labelRenderer: ReturnType<typeof createTreeItemLabelRenderer>;
@ -25,68 +51,55 @@ export class QueryPreview extends React.PureComponent<QueryPreviewProps> {
}
render(): ReactNode {
const { selectedQueryInfo, isWideLayout } = this.props;
const {
queryInfo,
isWideLayout,
selectedTab,
apiConfig,
onTabChange,
querySubscriptions,
tags,
} = this.props;
if (!selectedQueryInfo) {
const { component: TabComponent } =
tabs.find((tab) => tab.value === selectedTab) || tabs[0];
if (!queryInfo) {
return (
<StyleUtilsContext.Consumer>
{({ styling }) => <div {...styling('queryPreview')} />}
{({ styling }) => (
<div {...styling('queryPreview')}>
<QueryPreviewHeader
selectedTab={selectedTab}
onTabChange={onTabChange}
tabs={tabs}
/>
</div>
)}
</StyleUtilsContext.Consumer>
);
}
const {
query: {
endpointName,
fulfilledTimeStamp,
status,
startedTimeStamp,
data,
},
reducerPath,
} = selectedQueryInfo;
const startedAt = startedTimeStamp
? new Date(startedTimeStamp).toISOString()
: '-';
const latestFetch = fulfilledTimeStamp
? new Date(fulfilledTimeStamp).toISOString()
: '-';
return (
<StyleUtilsContext.Consumer>
{({ styling, base16Theme, invertTheme }) => {
return (
<div {...styling('queryPreview')}>
<React.Fragment>
<div {...styling('previewHeader')}></div>
<ul>
<li>{`reducerPath: ${reducerPath ?? '-'}`}</li>
<li>{`endpointName: ${endpointName ?? '-'}`}</li>
<li>{`status: ${status}`}</li>
<li>{`loaded at: ${latestFetch}`}</li>
<li>{`requested at: ${startedAt}`}</li>
</ul>
<div {...styling('treeWrapper')}>
<JSONTree
data={data}
labelRenderer={this.labelRenderer}
theme={getJsonTreeTheme(base16Theme)}
invertTheme={invertTheme}
getItemString={(type, data) =>
getItemString(
styling,
type,
data,
DATA_TYPE_KEY,
isWideLayout
)
}
hideRoot
/>
</div>
</React.Fragment>
<QueryPreviewHeader
selectedTab={selectedTab}
onTabChange={onTabChange}
tabs={tabs}
/>
<TabComponent
styling={styling}
base16Theme={base16Theme}
invertTheme={invertTheme}
querySubscriptions={querySubscriptions}
queryInfo={queryInfo}
tags={tags}
apiConfig={apiConfig}
isWideLayout={isWideLayout}
/>
</div>
);
}}

View File

@ -0,0 +1,30 @@
import React, { ReactNode, PureComponent } from 'react';
import { QueryPreviewTabProps } from '../types';
import { TreeView } from './TreeView';
export class QueryPreviewApiConfig extends PureComponent<QueryPreviewTabProps> {
render(): ReactNode {
const {
queryInfo,
isWideLayout,
base16Theme,
styling,
invertTheme,
apiConfig,
} = this.props;
if (!queryInfo) {
return null;
}
return (
<TreeView
data={apiConfig}
isWideLayout={isWideLayout}
base16Theme={base16Theme}
styling={styling}
invertTheme={invertTheme}
/>
);
}
}

View File

@ -0,0 +1,54 @@
import React, { ReactNode } from 'react';
import { StyleUtilsContext } from '../styles/createStylingFromTheme';
import { QueryPreviewTabOption, QueryPreviewTabs } from '../types';
import { emptyArray } from '../utils/object';
export interface QueryPreviewHeaderProps {
tabs: ReadonlyArray<QueryPreviewTabOption>;
onTabChange: (tab: QueryPreviewTabs) => void;
selectedTab: QueryPreviewTabs;
}
export class QueryPreviewHeader extends React.Component<
QueryPreviewHeaderProps
> {
handleTabClick = (tab: QueryPreviewTabOption): void => {
if (this.props.selectedTab !== tab.value) {
this.props.onTabChange(tab.value);
}
};
render(): ReactNode {
const { tabs, selectedTab } = this.props;
return (
<StyleUtilsContext.Consumer>
{({ styling }) => (
<div {...styling('previewHeader')}>
<div {...styling('tabSelector')}>
{tabs.map((tab) => (
<div
onClick={() => this.handleTabClick(tab)}
key={tab.value}
{...styling(
[
'selectorButton',
tab.value === selectedTab && 'selectorButtonSelected',
],
tab.value === selectedTab
)}
>
{tab.label}
</div>
))}
</div>
</div>
)}
</StyleUtilsContext.Consumer>
);
}
static defaultProps = {
tabs: emptyArray,
};
}

View File

@ -0,0 +1,72 @@
import { createSelector, Selector } from '@reduxjs/toolkit';
import React, { ReactNode, PureComponent } from 'react';
import { QueryInfo, QueryPreviewTabProps, RtkQueryState } from '../types';
import { identity } from '../utils/object';
import { TreeView } from './TreeView';
type ComputedQueryInfo = {
startedAt: string;
latestFetchAt: string;
};
interface FormattedQuery extends ComputedQueryInfo {
queryKey: string;
query: RtkQueryState;
}
export class QueryPreviewInfo extends PureComponent<QueryPreviewTabProps> {
selectFormattedQuery: Selector<
QueryPreviewTabProps['queryInfo'],
FormattedQuery | null
> = createSelector(
identity,
(queryInfo: QueryInfo | null): FormattedQuery | null => {
if (!queryInfo) {
return null;
}
const { query, queryKey } = queryInfo;
const startedAt = query.startedTimeStamp
? new Date(query.startedTimeStamp).toISOString()
: '-';
const latestFetchAt = query.fulfilledTimeStamp
? new Date(query.fulfilledTimeStamp).toISOString()
: '-';
return {
queryKey,
startedAt,
latestFetchAt,
query: queryInfo.query,
};
}
);
render(): ReactNode {
const {
queryInfo,
isWideLayout,
base16Theme,
styling,
invertTheme,
} = this.props;
const formattedQuery = this.selectFormattedQuery(queryInfo);
if (!formattedQuery) {
return null;
}
return (
<TreeView
data={formattedQuery}
isWideLayout={isWideLayout}
base16Theme={base16Theme}
styling={styling}
invertTheme={invertTheme}
/>
);
}
}

View File

@ -0,0 +1,34 @@
import React, { ReactNode, PureComponent } from 'react';
import { QueryPreviewTabProps } from '../types';
import { TreeView } from './TreeView';
export class QueryPreviewSubscriptions extends PureComponent<
QueryPreviewTabProps
> {
render(): ReactNode {
const {
queryInfo,
isWideLayout,
base16Theme,
styling,
invertTheme,
querySubscriptions,
} = this.props;
if (!querySubscriptions || !queryInfo) {
return null;
}
return (
<>
<TreeView
data={querySubscriptions}
isWideLayout={isWideLayout}
base16Theme={base16Theme}
styling={styling}
invertTheme={invertTheme}
/>
</>
);
}
}

View File

@ -0,0 +1,57 @@
import React, { ReactNode, PureComponent } from 'react';
import { QueryPreviewTabProps } from '../types';
import { TreeView } from './TreeView';
interface QueryPreviewTagsState {
data: { tags: QueryPreviewTabProps['tags'] };
}
export class QueryPreviewTags extends PureComponent<
QueryPreviewTabProps,
QueryPreviewTagsState
> {
static getDerivedStateFromProps(
{ tags }: QueryPreviewTabProps,
state: QueryPreviewTagsState
): QueryPreviewTagsState | null {
if (tags !== state.data.tags) {
return {
data: { tags },
};
}
return null;
}
constructor(props: QueryPreviewTabProps) {
super(props);
this.state = {
data: { tags: props.tags },
};
}
render(): ReactNode {
const {
queryInfo,
isWideLayout,
base16Theme,
styling,
invertTheme,
} = this.props;
if (!queryInfo) {
return null;
}
return (
<TreeView
data={this.state.data}
isWideLayout={isWideLayout}
base16Theme={base16Theme}
styling={styling}
invertTheme={invertTheme}
/>
);
}
}

View File

@ -0,0 +1,57 @@
import React, { ComponentProps, ReactNode } from 'react';
import JSONTree from 'react-json-tree';
import { DATA_TYPE_KEY } from '../monitor-config';
import { getJsonTreeTheme } from '../styles/createStylingFromTheme';
import { createTreeItemLabelRenderer, getItemString } from '../styles/tree';
import { StyleUtils } from '../types';
export interface TreeViewProps extends StyleUtils {
data: unknown;
isWideLayout: boolean;
before?: ReactNode;
after?: ReactNode;
children?: ReactNode;
keyPath?: ComponentProps<typeof JSONTree>['keyPath'];
}
export class TreeView extends React.PureComponent<TreeViewProps> {
readonly labelRenderer: ReturnType<typeof createTreeItemLabelRenderer>;
constructor(props: TreeViewProps) {
super(props);
this.labelRenderer = createTreeItemLabelRenderer(this.props.styling);
}
render(): ReactNode {
const {
styling,
base16Theme,
invertTheme,
isWideLayout,
data,
before,
after,
children,
keyPath,
} = this.props;
return (
<div {...styling('treeWrapper')}>
{before}
<JSONTree
keyPath={keyPath}
data={data}
labelRenderer={this.labelRenderer}
theme={getJsonTreeTheme(base16Theme)}
invertTheme={invertTheme}
getItemString={(type, data) =>
getItemString(styling, type, data, DATA_TYPE_KEY, isWideLayout)
}
hideRoot
/>
{after}
{children}
</div>
);
}
}

View File

@ -1 +1,3 @@
export const DATA_TYPE_KEY = Symbol.for('__serializedType__');
export const missingTagId = '__internal_without_id';

View File

@ -5,6 +5,7 @@ import {
QueryInfo,
RtkQueryInspectorMonitorState,
QueryFormValues,
QueryPreviewTabs,
} from './types';
import { QueryComparators } from './utils/comparators';
@ -16,6 +17,7 @@ const initialState: RtkQueryInspectorMonitorState = {
searchValue: '',
},
},
selectedPreviewTab: QueryPreviewTabs.queryinfo,
selectedQueryKey: null,
};
@ -42,6 +44,9 @@ const monitorSlice = createSlice({
reducerPath: action.payload.reducerPath,
};
},
selectedPreviewTab(state, action: PayloadAction<QueryPreviewTabs>) {
state.selectedPreviewTab = action.payload;
},
},
});
@ -53,4 +58,8 @@ export function reducer<S, A extends Action<unknown>>(
return monitorSlice.reducer(state, action);
}
export const { selectQueryKey, changeQueryFormValues } = monitorSlice.actions;
export const {
selectQueryKey,
changeQueryFormValues,
selectedPreviewTab,
} = monitorSlice.actions;

View File

@ -1,12 +1,13 @@
import { Action, createSelector, Selector } from '@reduxjs/toolkit';
import { RtkQueryInspectorProps } from './RtkQueryInspector';
import { QueryInfo, SelectorsSource } from './types';
import { QueryInfo, RtkQueryTag, SelectorsSource } from './types';
import { Comparator, queryComparators } from './utils/comparators';
import { escapeRegExpSpecialCharacter } from './utils/regexp';
import {
getApiStatesOf,
extractAllApiQueries,
flipComparator,
getQueryTagsOf,
} from './utils/rtk-query';
type InspectorSelector<S, Output> = Selector<SelectorsSource<S>, Output>;
@ -47,6 +48,7 @@ export interface InspectorSelectors<S> {
readonly selectAllVisbileQueries: InspectorSelector<S, QueryInfo[]>;
readonly selectorCurrentQueryInfo: InspectorSelector<S, QueryInfo | null>;
readonly selectSearchQueryRegex: InspectorSelector<S, RegExp | null>;
readonly selectCurrentQueryTags: InspectorSelector<S, RtkQueryTag[]>;
}
export function createInspectorSelectors<S>(): InspectorSelectors<S> {
@ -116,6 +118,12 @@ export function createInspectorSelectors<S>(): InspectorSelectors<S> {
}
);
const selectCurrentQueryTags = createSelector(
selectApiStates,
selectorCurrentQueryInfo,
(apiState, currentQueryInfo) => getQueryTagsOf(currentQueryInfo, apiState)
);
return {
selectQueryComparator,
selectApiStates,
@ -123,5 +131,6 @@ export function createInspectorSelectors<S>(): InspectorSelectors<S> {
selectAllVisbileQueries,
selectSearchQueryRegex,
selectorCurrentQueryInfo,
selectCurrentQueryTags,
};
}

View File

@ -4,13 +4,12 @@ import {
createStyling,
getBase16Theme,
invertTheme,
StylingFunction,
StylingConfig,
} from 'react-base16-styling';
import rgba from 'hex-rgba';
import * as reduxThemes from 'redux-devtools-themes';
import { Action } from 'redux';
import { RtkQueryInspectorMonitorProps } from '../types';
import { RtkQueryInspectorMonitorProps, StyleUtils } from '../types';
import { createContext } from 'react';
jss.setup(preset());
@ -319,12 +318,6 @@ export const createStylingFromTheme = createStyling(getDefaultThemeStyling, {
base16Themes: { ...reduxThemes },
});
export interface StyleUtils {
base16Theme: reduxThemes.Base16Theme;
styling: StylingFunction;
invertTheme: boolean;
}
export function createThemeState<S, A extends Action<unknown>>(
props: RtkQueryInspectorMonitorProps<S, A>
): StyleUtils {

View File

@ -1,11 +1,18 @@
import { LiftedAction, LiftedState } from '@redux-devtools/instrument';
import type { createApi } from '@reduxjs/toolkit/query';
import { Dispatch } from 'react';
import { Base16Theme } from 'react-base16-styling';
import { ComponentType, Dispatch } from 'react';
import { Base16Theme, StylingFunction } from 'react-base16-styling';
import { Action } from 'redux';
import * as themes from 'redux-devtools-themes';
import { QueryComparators } from './utils/comparators';
export enum QueryPreviewTabs {
queryinfo,
apiConfig,
querySubscriptions,
queryTags,
}
export interface QueryFormValues {
queryComparator: QueryComparators;
isAscendingQueryComparatorOrder: boolean;
@ -16,6 +23,7 @@ export interface RtkQueryInspectorMonitorState {
values: QueryFormValues;
};
readonly selectedQueryKey: Pick<QueryInfo, 'reducerPath' | 'queryKey'> | null;
readonly selectedPreviewTab: QueryPreviewTabs;
}
export interface RtkQueryInspectorMonitorProps<S, A extends Action<unknown>>
@ -42,6 +50,10 @@ export type RtkQueryState = NonNullable<
RtkQueryApiState['queries'][keyof RtkQueryApiState]
>;
export type RtkQueryApiConfig = RtkQueryApiState['config'];
export type RtkQueryProvided = RtkQueryApiState['provided'];
export interface ExternalProps<S, A extends Action<unknown>> {
dispatch: Dispatch<
Action | LiftedAction<S, A, RtkQueryInspectorMonitorState>
@ -79,3 +91,35 @@ export interface SelectorsSource<S> {
userState: S | null;
monitorState: RtkQueryInspectorMonitorState;
}
export interface StyleUtils {
readonly base16Theme: Base16Theme;
readonly styling: StylingFunction;
readonly invertTheme: boolean;
}
export type RTKQuerySubscribers = NonNullable<
RtkQueryApiState['subscriptions'][keyof RtkQueryApiState['subscriptions']]
>;
export interface RtkQueryTag {
type: string;
id?: number | string;
}
export interface QueryPreviewTabProps extends StyleUtils {
queryInfo: QueryInfo | null;
apiConfig: RtkQueryApiState['config'] | null;
querySubscriptions: RTKQuerySubscribers | null;
isWideLayout: boolean;
tags: RtkQueryTag[];
}
export interface TabOption<S, P> extends SelectOption<S> {
component: ComponentType<P>;
}
export type QueryPreviewTabOption = TabOption<
QueryPreviewTabs,
QueryPreviewTabProps
>;

View File

@ -10,13 +10,15 @@ export enum QueryComparators {
queryKey = 'key',
status = 'status',
endpointName = 'endpointName',
apiReducerPath = 'apiReducerPath',
}
export const sortQueryOptions: SelectOption<QueryComparators>[] = [
{ label: 'fulfilledTimeStamp', value: QueryComparators.fulfilledTimeStamp },
{ label: 'query key', value: QueryComparators.queryKey },
{ label: 'status ', value: QueryComparators.status },
{ label: 'status', value: QueryComparators.status },
{ label: 'endpoint', value: QueryComparators.endpointName },
{ label: 'reducerPath', value: QueryComparators.apiReducerPath },
];
function sortQueryByFulfilled(
@ -46,7 +48,10 @@ function sortQueryByStatus(
return thisTerm - thatTerm;
}
function compareStrings(a: string, b: string): number {
function compareJSONPrimitive<T extends string | number | boolean | null>(
a: T,
b: T
): number {
if (a === b) {
return 0;
}
@ -58,7 +63,7 @@ function sortByQueryKey(
thisQueryInfo: QueryInfo,
thatQueryInfo: QueryInfo
): number {
return compareStrings(thisQueryInfo.queryKey, thatQueryInfo.queryKey);
return compareJSONPrimitive(thisQueryInfo.queryKey, thatQueryInfo.queryKey);
}
function sortQueryByEndpointName(
@ -68,7 +73,17 @@ function sortQueryByEndpointName(
const thisEndpointName = thisQueryInfo.query.endpointName ?? '';
const thatEndpointName = thatQueryInfo.query.endpointName ?? '';
return compareStrings(thisEndpointName, thatEndpointName);
return compareJSONPrimitive(thisEndpointName, thatEndpointName);
}
function sortByApiReducerPath(
thisQueryInfo: QueryInfo,
thatQueryInfo: QueryInfo
): number {
return compareJSONPrimitive(
thisQueryInfo.reducerPath,
thatQueryInfo.reducerPath
);
}
export const queryComparators: Readonly<Record<
@ -79,4 +94,5 @@ export const queryComparators: Readonly<Record<
[QueryComparators.status]: sortQueryByStatus,
[QueryComparators.endpointName]: sortQueryByEndpointName,
[QueryComparators.queryKey]: sortByQueryKey,
[QueryComparators.apiReducerPath]: sortByApiReducerPath,
};

View File

@ -1,13 +1,15 @@
import { isPlainObject } from '@reduxjs/toolkit';
import type { createApi } from '@reduxjs/toolkit/query';
import { QueryInfo, RtkQueryInspectorMonitorState } from '../types';
import {
QueryInfo,
RtkQueryInspectorMonitorState,
RtkQueryApiState,
RTKQuerySubscribers,
RtkQueryTag,
} from '../types';
import { missingTagId } from '../monitor-config';
import { Comparator } from './comparators';
import { emptyArray } from './object';
export type RtkQueryApiState = ReturnType<
ReturnType<typeof createApi>['reducer']
>;
const rtkqueryApiStateKeys: ReadonlyArray<keyof RtkQueryApiState> = [
'queries',
'mutations',
@ -107,3 +109,76 @@ export function isQuerySelected(
selectedQueryKey.reducerPath === queryInfo.reducerPath
);
}
export function getApiStateOf(
queryInfo: QueryInfo | null,
apiStates: ReturnType<typeof getApiStatesOf>
): RtkQueryApiState | null {
if (!apiStates || !queryInfo) {
return null;
}
return apiStates[queryInfo.reducerPath] ?? null;
}
export function getQuerySubscriptionsOf(
queryInfo: QueryInfo | null,
apiStates: ReturnType<typeof getApiStatesOf>
): RTKQuerySubscribers | null {
if (!apiStates || !queryInfo) {
return null;
}
return (
apiStates[queryInfo.reducerPath]?.subscriptions?.[queryInfo.queryKey] ??
null
);
}
export function getProvidedOf(
queryInfo: QueryInfo | null,
apiStates: ReturnType<typeof getApiStatesOf>
): RtkQueryApiState['provided'] | null {
if (!apiStates || !queryInfo) {
return null;
}
return apiStates[queryInfo.reducerPath]?.provided ?? null;
}
export function getQueryTagsOf(
queryInfo: QueryInfo | null,
apiStates: ReturnType<typeof getApiStatesOf>
): RtkQueryTag[] {
if (!apiStates || !queryInfo) {
return emptyArray;
}
const provided = apiStates[queryInfo.reducerPath].provided;
const tagTypes = Object.keys(provided);
console.log({ tagTypes, provided });
if (tagTypes.length < 1) {
return emptyArray;
}
const output: RtkQueryTag[] = [];
for (const [type, tagIds] of Object.entries(provided)) {
if (tagIds) {
for (const [id, queryKeys] of Object.entries(tagIds)) {
if (queryKeys.includes(queryInfo.queryKey as any)) {
const tag: RtkQueryTag = { type };
if (id !== missingTagId) {
tag.id = id;
}
output.push(tag);
}
}
}
}
return output;
}