Replace isBusy with new activity-state manager

This commit is contained in:
Philipp Stracker 2024-12-05 18:55:56 +01:00
parent 405c397331
commit 9786a18eb0
No known key found for this signature in database
5 changed files with 77 additions and 16 deletions

View file

@ -13,6 +13,10 @@ export default {
RESET: 'COMMON:RESET', RESET: 'COMMON:RESET',
HYDRATE: 'COMMON:HYDRATE', HYDRATE: 'COMMON:HYDRATE',
// Activity management (advanced solution that replaces the isBusy state).
START_ACTIVITY: 'COMMON:START_ACTIVITY',
STOP_ACTIVITY: 'COMMON:STOP_ACTIVITY',
// Controls - always start with "DO_". // Controls - always start with "DO_".
DO_PERSIST_DATA: 'COMMON:DO_PERSIST_DATA', DO_PERSIST_DATA: 'COMMON:DO_PERSIST_DATA',
DO_MANUAL_CONNECTION: 'COMMON:DO_MANUAL_CONNECTION', DO_MANUAL_CONNECTION: 'COMMON:DO_MANUAL_CONNECTION',

View file

@ -59,14 +59,35 @@ export const setIsSaving = ( isSaving ) => ( {
} ); } );
/** /**
* Transient. Changes the "manual connection is busy" flag. * Transient (Activity): Marks the start of an async activity
* Think of it as "setIsBusy(true)"
* *
* @param {boolean} isBusy * @param {string} id Internal ID/key of the action, used to stop it again.
* @param {?string} description Optional, description for logging/debugging
* @return {?Action} The action.
*/
export const startActivity = ( id, description = null ) => {
if ( ! id || 'string' !== typeof id ) {
console.warn( 'Activity ID must be a non-empty string' );
return null;
}
return {
type: ACTION_TYPES.START_ACTIVITY,
payload: { id, description },
};
};
/**
* Transient (Activity): Marks the end of an async activity.
* Think of it as "setIsBusy(false)"
*
* @param {string} id Internal ID/key of the action, used to stop it again.
* @return {Action} The action. * @return {Action} The action.
*/ */
export const setIsBusy = ( isBusy ) => ( { export const stopActivity = ( id ) => ( {
type: ACTION_TYPES.SET_TRANSIENT, type: ACTION_TYPES.STOP_ACTIVITY,
payload: { isBusy }, payload: { id },
} ); } );
/** /**
@ -130,10 +151,8 @@ export const persist = function* () {
* @return {Action} The action. * @return {Action} The action.
*/ */
export const connectToSandbox = function* () { export const connectToSandbox = function* () {
yield setIsBusy( true );
const result = yield { type: ACTION_TYPES.DO_SANDBOX_LOGIN }; const result = yield { type: ACTION_TYPES.DO_SANDBOX_LOGIN };
yield setIsBusy( false );
return result; return result;
}; };
@ -145,13 +164,11 @@ export const connectToSandbox = function* () {
* @return {Action} The action. * @return {Action} The action.
*/ */
export const connectToProduction = function* ( products = [] ) { export const connectToProduction = function* ( products = [] ) {
yield setIsBusy( true );
const result = yield { const result = yield {
type: ACTION_TYPES.DO_PRODUCTION_LOGIN, type: ACTION_TYPES.DO_PRODUCTION_LOGIN,
products, products,
}; };
yield setIsBusy( false );
return result; return result;
}; };
@ -165,7 +182,6 @@ export const connectViaIdAndSecret = function* () {
const { clientId, clientSecret, useSandbox } = const { clientId, clientSecret, useSandbox } =
yield select( STORE_NAME ).persistentData(); yield select( STORE_NAME ).persistentData();
yield setIsBusy( true );
const result = yield { const result = yield {
type: ACTION_TYPES.DO_MANUAL_CONNECTION, type: ACTION_TYPES.DO_MANUAL_CONNECTION,
@ -173,7 +189,6 @@ export const connectViaIdAndSecret = function* () {
clientSecret, clientSecret,
useSandbox, useSandbox,
}; };
yield setIsBusy( false );
return result; return result;
}; };

View file

@ -81,12 +81,34 @@ const useHooks = () => {
}; };
export const useBusyState = () => { export const useBusyState = () => {
const { setIsBusy } = useDispatch( STORE_NAME ); const { startActivity, stopActivity } = useDispatch( STORE_NAME );
const isBusy = useTransient( 'isBusy' );
// Resolved value (object), contains a list of all running actions.
const activities = useSelect(
( select ) => select( STORE_NAME ).getActivityList(),
[]
);
// Derive isBusy state from activities
const isBusy = Object.keys( activities ).length > 0;
// HOC that starts and stops an activity while the callback is executed.
const withActivity = useCallback(
async ( id, description, asyncFn ) => {
startActivity( id, description );
try {
return await asyncFn();
} finally {
stopActivity( id );
}
},
[ startActivity, stopActivity ]
);
return { return {
isBusy, withActivity, // HOC
setIsBusy: useCallback( ( busy ) => setIsBusy( busy ), [ setIsBusy ] ), isBusy, // Boolean.
activities, // Object.
}; };
}; };

View file

@ -14,7 +14,7 @@ import ACTION_TYPES from './action-types';
const defaultTransient = { const defaultTransient = {
isReady: false, isReady: false,
isBusy: false, activities: new Map(),
// Read only values, provided by the server via hydrate. // Read only values, provided by the server via hydrate.
wooSettings: { wooSettings: {
@ -57,6 +57,21 @@ const commonReducer = createReducer( defaultTransient, defaultPersistent, {
return cleanState; return cleanState;
}, },
[ ACTION_TYPES.START_ACTIVITY ]: ( state, payload ) => {
return setTransient( state, {
activities: new Map( state.activities ).set(
payload.id,
payload.description
),
} );
},
[ ACTION_TYPES.STOP_ACTIVITY ]: ( state, payload ) => {
const newActivities = new Map( state.activities );
newActivities.delete( payload.id );
return setTransient( state, { activities: newActivities } );
},
[ ACTION_TYPES.HYDRATE ]: ( state, payload ) => { [ ACTION_TYPES.HYDRATE ]: ( state, payload ) => {
const newState = setPersistent( state, payload.data ); const newState = setPersistent( state, payload.data );

View file

@ -20,6 +20,11 @@ export const transientData = ( state ) => {
return transientState || EMPTY_OBJ; return transientState || EMPTY_OBJ;
}; };
export const getActivityList = ( state ) => {
const { activities = new Map() } = state;
return Object.fromEntries( activities );
};
export const wooSettings = ( state ) => { export const wooSettings = ( state ) => {
return getState( state ).wooSettings || EMPTY_OBJ; return getState( state ).wooSettings || EMPTY_OBJ;
}; };