refactor(rtk-query): reduce selector computations

Other changes:

* simplify TreeView props
This commit is contained in:
FaberVitale 2021-06-24 18:13:59 +02:00
parent ad02c0ab26
commit 71b483bf02
11 changed files with 239 additions and 261 deletions

View File

@ -2,51 +2,101 @@ import React, { ReactNode } from 'react';
import { StyleUtilsContext } from '../styles/createStylingFromTheme'; import { StyleUtilsContext } from '../styles/createStylingFromTheme';
import { createTreeItemLabelRenderer } from '../styles/tree'; import { createTreeItemLabelRenderer } from '../styles/tree';
import { import {
QueryPreviewTabOption,
QueryPreviewTabs, QueryPreviewTabs,
QueryPreviewTabProps, QueryInfo,
SelectorsSource,
TabOption,
} from '../types'; } from '../types';
import { QueryPreviewHeader } from './QueryPreviewHeader'; import { QueryPreviewHeader } from './QueryPreviewHeader';
import { QueryPreviewInfo } from './QueryPreviewInfo'; import { QueryPreviewInfo, QueryPreviewInfoProps } from './QueryPreviewInfo';
import { QueryPreviewApi } from './QueryPreviewApi'; import { QueryPreviewApi, QueryPreviewApiProps } from './QueryPreviewApi';
import { QueryPreviewSubscriptions } from './QueryPreviewSubscriptions'; import {
import { QueryPreviewTags } from './QueryPreviewTags'; QueryPreviewSubscriptions,
QueryPreviewSubscriptionsProps,
} from './QueryPreviewSubscriptions';
import { QueryPreviewTags, QueryPreviewTagsProps } from './QueryPreviewTags';
import { NoRtkQueryApi } from './NoRtkQueryApi'; import { NoRtkQueryApi } from './NoRtkQueryApi';
import { InspectorSelectors } from '../selectors';
import { StylingFunction } from 'react-base16-styling';
import { mapProps } from '../containers/mapProps';
export interface QueryPreviewProps export interface QueryPreviewProps<S = unknown> {
extends Omit<QueryPreviewTabProps, 'base16Theme' | 'invertTheme'> { readonly selectedTab: QueryPreviewTabs;
selectedTab: QueryPreviewTabs; readonly hasNoApis: boolean;
hasNoApis: boolean; readonly onTabChange: (tab: QueryPreviewTabs) => void;
onTabChange: (tab: QueryPreviewTabs) => void; readonly queryInfo: QueryInfo | null;
readonly styling: StylingFunction;
readonly isWideLayout: boolean;
readonly selectorsSource: SelectorsSource<S>;
readonly selectors: InspectorSelectors<S>;
} }
const tabs: ReadonlyArray<QueryPreviewTabOption> = [ /**
* Tab content is not rendered if there's no selected query.
*/
type QueryPreviewTabProps = Omit<QueryPreviewProps<unknown>, 'queryInfo'> & {
queryInfo: QueryInfo;
};
const MappedQueryPreviewTags = mapProps<
QueryPreviewTabProps,
QueryPreviewTagsProps
>(({ selectors, selectorsSource, isWideLayout, queryInfo }) => ({
queryInfo,
tags: selectors.selectCurrentQueryTags(selectorsSource),
isWideLayout,
}))(QueryPreviewTags);
const MappedQueryPreviewInfo = mapProps<
QueryPreviewTabProps,
QueryPreviewInfoProps
>(({ queryInfo, isWideLayout }) => ({ queryInfo, isWideLayout }))(
QueryPreviewInfo
);
const MappedQuerySubscriptipns = mapProps<
QueryPreviewTabProps,
QueryPreviewSubscriptionsProps
>(({ selectors, selectorsSource, isWideLayout }) => ({
isWideLayout,
subscriptions: selectors.selectSubscriptionsOfCurrentQuery(selectorsSource),
}))(QueryPreviewSubscriptions);
const MappedApiPreview = mapProps<QueryPreviewTabProps, QueryPreviewApiProps>(
({ isWideLayout, selectors, selectorsSource }) => ({
isWideLayout,
apiState: selectors.selectApiOfCurrentQuery(selectorsSource),
apiStats: selectors.selectApiStatsOfCurrentQuery(selectorsSource),
})
)(QueryPreviewApi);
const tabs: ReadonlyArray<TabOption<QueryPreviewTabs, QueryPreviewTabProps>> = [
{ {
label: 'query', label: 'query',
value: QueryPreviewTabs.queryinfo, value: QueryPreviewTabs.queryinfo,
component: QueryPreviewInfo, component: MappedQueryPreviewInfo,
}, },
{ {
label: 'tags', label: 'tags',
value: QueryPreviewTabs.queryTags, value: QueryPreviewTabs.queryTags,
component: QueryPreviewTags, component: MappedQueryPreviewTags,
}, },
{ {
label: 'subs', label: 'subs',
value: QueryPreviewTabs.querySubscriptions, value: QueryPreviewTabs.querySubscriptions,
component: QueryPreviewSubscriptions, component: MappedQuerySubscriptipns,
}, },
{ {
label: 'api', label: 'api',
value: QueryPreviewTabs.apiConfig, value: QueryPreviewTabs.apiConfig,
component: QueryPreviewApi, component: MappedApiPreview,
}, },
]; ];
export class QueryPreview extends React.PureComponent<QueryPreviewProps> { export class QueryPreview<S> extends React.PureComponent<QueryPreviewProps<S>> {
readonly labelRenderer: ReturnType<typeof createTreeItemLabelRenderer>; readonly labelRenderer: ReturnType<typeof createTreeItemLabelRenderer>;
constructor(props: QueryPreviewProps) { constructor(props: QueryPreviewProps<S>) {
super(props); super(props);
this.labelRenderer = createTreeItemLabelRenderer(this.props.styling); this.labelRenderer = createTreeItemLabelRenderer(this.props.styling);
@ -65,40 +115,19 @@ export class QueryPreview extends React.PureComponent<QueryPreviewProps> {
return `${label} (${counterAsString})`; return `${label} (${counterAsString})`;
}; };
renderTabLabel = (tab: QueryPreviewTabOption): ReactNode => { renderTabLabel = (tab: TabOption<QueryPreviewTabs, unknown>): ReactNode => {
const { queryInfo, tags, querySubscriptions } = this.props; const { selectors, selectorsSource } = this.props;
if (queryInfo) { const tabCount = selectors.selectTabCounters(selectorsSource)[tab.value];
if (tab.value === QueryPreviewTabs.queryTags && tags.length > 0) {
return this.renderLabelWithCounter(tab.label, tags.length);
}
if ( if (tabCount > 0) {
tab.value === QueryPreviewTabs.querySubscriptions && return this.renderLabelWithCounter(tab.label, tabCount);
querySubscriptions
) {
const subsCount = Object.keys(querySubscriptions).length;
if (subsCount > 0) {
return this.renderLabelWithCounter(tab.label, subsCount);
}
}
} }
return tab.label; return tab.label;
}; };
render(): ReactNode { render(): ReactNode {
const { const { queryInfo, selectedTab, onTabChange, hasNoApis } = this.props;
queryInfo,
isWideLayout,
selectedTab,
apiState,
onTabChange,
querySubscriptions,
tags,
apiStats,
hasNoApis,
} = this.props;
const { component: TabComponent } = const { component: TabComponent } =
tabs.find((tab) => tab.value === selectedTab) || tabs[0]; tabs.find((tab) => tab.value === selectedTab) || tabs[0];
@ -111,7 +140,9 @@ export class QueryPreview extends React.PureComponent<QueryPreviewProps> {
<QueryPreviewHeader <QueryPreviewHeader
selectedTab={selectedTab} selectedTab={selectedTab}
onTabChange={onTabChange} onTabChange={onTabChange}
tabs={tabs} tabs={
tabs as ReadonlyArray<TabOption<QueryPreviewTabs, unknown>>
}
renderTabLabel={this.renderTabLabel} renderTabLabel={this.renderTabLabel}
/> />
{hasNoApis && <NoRtkQueryApi />} {hasNoApis && <NoRtkQueryApi />}
@ -123,26 +154,18 @@ export class QueryPreview extends React.PureComponent<QueryPreviewProps> {
return ( return (
<StyleUtilsContext.Consumer> <StyleUtilsContext.Consumer>
{({ styling, base16Theme, invertTheme }) => { {({ styling }) => {
return ( return (
<div {...styling('queryPreview')}> <div {...styling('queryPreview')}>
<QueryPreviewHeader <QueryPreviewHeader
selectedTab={selectedTab} selectedTab={selectedTab}
onTabChange={onTabChange} onTabChange={onTabChange}
tabs={tabs} tabs={
tabs as ReadonlyArray<TabOption<QueryPreviewTabs, unknown>>
}
renderTabLabel={this.renderTabLabel} renderTabLabel={this.renderTabLabel}
/> />
<TabComponent <TabComponent {...(this.props as QueryPreviewTabProps)} />
styling={styling}
base16Theme={base16Theme}
invertTheme={invertTheme}
querySubscriptions={querySubscriptions}
queryInfo={queryInfo}
tags={tags}
apiState={apiState}
isWideLayout={isWideLayout}
apiStats={apiStats}
/>
</div> </div>
); );
}} }}

View File

@ -1,18 +1,19 @@
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import React, { ReactNode, PureComponent } from 'react'; import React, { ReactNode, PureComponent } from 'react';
import { QueryPreviewTabProps } from '../types'; import { ApiStats, RtkQueryApiState } from '../types';
import { TreeView } from './TreeView'; import { TreeView } from './TreeView';
interface TreeDisplayed { export interface QueryPreviewApiProps {
reducerPath: string; apiStats: ApiStats | null;
api: QueryPreviewTabProps['apiState']; apiState: RtkQueryApiState | null;
stats: QueryPreviewTabProps['apiStats']; isWideLayout: boolean;
} }
export class QueryPreviewApi extends PureComponent<QueryPreviewTabProps> {
export class QueryPreviewApi extends PureComponent<QueryPreviewApiProps> {
selectData = createSelector( selectData = createSelector(
[ [
({ apiState }: QueryPreviewTabProps) => apiState, ({ apiState }: QueryPreviewApiProps) => apiState,
({ apiStats }: QueryPreviewTabProps) => apiStats, ({ apiStats }: QueryPreviewApiProps) => apiStats,
], ],
(apiState, apiStats) => ({ (apiState, apiStats) => ({
reducerPath: apiState?.config?.reducerPath ?? null, reducerPath: apiState?.config?.reducerPath ?? null,
@ -22,20 +23,10 @@ export class QueryPreviewApi extends PureComponent<QueryPreviewTabProps> {
); );
render(): ReactNode { render(): ReactNode {
const { queryInfo, isWideLayout, base16Theme, styling, invertTheme } =
this.props;
if (!queryInfo) {
return null;
}
return ( return (
<TreeView <TreeView
data={this.selectData(this.props)} data={this.selectData(this.props)}
isWideLayout={isWideLayout} isWideLayout={this.props.isWideLayout}
base16Theme={base16Theme}
styling={styling}
invertTheme={invertTheme}
/> />
); );
} }

View File

@ -1,17 +1,17 @@
import React, { ReactNode } from 'react'; import React, { ReactNode } from 'react';
import { StyleUtilsContext } from '../styles/createStylingFromTheme'; import { StyleUtilsContext } from '../styles/createStylingFromTheme';
import { QueryPreviewTabOption, QueryPreviewTabs } from '../types'; import { QueryPreviewTabs, TabOption } from '../types';
import { emptyArray } from '../utils/object'; import { emptyArray } from '../utils/object';
export interface QueryPreviewHeaderProps { export interface QueryPreviewHeaderProps {
tabs: ReadonlyArray<QueryPreviewTabOption>; tabs: ReadonlyArray<TabOption<QueryPreviewTabs, unknown>>;
onTabChange: (tab: QueryPreviewTabs) => void; onTabChange: (tab: QueryPreviewTabs) => void;
selectedTab: QueryPreviewTabs; selectedTab: QueryPreviewTabs;
renderTabLabel?: (tab: QueryPreviewTabOption) => ReactNode; renderTabLabel?: (tab: QueryPreviewHeaderProps['tabs'][number]) => ReactNode;
} }
export class QueryPreviewHeader extends React.Component<QueryPreviewHeaderProps> { export class QueryPreviewHeader extends React.Component<QueryPreviewHeaderProps> {
handleTabClick = (tab: QueryPreviewTabOption): void => { handleTabClick = (tab: QueryPreviewHeaderProps['tabs'][number]): void => {
if (this.props.selectedTab !== tab.value) { if (this.props.selectedTab !== tab.value) {
this.props.onTabChange(tab.value); this.props.onTabChange(tab.value);
} }

View File

@ -1,11 +1,6 @@
import { createSelector, Selector } from '@reduxjs/toolkit'; import { createSelector, Selector } from '@reduxjs/toolkit';
import React, { ReactNode, PureComponent } from 'react'; import React, { ReactNode, PureComponent } from 'react';
import { import { QueryInfo, RtkQueryState, RTKStatusFlags } from '../types';
QueryInfo,
QueryPreviewTabProps,
RtkQueryState,
RTKStatusFlags,
} from '../types';
import { identity } from '../utils/object'; import { identity } from '../utils/object';
import { getQueryStatusFlags } from '../utils/rtk-query'; import { getQueryStatusFlags } from '../utils/rtk-query';
import { TreeView } from './TreeView'; import { TreeView } from './TreeView';
@ -22,17 +17,14 @@ interface FormattedQuery extends ComputedQueryInfo {
query: RtkQueryState; query: RtkQueryState;
} }
export class QueryPreviewInfo extends PureComponent<QueryPreviewTabProps> { export interface QueryPreviewInfoProps {
selectFormattedQuery: Selector< queryInfo: QueryInfo;
QueryPreviewTabProps['queryInfo'], isWideLayout: boolean;
FormattedQuery | null
> = createSelector(
identity,
(queryInfo: QueryInfo | null): FormattedQuery | null => {
if (!queryInfo) {
return null;
} }
export class QueryPreviewInfo extends PureComponent<QueryPreviewInfoProps> {
selectFormattedQuery: Selector<QueryInfo, FormattedQuery> = createSelector(
identity,
(queryInfo: QueryInfo): FormattedQuery => {
const { query, queryKey, reducerPath } = queryInfo; const { query, queryKey, reducerPath } = queryInfo;
const startedAt = query.startedTimeStamp const startedAt = query.startedTimeStamp
@ -57,23 +49,9 @@ export class QueryPreviewInfo extends PureComponent<QueryPreviewTabProps> {
); );
render(): ReactNode { render(): ReactNode {
const { queryInfo, isWideLayout, base16Theme, styling, invertTheme } = const { queryInfo, isWideLayout } = this.props;
this.props;
const formattedQuery = this.selectFormattedQuery(queryInfo); const formattedQuery = this.selectFormattedQuery(queryInfo);
if (!formattedQuery) { return <TreeView data={formattedQuery} isWideLayout={isWideLayout} />;
return null;
}
return (
<TreeView
data={formattedQuery}
isWideLayout={isWideLayout}
base16Theme={base16Theme}
styling={styling}
invertTheme={invertTheme}
/>
);
} }
} }

View File

@ -1,60 +1,18 @@
import React, { ReactNode, PureComponent } from 'react'; import React, { ReactNode, PureComponent } from 'react';
import { QueryPreviewTabProps } from '../types'; import { RtkQueryApiState } from '../types';
import { TreeView } from './TreeView'; import { TreeView } from './TreeView';
export interface QueryPreviewSubscriptionsState { export interface QueryPreviewSubscriptionsProps {
data: { subscriptions: QueryPreviewTabProps['querySubscriptions'] }; subscriptions: RtkQueryApiState['subscriptions'][keyof RtkQueryApiState['subscriptions']];
} isWideLayout: boolean;
export class QueryPreviewSubscriptions extends PureComponent<
QueryPreviewTabProps,
QueryPreviewSubscriptionsState
> {
static getDerivedStateFromProps(
props: QueryPreviewTabProps,
state: QueryPreviewSubscriptionsState
): Partial<QueryPreviewSubscriptionsState> | null {
if (props.querySubscriptions !== state.data.subscriptions) {
return {
data: { subscriptions: props.querySubscriptions },
};
}
return null;
}
constructor(props: QueryPreviewTabProps) {
super(props);
this.state = {
data: { subscriptions: props.querySubscriptions },
};
} }
export class QueryPreviewSubscriptions extends PureComponent<QueryPreviewSubscriptionsProps> {
render(): ReactNode { render(): ReactNode {
const { const { subscriptions } = this.props;
queryInfo,
isWideLayout,
base16Theme,
styling,
invertTheme,
querySubscriptions,
} = this.props;
if (!querySubscriptions || !queryInfo) {
return null;
}
return ( return (
<> <TreeView data={subscriptions} isWideLayout={this.props.isWideLayout} />
<TreeView
data={this.state.data}
isWideLayout={isWideLayout}
base16Theme={base16Theme}
styling={styling}
invertTheme={invertTheme}
/>
</>
); );
} }
} }

View File

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

View File

@ -1,11 +1,16 @@
import { createSelector } from '@reduxjs/toolkit';
import React, { ComponentProps, ReactNode } from 'react'; import React, { ComponentProps, ReactNode } from 'react';
import JSONTree from 'react-json-tree'; import JSONTree from 'react-json-tree';
import { StylingFunction } from 'react-base16-styling';
import { DATA_TYPE_KEY } from '../monitor-config'; import { DATA_TYPE_KEY } from '../monitor-config';
import { getJsonTreeTheme } from '../styles/createStylingFromTheme'; import {
getJsonTreeTheme,
StyleUtilsContext,
} from '../styles/createStylingFromTheme';
import { createTreeItemLabelRenderer, getItemString } from '../styles/tree'; import { createTreeItemLabelRenderer, getItemString } from '../styles/tree';
import { StyleUtils } from '../types'; import { identity } from '../utils/object';
export interface TreeViewProps extends StyleUtils { export interface TreeViewProps {
data: unknown; data: unknown;
isWideLayout: boolean; isWideLayout: boolean;
before?: ReactNode; before?: ReactNode;
@ -15,37 +20,39 @@ export interface TreeViewProps extends StyleUtils {
} }
export class TreeView extends React.PureComponent<TreeViewProps> { export class TreeView extends React.PureComponent<TreeViewProps> {
readonly labelRenderer: ReturnType<typeof createTreeItemLabelRenderer>; readonly selectLabelRenderer = createSelector<
StylingFunction,
StylingFunction,
ReturnType<typeof createTreeItemLabelRenderer>
>(identity, createTreeItemLabelRenderer);
constructor(props: TreeViewProps) { constructor(props: TreeViewProps) {
super(props); super(props);
this.labelRenderer = createTreeItemLabelRenderer(this.props.styling);
} }
render(): ReactNode { render(): ReactNode {
const { const { isWideLayout, data, before, after, children, keyPath } = this.props;
styling,
base16Theme,
invertTheme,
isWideLayout,
data,
before,
after,
children,
keyPath,
} = this.props;
return (
<StyleUtilsContext.Consumer>
{({ styling, invertTheme, base16Theme }) => {
return ( return (
<div {...styling('treeWrapper')}> <div {...styling('treeWrapper')}>
{before} {before}
<JSONTree <JSONTree
keyPath={keyPath} keyPath={keyPath}
data={data} data={data}
labelRenderer={this.labelRenderer} labelRenderer={this.selectLabelRenderer(styling)}
theme={getJsonTreeTheme(base16Theme)} theme={getJsonTreeTheme(base16Theme)}
invertTheme={invertTheme} invertTheme={invertTheme}
getItemString={(type, data) => getItemString={(type, data) =>
getItemString(styling, type, data, DATA_TYPE_KEY, isWideLayout) getItemString(
styling,
type,
data,
DATA_TYPE_KEY,
isWideLayout
)
} }
hideRoot hideRoot
/> />
@ -53,5 +60,8 @@ export class TreeView extends React.PureComponent<TreeViewProps> {
{children} {children}
</div> </div>
); );
}}
</StyleUtilsContext.Consumer>
);
} }
} }

View File

@ -18,7 +18,6 @@ import {
import { QueryList } from '../components/QueryList'; import { QueryList } from '../components/QueryList';
import { QueryForm } from '../components/QueryForm'; import { QueryForm } from '../components/QueryForm';
import { QueryPreview } from '../components/QueryPreview'; import { QueryPreview } from '../components/QueryPreview';
import { getApiStateOf, getQuerySubscriptionsOf } from '../utils/rtk-query';
type ForwardedMonitorProps<S, A extends Action<unknown>> = Pick< type ForwardedMonitorProps<S, A extends Action<unknown>> = Pick<
LiftedState<S, A, RtkQueryMonitorState>, LiftedState<S, A, RtkQueryMonitorState>,
@ -115,26 +114,15 @@ class RtkQueryInspector<S, A extends Action<unknown>> extends PureComponent<
const { const {
styleUtils: { styling }, styleUtils: { styling },
} = this.props; } = this.props;
const apiStates = this.selectors.selectApiStates(selectorsSource);
const allVisibleQueries = const allVisibleQueries =
this.selectors.selectAllVisbileQueries(selectorsSource); this.selectors.selectAllVisbileQueries(selectorsSource);
const currentQueryInfo = const currentQueryInfo =
this.selectors.selectCurrentQueryInfo(selectorsSource); this.selectors.selectCurrentQueryInfo(selectorsSource);
const currentRtkApi = const apiStates = this.selectors.selectApiStates(selectorsSource);
this.selectors.selectApiOfCurrentQuery(selectorsSource);
const currentQuerySubscriptions = getQuerySubscriptionsOf(
currentQueryInfo,
apiStates
);
const currentTags = this.selectors.selectCurrentQueryTags(selectorsSource); const hasNoApi = apiStates == null;
const currentApiStats =
this.selectors.selectApiStatsOfCurrentQuery(selectorsSource);
const hasNoApis = apiStates == null;
return ( return (
<div <div
@ -156,17 +144,15 @@ class RtkQueryInspector<S, A extends Action<unknown>> extends PureComponent<
selectedQueryKey={selectorsSource.monitorState.selectedQueryKey} selectedQueryKey={selectorsSource.monitorState.selectedQueryKey}
/> />
</div> </div>
<QueryPreview <QueryPreview<S>
selectorsSource={this.state.selectorsSource}
selectors={this.selectors}
queryInfo={currentQueryInfo} queryInfo={currentQueryInfo}
selectedTab={selectorsSource.monitorState.selectedPreviewTab} selectedTab={selectorsSource.monitorState.selectedPreviewTab}
onTabChange={this.handleTabChange} onTabChange={this.handleTabChange}
styling={styling} styling={styling}
tags={currentTags}
querySubscriptions={currentQuerySubscriptions}
apiState={currentRtkApi}
isWideLayout={isWideLayout} isWideLayout={isWideLayout}
apiStats={currentApiStats} hasNoApis={hasNoApi}
hasNoApis={hasNoApis}
/> />
</div> </div>
); );

View File

@ -0,0 +1,29 @@
import React, { ComponentType, ReactNode, Component } from 'react';
interface Mapper<In, Out> {
(inProps: In): Out;
}
interface MapPropsOutput<In, Out> {
(comp: ComponentType<Out>): ComponentType<In>;
}
export function mapProps<In, Out>(
mapper: Mapper<In, Out>
): MapPropsOutput<In, Out> {
return function mapPropsHoc(Comp) {
class MapPropsHoc extends Component<In> {
render(): ReactNode {
const mappedProps = mapper(this.props);
return <Comp {...mappedProps} />;
}
static displayName = `mapProps(${
Comp.displayName || Comp.name || 'Component'
})`;
}
return MapPropsHoc;
};
}

View File

@ -7,9 +7,11 @@ import {
RtkQueryTag, RtkQueryTag,
SelectorsSource, SelectorsSource,
RtkQueryProvided, RtkQueryProvided,
QueryPreviewTabs,
} 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';
import { emptyRecord } from './utils/object';
import { escapeRegExpSpecialCharacter } from './utils/regexp'; import { escapeRegExpSpecialCharacter } from './utils/regexp';
import { import {
getApiStatesOf, getApiStatesOf,
@ -63,6 +65,14 @@ export interface InspectorSelectors<S> {
S, S,
RtkQueryApiState | null RtkQueryApiState | null
>; >;
readonly selectTabCounters: InspectorSelector<
S,
Record<QueryPreviewTabs, number>
>;
readonly selectSubscriptionsOfCurrentQuery: InspectorSelector<
S,
RtkQueryApiState['subscriptions'][string]
>;
} }
export function createInspectorSelectors<S>(): InspectorSelectors<S> { export function createInspectorSelectors<S>(): InspectorSelectors<S> {
@ -161,6 +171,17 @@ export function createInspectorSelectors<S>(): InspectorSelectors<S> {
return selectApiOfCurrentQuery(selectorsSource)?.provided ?? null; return selectApiOfCurrentQuery(selectorsSource)?.provided ?? null;
}; };
const selectSubscriptionsOfCurrentQuery = createSelector(
[selectApiOfCurrentQuery, selectCurrentQueryInfo],
(apiState, queryInfo) => {
if (!queryInfo || !apiState) {
return emptyRecord;
}
return apiState.subscriptions[queryInfo.queryKey];
}
);
const selectCurrentQueryTags = createSelector( const selectCurrentQueryTags = createSelector(
[selectCurrentQueryInfo, selectProvidedOfCurrentQuery], [selectCurrentQueryInfo, selectProvidedOfCurrentQuery],
getQueryTagsOf getQueryTagsOf
@ -171,6 +192,21 @@ export function createInspectorSelectors<S>(): InspectorSelectors<S> {
generateApiStatsOfCurrentQuery generateApiStatsOfCurrentQuery
); );
const selectTabCounters = (
selectorsSource: SelectorsSource<S>
): Record<QueryPreviewTabs, number> => {
const subscriptions = selectSubscriptionsOfCurrentQuery(selectorsSource);
const subsLen = Object.keys(subscriptions ?? {}).length;
return {
[QueryPreviewTabs.queryTags]:
selectCurrentQueryTags(selectorsSource).length,
[QueryPreviewTabs.querySubscriptions]: subsLen,
[QueryPreviewTabs.apiConfig]: 0,
[QueryPreviewTabs.queryinfo]: 0,
};
};
return { return {
selectQueryComparator, selectQueryComparator,
selectApiStates, selectApiStates,
@ -180,6 +216,8 @@ export function createInspectorSelectors<S>(): InspectorSelectors<S> {
selectCurrentQueryInfo, selectCurrentQueryInfo,
selectCurrentQueryTags, selectCurrentQueryTags,
selectApiStatsOfCurrentQuery, selectApiStatsOfCurrentQuery,
selectSubscriptionsOfCurrentQuery,
selectApiOfCurrentQuery, selectApiOfCurrentQuery,
selectTabCounters,
}; };
} }

View File

@ -132,24 +132,10 @@ export interface ApiStats {
}>; }>;
} }
export interface QueryPreviewTabProps extends StyleUtils {
queryInfo: QueryInfo | null;
apiState: RtkQueryApiState | null;
querySubscriptions: RTKQuerySubscribers | null;
isWideLayout: boolean;
tags: RtkQueryTag[];
apiStats: ApiStats | null;
}
export interface TabOption<S, P> extends SelectOption<S> { export interface TabOption<S, P> extends SelectOption<S> {
component: ComponentType<P>; component: ComponentType<P>;
} }
export type QueryPreviewTabOption = TabOption<
QueryPreviewTabs,
QueryPreviewTabProps
>;
/** /**
* It is Omit<RequestStatusFlags, 'status'> & { isFetching: boolean; } * It is Omit<RequestStatusFlags, 'status'> & { isFetching: boolean; }
*/ */