make updating states simple and code clean up

This commit is contained in:
Sandeep Yadav 2025-08-27 21:48:19 +05:30 committed by GitHub
parent 193141bf52
commit faf719c1fd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -1,223 +1,45 @@
import { AnyAction, Dispatch } from 'redux';
import React from 'react';
import { Button, Toolbar } from '@redux-devtools/ui'; import { Button, Toolbar } from '@redux-devtools/ui';
import { RtkResourceInfo } from '../types'; import React from 'react';
import { AnyAction, Dispatch } from 'redux';
interface RTKActionButtonsProps { interface RTKActionButtonsProps {
dispatch?: Dispatch<AnyAction>; dispatch?: Dispatch<AnyAction>;
liftedDispatch?: Dispatch<AnyAction>; liftedDispatch?: Dispatch<AnyAction>;
resInfo?: RtkResourceInfo | null; data: Record<string, AnyAction>;
actionsOfQuery?: AnyAction[];
} }
const ACTION_TYPES = { const ACTION_TYPES = {
REFRESH: 'REFRESH', REFRESH: 'REFRESH',
FULFILLED: 'FULFILLED', FULFILLED: 'FULFILLED',
TRIGGER_LOADING: 'TRIGGER_LOADING', TRIGGER_FETCHING: 'TRIGGER_FETCHING',
} as const; } as const;
const createFulfilledMeta = ( const REFRESH_TIMEOUT_MS = 1000;
requestStatus: string,
requestId: string,
baseEndpoint: string,
parsedArgs: any,
queryKey: string,
) => ({
arg: {
type: 'query',
subscribe: true,
forceRefetch: true,
subscriptionOptions: {
pollingInterval: 0,
skipPollingIfUnfocused: false,
},
endpointName: baseEndpoint,
originalArgs: parsedArgs || {},
queryCacheKey: queryKey,
},
requestId,
requestStatus,
fulfilledTimeStamp: Date.now(),
baseQueryMeta: {
request: {},
response: {},
},
RTK_autoBatch: true,
});
const createPendingMeta = (
requestStatus: string,
requestId: string,
baseEndpoint: string,
parsedArgs: any,
queryKey: string,
) => ({
arg: {
type: 'query',
subscribe: true,
forceRefetch: true,
subscriptionOptions: {
pollingInterval: 0,
skipPollingIfUnfocused: false,
},
endpointName: baseEndpoint,
originalArgs: parsedArgs || {},
queryCacheKey: queryKey,
},
requestId,
requestStatus,
baseQueryMeta: {
request: {},
response: {},
},
RTK_autoBatch: true,
});
export const createRtkQueryActions = (
reducerPath: string,
queryKey: string,
endpointName?: string,
currentData?: any,
originalArgs?: any,
) => {
const baseEndpoint = endpointName || queryKey.split('(')[0];
let parsedArgs = originalArgs;
if (!parsedArgs && queryKey.includes('(')) {
const argsString = queryKey.split('(')[1]?.split(')')[0];
if (
argsString === undefined ||
argsString === '' ||
argsString === 'undefined'
) {
parsedArgs = undefined;
} else {
try {
parsedArgs = JSON.parse(argsString);
} catch (e) {
parsedArgs = undefined;
}
}
}
const actualPayload = currentData || {
message: `No data available for ${baseEndpoint}`,
timestamp: Date.now(),
endpointName: baseEndpoint,
queryKey: queryKey,
};
return {
refresh: {
type: `${reducerPath}/executeQuery/pending`,
payload: undefined,
meta: createPendingMeta(
'pending',
`refresh-${Date.now()}`,
baseEndpoint,
parsedArgs,
queryKey,
),
},
fulfilled: {
type: `${reducerPath}/executeQuery/fulfilled`,
payload: actualPayload,
meta: createFulfilledMeta(
'fulfilled',
`fulfilled-${Date.now()}`,
baseEndpoint,
parsedArgs,
queryKey,
),
},
triggerLoading: {
type: `${reducerPath}/executeQuery/pending`,
payload: undefined,
meta: createPendingMeta(
'pending',
`loading-${Date.now()}`,
baseEndpoint,
parsedArgs,
queryKey,
),
},
};
};
export const RTKActionButtons: React.FC<RTKActionButtonsProps> = ({ export const RTKActionButtons: React.FC<RTKActionButtonsProps> = ({
dispatch, dispatch,
liftedDispatch, liftedDispatch,
resInfo, data,
actionsOfQuery,
}) => { }) => {
// Extract current data from query actions if available const [isLoading, setIsLoading] = React.useState(false);
let currentData: any; const pending = Object.values(data).filter(
if (actionsOfQuery && actionsOfQuery.length > 0) { (action) => action.meta?.requestStatus === 'pending',
// Look for the most recent fulfilled action to get current data
const fulfilledActions = actionsOfQuery.filter(
(action) => action.type?.includes('/fulfilled') && action.payload,
);
if (fulfilledActions.length > 0) {
currentData = fulfilledActions[fulfilledActions.length - 1].payload;
console.log('📊 Found current data from actions:', currentData);
}
}
if (!resInfo) {
return (
<div css={{ padding: '8px 12px', fontSize: '12px', color: 'orange' }}>
No query selected
</div>
);
}
let originalArgs;
let lastPendingAction;
if (actionsOfQuery && actionsOfQuery.length > 0) {
const queryAction = actionsOfQuery.find(
(action) =>
action.type?.includes('/executeQuery/') ||
action.type?.includes('/pending') ||
action.type?.includes('/fulfilled'),
);
if (queryAction?.meta?.arg?.originalArgs) {
originalArgs = queryAction.meta.arg.originalArgs;
}
const pendingActions = actionsOfQuery.filter(
(action) => action.type?.includes('/pending') && action.meta?.requestId,
);
if (pendingActions.length > 0) {
lastPendingAction = pendingActions[pendingActions.length - 1];
}
}
const actions = createRtkQueryActions(
resInfo.reducerPath,
resInfo.queryKey,
resInfo.queryKey.split('(')[0],
currentData,
originalArgs,
); );
if (lastPendingAction) { const fulfilled = Object.values(data).filter(
actions.fulfilled.meta.requestId = lastPendingAction.meta.requestId; (action) => action.meta?.requestStatus === 'fulfilled',
);
actions.fulfilled.meta.arg.queryCacheKey = // Get the most recent actions
lastPendingAction.meta.arg.queryCacheKey; const recentPending = pending[pending.length - 1];
const recentFulfilled = fulfilled[fulfilled.length - 1];
actions.triggerLoading.meta.requestId = lastPendingAction.meta.requestId;
actions.triggerLoading.meta.arg.queryCacheKey =
lastPendingAction.meta.arg.queryCacheKey;
actions.refresh.meta.requestId = lastPendingAction.meta.requestId;
actions.refresh.meta.arg.queryCacheKey =
lastPendingAction.meta.arg.queryCacheKey;
}
const handleAction = ( const handleAction = (
actionType: keyof typeof ACTION_TYPES, actionType: keyof typeof ACTION_TYPES,
action: AnyAction, action: AnyAction,
fulfilledAction?: AnyAction,
) => { ) => {
setIsLoading(true);
if (liftedDispatch) { if (liftedDispatch) {
try { try {
const performAction = { const performAction = {
@ -227,37 +49,46 @@ export const RTKActionButtons: React.FC<RTKActionButtonsProps> = ({
}; };
liftedDispatch(performAction); liftedDispatch(performAction);
if (actionType === ACTION_TYPES.REFRESH && currentData) { // For refresh actions, automatically dispatch fulfilled action after delay
if (actionType === ACTION_TYPES.REFRESH && fulfilledAction) {
setTimeout(() => { setTimeout(() => {
const resolveAction = { const resolveAction = {
type: 'PERFORM_ACTION' as const, type: 'PERFORM_ACTION' as const,
action: actions.fulfilled, action: fulfilledAction,
timestamp: Date.now(), timestamp: Date.now(),
}; };
liftedDispatch(resolveAction); liftedDispatch(resolveAction);
}, 1000); setIsLoading(false);
}, REFRESH_TIMEOUT_MS);
} }
} catch (e) { } catch (error) {
console.error('Error in liftedDispatch:', e); console.error('Error in liftedDispatch:', error);
} }
} }
if (dispatch) { if (dispatch) {
try { try {
dispatch(action); dispatch(action);
if (actionType === ACTION_TYPES.REFRESH && currentData) {
// For refresh actions, automatically dispatch fulfilled action after delay
if (actionType === ACTION_TYPES.REFRESH && fulfilledAction) {
setTimeout(() => { setTimeout(() => {
dispatch(actions.fulfilled); dispatch(fulfilledAction);
}, 1000); setIsLoading(false);
}, REFRESH_TIMEOUT_MS);
} }
} catch (e) { } catch (error) {
console.error('Error in dispatch:', e); console.error('Error in dispatch:', error);
} }
} }
}; };
return ( return (
<Toolbar borderPosition="bottom" style={{ alignItems: 'center' }}> <Toolbar
borderPosition="bottom"
style={{ alignItems: 'center' }}
key={JSON.stringify(data)}
>
<label <label
css={(theme) => ({ css={(theme) => ({
fontSize: '12px', fontSize: '12px',
@ -268,26 +99,33 @@ export const RTKActionButtons: React.FC<RTKActionButtonsProps> = ({
> >
RTK Query Actions: RTK Query Actions:
</label> </label>
<Button <Button
tooltipPosition="top-right" mark={isLoading && 'base0D'}
onClick={() => handleAction(ACTION_TYPES.REFRESH, actions.refresh)} onClick={() =>
title="Force refetch" handleAction(ACTION_TYPES.REFRESH, recentPending, recentFulfilled)
}
disabled={isLoading || !recentPending || !recentFulfilled}
> >
Refresh Refresh
</Button> </Button>
<Button <Button
tooltipPosition="top-right" mark={isLoading && 'base0D'}
onClick={() => onClick={() =>
handleAction(ACTION_TYPES.TRIGGER_LOADING, actions.triggerLoading) handleAction(ACTION_TYPES.TRIGGER_FETCHING, recentPending)
} }
title="Simulate loading state" disabled={isLoading || !recentPending || !recentFulfilled}
> >
Loading Fetching
</Button> </Button>
<Button <Button
tooltipPosition="top-right" onClick={() => {
onClick={() => handleAction(ACTION_TYPES.FULFILLED, actions.fulfilled)} handleAction(ACTION_TYPES.FULFILLED, recentFulfilled);
title="Simulate successful response" setIsLoading(false);
}}
disabled={!recentFulfilled}
> >
Fulfill Fulfill
</Button> </Button>