♻️ Refactor onboarding state management

This commit is contained in:
Philipp Stracker 2024-11-18 18:26:10 +01:00
parent 81f45692f4
commit fee9a016c6
No known key found for this signature in database
6 changed files with 100 additions and 148 deletions

View file

@ -1,21 +1,11 @@
export default { export default {
RESET_ONBOARDING: 'RESET_ONBOARDING',
// Transient data. // Transient data.
SET_ONBOARDING_IS_READY: 'SET_ONBOARDING_IS_READY', SET_TRANSIENT: 'ONBOARDING:SET_TRANSIENT',
SET_IS_SAVING_ONBOARDING: 'SET_IS_SAVING_ONBOARDING',
SET_MANUAL_CONNECTION_BUSY: 'SET_MANUAL_CONNECTION_BUSY',
// Persistent data. // Persistent data.
SET_ONBOARDING_COMPLETED: 'SET_ONBOARDING_COMPLETED', SET_PERSISTENT: 'ONBOARDING:SET_PERSISTENT',
SET_ONBOARDING_DETAILS: 'SET_ONBOARDING_DETAILS', RESET: 'ONBOARDING:RESET',
SET_ONBOARDING_STEP: 'SET_ONBOARDING_STEP', HYDRATE: 'ONBOARDING:HYDRATE',
SET_SANDBOX_MODE: 'SET_SANDBOX_MODE',
SET_MANUAL_CONNECTION_MODE: 'SET_MANUAL_CONNECTION_MODE',
SET_CLIENT_ID: 'SET_CLIENT_ID',
SET_CLIENT_SECRET: 'SET_CLIENT_SECRET',
SET_IS_CASUAL_SELLER: 'SET_IS_CASUAL_SELLER',
SET_PRODUCTS: 'SET_PRODUCTS',
// Controls - always start with "DO_". // Controls - always start with "DO_".
DO_PERSIST_DATA: 'ONBOARDING:DO_PERSIST_DATA', DO_PERSIST_DATA: 'ONBOARDING:DO_PERSIST_DATA',

View file

@ -9,48 +9,48 @@ import ACTION_TYPES from './action-types';
/** /**
* Special. Resets all values in the onboarding store to initial defaults. * Special. Resets all values in the onboarding store to initial defaults.
* *
* @return {{type: string}} The action. * @return {Action} The action.
*/ */
export const resetOnboarding = () => { export const resetOnboarding = () => {
return { type: ACTION_TYPES.RESET_ONBOARDING }; return { type: ACTION_TYPES.RESET };
}; };
/** /**
* Non-persistent. Marks the onboarding details as "ready", i.e., fully initialized. * Transient. Marks the onboarding details as "ready", i.e., fully initialized.
* *
* @param {boolean} isReady * @param {boolean} isReady
* @return {{type: string, isReady}} The action. * @return {Action} The action.
*/ */
export const setIsReady = ( isReady ) => { export const setIsReady = ( isReady ) => {
return { return {
type: ACTION_TYPES.SET_ONBOARDING_IS_READY, type: ACTION_TYPES.SET_TRANSIENT,
isReady, payload: { isReady },
}; };
}; };
/** /**
* Non-persistent. Changes the "saving" flag. * Transient. Changes the "saving" flag.
* *
* @param {boolean} isSaving * @param {boolean} isSaving
* @return {{type: string, isSaving}} The action. * @return {Action} The action.
*/ */
export const setIsSaving = ( isSaving ) => { export const setIsSaving = ( isSaving ) => {
return { return {
type: ACTION_TYPES.SET_IS_SAVING_ONBOARDING, type: ACTION_TYPES.SET_TRANSIENT,
isSaving, payload: { isSaving },
}; };
}; };
/** /**
* Non-persistent. Changes the "manual connection is busy" flag. * Transient. Changes the "manual connection is busy" flag.
* *
* @param {boolean} isBusy * @param {boolean} isBusy
* @return {{type: string, isBusy}} The action. * @return {Action} The action.
*/ */
export const setManualConnectionIsBusy = ( isBusy ) => { export const setManualConnectionIsBusy = ( isBusy ) => {
return { return {
type: ACTION_TYPES.SET_MANUAL_CONNECTION_BUSY, type: ACTION_TYPES.SET_TRANSIENT,
isBusy, payload: { isBusy },
}; };
}; };
@ -58,11 +58,11 @@ export const setManualConnectionIsBusy = ( isBusy ) => {
* Persistent. Set the full onboarding details, usually during app initialization. * Persistent. Set the full onboarding details, usually during app initialization.
* *
* @param {{data: {}, flags?: {}}} payload * @param {{data: {}, flags?: {}}} payload
* @return {{type: string, payload}} The action. * @return {Action} The action.
*/ */
export const setOnboardingDetails = ( payload ) => { export const hydrateOnboardingDetails = ( payload ) => {
return { return {
type: ACTION_TYPES.SET_ONBOARDING_DETAILS, type: ACTION_TYPES.HYDRATE,
payload, payload,
}; };
}; };
@ -71,12 +71,12 @@ export const setOnboardingDetails = ( payload ) => {
* Persistent.Set the "onboarding completed" flag which shows or hides the wizard. * Persistent.Set the "onboarding completed" flag which shows or hides the wizard.
* *
* @param {boolean} completed * @param {boolean} completed
* @return {{type: string, payload}} The action. * @return {Action} The action.
*/ */
export const setCompleted = ( completed ) => { export const setCompleted = ( completed ) => {
return { return {
type: ACTION_TYPES.SET_ONBOARDING_COMPLETED, type: ACTION_TYPES.SET_PERSISTENT,
completed, payload: { completed },
}; };
}; };
@ -84,38 +84,38 @@ export const setCompleted = ( completed ) => {
* Persistent. Sets the onboarding wizard to a new step. * Persistent. Sets the onboarding wizard to a new step.
* *
* @param {number} step * @param {number} step
* @return {{type: string, step}} An action. * @return {Action} The action.
*/ */
export const setOnboardingStep = ( step ) => { export const setOnboardingStep = ( step ) => {
return { return {
type: ACTION_TYPES.SET_ONBOARDING_STEP, type: ACTION_TYPES.SET_PERSISTENT,
step, payload: { step },
}; };
}; };
/** /**
* Persistent. Sets the sandbox mode on or off. * Persistent. Sets the sandbox mode on or off.
* *
* @param {boolean} sandboxMode * @param {boolean} useSandbox
* @return {{type: string, useSandbox}} An action. * @return {Action} The action.
*/ */
export const setSandboxMode = ( sandboxMode ) => { export const setSandboxMode = ( useSandbox ) => {
return { return {
type: ACTION_TYPES.SET_SANDBOX_MODE, type: ACTION_TYPES.SET_PERSISTENT,
useSandbox: sandboxMode, payload: { useSandbox },
}; };
}; };
/** /**
* Persistent. Toggles the "Manual Connection" mode on or off. * Persistent. Toggles the "Manual Connection" mode on or off.
* *
* @param {boolean} manualConnectionMode * @param {boolean} useManualConnection
* @return {{type: string, useManualConnection}} An action. * @return {Action} The action.
*/ */
export const setManualConnectionMode = ( manualConnectionMode ) => { export const setManualConnectionMode = ( useManualConnection ) => {
return { return {
type: ACTION_TYPES.SET_MANUAL_CONNECTION_MODE, type: ACTION_TYPES.SET_PERSISTENT,
useManualConnection: manualConnectionMode, payload: { useManualConnection },
}; };
}; };
@ -123,12 +123,12 @@ export const setManualConnectionMode = ( manualConnectionMode ) => {
* Persistent. Changes the "client ID" value. * Persistent. Changes the "client ID" value.
* *
* @param {string} clientId * @param {string} clientId
* @return {{type: string, clientId}} The action. * @return {Action} The action.
*/ */
export const setClientId = ( clientId ) => { export const setClientId = ( clientId ) => {
return { return {
type: ACTION_TYPES.SET_CLIENT_ID, type: ACTION_TYPES.SET_PERSISTENT,
clientId, payload: { clientId },
}; };
}; };
@ -136,12 +136,12 @@ export const setClientId = ( clientId ) => {
* Persistent. Changes the "client secret" value. * Persistent. Changes the "client secret" value.
* *
* @param {string} clientSecret * @param {string} clientSecret
* @return {{type: string, clientSecret}} The action. * @return {Action} The action.
*/ */
export const setClientSecret = ( clientSecret ) => { export const setClientSecret = ( clientSecret ) => {
return { return {
type: ACTION_TYPES.SET_CLIENT_SECRET, type: ACTION_TYPES.SET_PERSISTENT,
clientSecret, payload: { clientSecret },
}; };
}; };
@ -149,12 +149,12 @@ export const setClientSecret = ( clientSecret ) => {
* Persistent. Sets the "isCasualSeller" value. * Persistent. Sets the "isCasualSeller" value.
* *
* @param {boolean} isCasualSeller * @param {boolean} isCasualSeller
* @return {{type: string, isCasualSeller}} The action. * @return {Action} The action.
*/ */
export const setIsCasualSeller = ( isCasualSeller ) => { export const setIsCasualSeller = ( isCasualSeller ) => {
return { return {
type: ACTION_TYPES.SET_IS_CASUAL_SELLER, type: ACTION_TYPES.SET_PERSISTENT,
isCasualSeller, payload: { isCasualSeller },
}; };
}; };
@ -162,12 +162,12 @@ export const setIsCasualSeller = ( isCasualSeller ) => {
* Persistent. Sets the "products" array. * Persistent. Sets the "products" array.
* *
* @param {string[]} products * @param {string[]} products
* @return {{type: string, products}} The action. * @return {Action} The action.
*/ */
export const setProducts = ( products ) => { export const setProducts = ( products ) => {
return { return {
type: ACTION_TYPES.SET_PRODUCTS, type: ACTION_TYPES.SET_PERSISTENT,
products, payload: { products },
}; };
}; };

View file

@ -1,7 +1,6 @@
import { useSelect, useDispatch } from '@wordpress/data'; import { useSelect, useDispatch } from '@wordpress/data';
import apiFetch from '@wordpress/api-fetch';
import { NAMESPACE, PRODUCT_TYPES, STORE_NAME } from '../constants'; import { PRODUCT_TYPES, STORE_NAME } from '../constants';
import { getFlags } from './selectors';
const useOnboardingDetails = () => { const useOnboardingDetails = () => {
const { const {
@ -16,55 +15,60 @@ const useOnboardingDetails = () => {
setProducts, setProducts,
} = useDispatch( STORE_NAME ); } = useDispatch( STORE_NAME );
// Transient accessors. const transientData = ( select ) =>
const isSaving = useSelect( ( select ) => { select( STORE_NAME ).onboardingTransientData();
return select( STORE_NAME ).getTransientData().isSaving; const persistentData = ( select ) =>
}, [] ); select( STORE_NAME ).onboardingPersistentData();
const isReady = useSelect( ( select ) => {
return select( STORE_NAME ).getTransientData().isReady;
} );
const isManualConnectionBusy = useSelect( ( select ) => {
return select( STORE_NAME ).getTransientData().isManualConnectionBusy;
}, [] );
// Read-only flags. // Read-only flags.
const flags = useSelect( ( select ) => { const flags = useSelect( ( select ) => {
return select( STORE_NAME ).getFlags(); return select( STORE_NAME ).onboardingFlags();
} ); } );
// Transient accessors.
const isSaving = useSelect( ( select ) => {
return transientData( select ).isSaving;
}, [] );
const isReady = useSelect( ( select ) => {
return transientData( select ).isReady;
} );
const isManualConnectionBusy = useSelect( ( select ) => {
return transientData( select ).isManualConnectionBusy;
}, [] );
// Persistent accessors. // Persistent accessors.
const step = useSelect( ( select ) => { const step = useSelect( ( select ) => {
return select( STORE_NAME ).getPersistentData().step || 0; return persistentData( select ).step || 0;
} ); } );
const completed = useSelect( ( select ) => { const completed = useSelect( ( select ) => {
return select( STORE_NAME ).getPersistentData().completed; return persistentData( select ).completed;
} ); } );
const clientId = useSelect( ( select ) => { const clientId = useSelect( ( select ) => {
return select( STORE_NAME ).getPersistentData().clientId; return persistentData( select ).clientId;
}, [] ); }, [] );
const clientSecret = useSelect( ( select ) => { const clientSecret = useSelect( ( select ) => {
return select( STORE_NAME ).getPersistentData().clientSecret; return persistentData( select ).clientSecret;
}, [] ); }, [] );
const isSandboxMode = useSelect( ( select ) => { const isSandboxMode = useSelect( ( select ) => {
return select( STORE_NAME ).getPersistentData().useSandbox; return persistentData( select ).useSandbox;
}, [] ); }, [] );
const isManualConnectionMode = useSelect( ( select ) => { const isManualConnectionMode = useSelect( ( select ) => {
return select( STORE_NAME ).getPersistentData().useManualConnection; return persistentData( select ).useManualConnection;
}, [] ); }, [] );
const isCasualSeller = useSelect( ( select ) => { const isCasualSeller = useSelect( ( select ) => {
return select( STORE_NAME ).getPersistentData().isCasualSeller; return persistentData( select ).isCasualSeller;
}, [] ); }, [] );
const products = useSelect( ( select ) => { const products = useSelect( ( select ) => {
return select( STORE_NAME ).getPersistentData().products || []; return persistentData( select ).products || [];
}, [] ); }, [] );
const toggleProduct = ( list ) => { const toggleProduct = ( list ) => {

View file

@ -34,69 +34,26 @@ const [ setTransient, setPersistent ] = createSetters(
defaultPersistent defaultPersistent
); );
const defaultState = { const onboardingReducer = createReducer( defaultTransient, defaultPersistent, {
...defaultTransient, [ ACTION_TYPES.SET_TRANSIENT ]: ( state, { payload } ) =>
data: { ...defaultPersistent }, setTransient( state, payload ),
};
export const onboardingReducer = ( [ ACTION_TYPES.SET_PERSISTENT ]: ( state, { payload } ) =>
state = defaultState, setPersistent( state, payload ),
{ type, ...action }
) => {
switch ( type ) {
// Reset store to initial state.
case ACTION_TYPES.RESET_ONBOARDING:
return setPersistent( defaultState.data );
// Transient data. [ ACTION_TYPES.RESET ]: ( state ) =>
case ACTION_TYPES.SET_ONBOARDING_IS_READY: setPersistent( state, defaultPersistent ),
return setTransient( { isReady: action.isReady } );
case ACTION_TYPES.SET_IS_SAVING_ONBOARDING: [ ACTION_TYPES.HYDRATE ]: ( state, { payload } ) => {
return setTransient( { isSaving: action.isSaving } ); const newState = setPersistent( payload );
case ACTION_TYPES.SET_MANUAL_CONNECTION_BUSY: // Flags are not updated by `setPersistent()`.
return setTransient( { isManualConnectionBusy: action.isBusy } ); if ( payload.flags ) {
newState.flags = { ...newState.flags, ...payload.flags };
// Persistent data.
case ACTION_TYPES.SET_ONBOARDING_DETAILS:
const newState = setPersistent( action.payload.data );
if ( action.payload.flags ) {
newState.flags = { ...newState.flags, ...action.payload.flags };
} }
return newState; return newState;
},
case ACTION_TYPES.SET_ONBOARDING_COMPLETED: } );
return setPersistent( { completed: action.completed } );
case ACTION_TYPES.SET_CLIENT_ID:
return setPersistent( { clientId: action.clientId } );
case ACTION_TYPES.SET_CLIENT_SECRET:
return setPersistent( { clientSecret: action.clientSecret } );
case ACTION_TYPES.SET_ONBOARDING_STEP:
return setPersistent( { step: action.step } );
case ACTION_TYPES.SET_SANDBOX_MODE:
return setPersistent( { useSandbox: action.useSandbox } );
case ACTION_TYPES.SET_MANUAL_CONNECTION_MODE:
return setPersistent( {
useManualConnection: action.useManualConnection,
} );
case ACTION_TYPES.SET_IS_CASUAL_SELLER:
return setPersistent( { isCasualSeller: action.isCasualSeller } );
case ACTION_TYPES.SET_PRODUCTS:
return setPersistent( { products: action.products } );
default:
return state;
}
};
export default onboardingReducer; export default onboardingReducer;

View file

@ -1,19 +1,20 @@
import { dispatch } from '@wordpress/data'; import { dispatch } from '@wordpress/data';
import { __ } from '@wordpress/i18n'; import { __ } from '@wordpress/i18n';
import { apiFetch } from '@wordpress/data-controls'; import { apiFetch } from '@wordpress/data-controls';
import { NAMESPACE } from '../constants'; import { NAMESPACE } from '../constants';
import { REST_HYDRATE_PATH } from './constants'; import { REST_HYDRATE_PATH } from './constants';
import { setIsReady, setOnboardingDetails } from './actions'; import { setIsReady, hydrateOnboardingDetails } from './actions';
/** /**
* Retrieve settings from the site's REST API. * Retrieve settings from the site's REST API.
*/ */
export function* getPersistentData() { export function* onboardingPersistentData() {
const path = `${ NAMESPACE }/${ REST_HYDRATE_PATH }`; const path = `${ NAMESPACE }/${ REST_HYDRATE_PATH }`;
try { try {
const result = yield apiFetch( { path } ); const result = yield apiFetch( { path } );
yield setOnboardingDetails( result ); yield hydrateOnboardingDetails( result );
yield setIsReady( true ); yield setIsReady( true );
} catch ( e ) { } catch ( e ) {
yield dispatch( 'core/notices' ).createErrorNotice( yield dispatch( 'core/notices' ).createErrorNotice(

View file

@ -10,15 +10,15 @@ const getState = ( state ) => {
return state[ STORE_KEY ] || EMPTY_OBJ; return state[ STORE_KEY ] || EMPTY_OBJ;
}; };
export const getPersistentData = ( state ) => { export const onboardingPersistentData = ( state ) => {
return getState( state ).data || EMPTY_OBJ; return getState( state ).data || EMPTY_OBJ;
}; };
export const getTransientData = ( state ) => { export const onboardingTransientData = ( state ) => {
const { data, flags, ...transientState } = getState( state ); const { data, flags, ...transientState } = getState( state );
return transientState || EMPTY_OBJ; return transientState || EMPTY_OBJ;
}; };
export const getFlags = ( state ) => { export const onboardingFlags = ( state ) => {
return getState( state ).flags || EMPTY_OBJ; return getState( state ).flags || EMPTY_OBJ;
}; };