diff --git a/src/common/README.md b/src/common/README.md index 682431904..c241e5b91 100644 --- a/src/common/README.md +++ b/src/common/README.md @@ -274,6 +274,7 @@ Download the debug log file. ## General * [General](#Helpers.module_General) + * [~memoClone](#Helpers.module_General..memoClone) ⇒ any * [~noop](#Helpers.module_General..noop) * [~noopPromise](#Helpers.module_General..noopPromise) : Promise.<{}> * [~setImmutableData](#Helpers.module_General..setImmutableData) ⇒ \* @@ -315,6 +316,25 @@ Download the debug log file. * [~objFreeze(obj)](#Helpers.module_General..objFreeze) ⇒ \* * [~browserExpose(obj, options)](#Helpers.module_General..browserExpose) + + +### General~memoClone ⇒ any +Used for the numerous configuration callbacks that drive the UI. Often the same data +is passed repeatedly. Avoid "accidental" mutation. + +**Kind**: inner constant of [General](#Helpers.module_General) + + + + + + + + + + +
ParamType
valueany
+ ### General~noop diff --git a/src/common/__tests__/__snapshots__/helpers.test.js.snap b/src/common/__tests__/__snapshots__/helpers.test.js.snap index f8f9787ad..80ca64cd0 100644 --- a/src/common/__tests__/__snapshots__/helpers.test.js.snap +++ b/src/common/__tests__/__snapshots__/helpers.test.js.snap @@ -44,6 +44,7 @@ exports[`Helpers should expose a window object: limited window object 1`] = ` "isPromise": [Function], "lorem": "ipsum", "memo": [Function], + "memoClone": [Function], "noop": [Function], "noopPromise": Promise {}, "numberDisplay": [Function], @@ -90,6 +91,7 @@ exports[`Helpers should expose a window object: window object 1`] = ` "isPromise": [Function], "lorem": "ipsum", "memo": [Function], + "memoClone": [Function], "noop": [Function], "noopPromise": Promise {}, "numberDisplay": [Function], @@ -147,6 +149,7 @@ exports[`Helpers should have specific functions: helpers 1`] = ` "isDate": [Function], "isPromise": [Function], "memo": [Function], + "memoClone": [Function], "noop": [Function], "noopPromise": Promise {}, "numberDisplay": [Function], diff --git a/src/common/helpers.js b/src/common/helpers.js index b818df64b..066d1ddfa 100644 --- a/src/common/helpers.js +++ b/src/common/helpers.js @@ -125,6 +125,15 @@ const memo = (func, { cacheLimit = 1 } = {}) => { return ized(); }; +/** + * Used for the numerous configuration callbacks that drive the UI. Often the same data + * is passed repeatedly. Avoid "accidental" mutation. + * + * @param {any} value + * @returns {any} + */ +const memoClone = memo(value => _cloneDeep(value), { cacheLimit: 25 }); + /** * An empty function. * Typically used as a default prop. @@ -444,6 +453,7 @@ const helpers = { isDate, isPromise, memo, + memoClone, noop, noopPromise, numberDisplay, diff --git a/src/services/common/__tests__/__snapshots__/helpers.test.js.snap b/src/services/common/__tests__/__snapshots__/helpers.test.js.snap index 2bb568d7d..1f4fe7b2b 100644 --- a/src/services/common/__tests__/__snapshots__/helpers.test.js.snap +++ b/src/services/common/__tests__/__snapshots__/helpers.test.js.snap @@ -23,6 +23,7 @@ exports[`Service Helpers should have specific functions: serviceHelpers 1`] = ` { "camelCase": [Function], "generateHash": [Function], + "memoClone": [Function], "passDataToCallback": [Function], "schemaResponse": [Function], "timeoutFunctionCancel": [Function], diff --git a/src/services/common/__tests__/__snapshots__/serviceConfig.test.js.snap b/src/services/common/__tests__/__snapshots__/serviceConfig.test.js.snap index f1d6591d2..422bcdc48 100644 --- a/src/services/common/__tests__/__snapshots__/serviceConfig.test.js.snap +++ b/src/services/common/__tests__/__snapshots__/serviceConfig.test.js.snap @@ -363,7 +363,9 @@ exports[`ServiceConfig should handle transforming service call responses: transf [ "success-schema-transform", "success-transform", + "success", "error-error-transform", + "error", [ "cancelled request", undefined, diff --git a/src/services/common/__tests__/serviceConfig.test.js b/src/services/common/__tests__/serviceConfig.test.js index d6f319180..fd3f05fe9 100644 --- a/src/services/common/__tests__/serviceConfig.test.js +++ b/src/services/common/__tests__/serviceConfig.test.js @@ -188,6 +188,18 @@ describe('ServiceConfig', () => { }); responses.push(responseTwo.data); + // Second-Error, use a transform but expect an error + const responseTwoError = await serviceConfig.axiosServiceCall({ + cache: true, + url: '/test/', + transform: [ + () => { + throw new Error('success response transform error'); + } + ] + }); + responses.push(responseTwoError.data); + // Third, use error transform const responseThree = await returnPromiseAsync(async () => serviceConfig.axiosServiceCall({ @@ -199,9 +211,23 @@ describe('ServiceConfig', () => { ] }) ); - responses.push(responseThree.data); + // Third-Error, use error transform + const responseThreeError = await returnPromiseAsync(async () => + serviceConfig.axiosServiceCall({ + cache: true, + url: '/error/', + transform: [ + successResponse => `${successResponse}-transform`, + () => { + throw new Error('error response transform error'); + } + ] + }) + ); + responses.push(responseThreeError.data); + // Fourth, use error transform with cancel const responseFourConfig = { cache: true, diff --git a/src/services/common/helpers.js b/src/services/common/helpers.js index f0a69d397..bf3001d2c 100644 --- a/src/services/common/helpers.js +++ b/src/services/common/helpers.js @@ -8,9 +8,9 @@ import { helpers } from '../../common'; */ /** - * Pass through generate hash + * Pass through generate hash, memo clone */ -const { generateHash } = helpers; +const { generateHash, memoClone } = helpers; /** * A timeout cancel for function calls. @@ -122,6 +122,7 @@ const schemaResponse = ({ casing, convert = true, id = null, response, schema } const serviceHelpers = { camelCase, generateHash, + memoClone, passDataToCallback, schemaResponse, timeoutFunctionCancel @@ -132,6 +133,7 @@ export { serviceHelpers, camelCase, generateHash, + memoClone, passDataToCallback, schemaResponse, timeoutFunctionCancel diff --git a/src/services/common/serviceConfig.js b/src/services/common/serviceConfig.js index b089e5641..43102a30a 100644 --- a/src/services/common/serviceConfig.js +++ b/src/services/common/serviceConfig.js @@ -82,6 +82,7 @@ const axiosServiceCall = async ( updatedConfig.cacheId = cacheId; } + // apply cancel configuration if (updatedConfig.cancel === true) { const cancelTokensId = updatedConfig.cancelId || serviceHelpers.generateHash({ ...updatedConfig, data: undefined, params: undefined }); @@ -96,6 +97,7 @@ const axiosServiceCall = async ( delete updatedConfig.cancel; } + // if cached response return if (updatedConfig.cacheResponse === true) { const cachedResponse = responseCache.get(cacheId); @@ -112,14 +114,17 @@ const axiosServiceCall = async ( } } + // if schema transform, add before standard transform if (updatedConfig.schema) { responseTransformers.push(updatedConfig.schema); } + // add response transformers if (updatedConfig.transform) { responseTransformers.push(updatedConfig.transform); } + // apply response transformers responseTransformers.forEach(([successTransform, errorTransform]) => { const transformers = [undefined, response => Promise.reject(response)]; @@ -128,11 +133,13 @@ const axiosServiceCall = async ( const updatedResponse = { ...response }; const { data, error: normalizeError } = serviceHelpers.passDataToCallback( successTransform, - updatedResponse.data, - updatedResponse.config + serviceHelpers.memoClone(updatedResponse.data), + serviceHelpers.memoClone(updatedResponse.config) ); - if (!normalizeError) { + if (normalizeError) { + console.warn(normalizeError); + } else { updatedResponse.data = data; } @@ -150,11 +157,13 @@ const axiosServiceCall = async ( const { data, error: normalizeError } = serviceHelpers.passDataToCallback( errorTransform, - updatedResponse?.data || updatedResponse?.message, - updatedResponse.config + serviceHelpers.memoClone(updatedResponse?.data || updatedResponse?.message), + serviceHelpers.memoClone(updatedResponse.config) ); - if (!normalizeError) { + if (normalizeError) { + console.warn(normalizeError); + } else { updatedResponse.response = { ...updatedResponse, data }; } @@ -165,6 +174,7 @@ const axiosServiceCall = async ( axiosInstance.interceptors.response.use(...transformers); }); + // apply a response to cache if (updatedConfig.cacheResponse === true) { axiosInstance.interceptors.response.use( response => { @@ -176,6 +186,7 @@ const axiosServiceCall = async ( ); } + // use a function instead of a url-string, receive service emulated output (for implementation consistency) if (typeof updatedConfig.url === 'function') { const emulateCallback = updatedConfig.url; updatedConfig.url = '/emulated'; diff --git a/tests/__snapshots__/code.test.js.snap b/tests/__snapshots__/code.test.js.snap index 8380e2c2f..f8f3fa6df 100644 --- a/tests/__snapshots__/code.test.js.snap +++ b/tests/__snapshots__/code.test.js.snap @@ -10,5 +10,7 @@ exports[`General code checks should only have specific console.[warn|log|info|er "redux/common/reduxHelpers.js:287: console.error(\`Error: Property \${prop} does not exist within the passed state.\`, state);", "redux/common/reduxHelpers.js:291: console.warn(\`Warning: Property \${prop} does not exist within the passed initialState.\`, initialState);", "services/common/helpers.js:105: console.error(", + "services/common/serviceConfig.js:141: console.warn(normalizeError);", + "services/common/serviceConfig.js:165: console.warn(normalizeError);", ] `;