diff --git a/modules/ppcp-settings/resources/js/data/onboarding/reducer.js b/modules/ppcp-settings/resources/js/data/onboarding/reducer.js index 5c1f59263..9cdeb7018 100644 --- a/modules/ppcp-settings/resources/js/data/onboarding/reducer.js +++ b/modules/ppcp-settings/resources/js/data/onboarding/reducer.js @@ -1,22 +1,13 @@ +import { createReducer, createSetters } from '../utils'; import ACTION_TYPES from './action-types'; -const defaultState = { +// Store structure. + +const defaultTransient = { isReady: false, isSaving: false, isManualConnectionBusy: false, - // Data persisted to the server. - data: { - completed: false, - step: 0, - useSandbox: false, - useManualConnection: false, - clientId: '', - clientSecret: '', - isCasualSeller: null, // null value will uncheck both options in the UI. - products: [], - }, - // Read only values, provided by the server. flags: { canUseCasualSelling: false, @@ -25,29 +16,33 @@ const defaultState = { }, }; +const defaultPersistent = { + completed: false, + step: 0, + useSandbox: false, + useManualConnection: false, + clientId: '', + clientSecret: '', + isCasualSeller: null, // null value will uncheck both options in the UI. + products: [], +}; + +// Reducer logic. + +const [ setTransient, setPersistent ] = createSetters( + defaultTransient, + defaultPersistent +); + +const defaultState = { + ...defaultTransient, + data: { ...defaultPersistent }, +}; + export const onboardingReducer = ( state = defaultState, { type, ...action } ) => { - const setTransient = ( changes ) => { - const { data, ...transientChanges } = changes; - return { ...state, ...transientChanges }; - }; - - const setPersistent = ( changes ) => { - const validChanges = Object.keys( changes ).reduce( ( acc, key ) => { - if ( key in defaultState.data ) { - acc[ key ] = changes[ key ]; - } - return acc; - }, {} ); - - return { - ...state, - data: { ...state.data, ...validChanges }, - }; - }; - switch ( type ) { // Reset store to initial state. case ACTION_TYPES.RESET_ONBOARDING: diff --git a/modules/ppcp-settings/resources/js/data/utils.js b/modules/ppcp-settings/resources/js/data/utils.js new file mode 100644 index 000000000..2cec07815 --- /dev/null +++ b/modules/ppcp-settings/resources/js/data/utils.js @@ -0,0 +1,75 @@ +/** + * Updates an object with new values, filtering based on allowed keys. + * + * Helper method used by createSetters. + * + * @param {Object} oldObject The original object to update. + * @param {Object} newValues The new values to apply. + * @param {Object} allowedKeys An object whose keys define the allowed keys to update. + * @return {Object} A new object with the allowed updates applied. + */ +const updateObject = ( oldObject, newValues, allowedKeys = {} ) => ( { + ...oldObject, + ...Object.keys( newValues ).reduce( ( acc, key ) => { + if ( key in allowedKeys ) { + acc[ key ] = newValues[ key ]; + } + return acc; + }, {} ), +} ); + +/** + * Creates setter functions for updating state. + * + * Only properties that are present in the "defaultTransient" or "defaultPersistent" + * arguments can be updated by the setters. Make sure that the default state defines + * ALL possible properties. + * + * @param {Object} defaultTransient Object defining initial transient values. + * @param {Object} defaultPersistent Object defining initial persistent values. + * @return {[Function, Function]} An array containing setTransient and setPersistent functions. + */ +export const createSetters = ( defaultTransient, defaultPersistent ) => { + const setTransient = ( oldState, newValues ) => + updateObject( oldState, newValues, defaultTransient ); + + const setPersistent = ( oldState, newValues ) => ( { + ...oldState, + data: updateObject( oldState.data, newValues, defaultPersistent ), + } ); + + return [ setTransient, setPersistent ]; +}; + +/** + * Creates a reducer function with predefined action handlers. + * + * @param {Object} defaultTransient Object defining initial transient values. + * @param {Object} defaultPersistent Object defining initial persistent values. + * @param {Object} handlers An object mapping action types to handler functions. + * @return {Function} A reducer function. + */ +export const createReducer = ( + defaultTransient, + defaultPersistent, + handlers +) => { + if ( Object.hasOwnProperty.call( defaultTransient, 'data' ) ) { + throw new Error( + 'The transient state cannot contain a "data" property.' + ); + } + + const initialState = { + ...defaultTransient, + data: defaultPersistent, + }; + + return function reducer( state = initialState, action ) { + if ( Object.hasOwnProperty.call( handlers, action.type ) ) { + return handlers[ action.type ]( state, action.data ); + } + + return state; + }; +};