From 337b719395b89c1b735286e409085c0b01063142 Mon Sep 17 00:00:00 2001 From: David Chin Date: Thu, 5 Sep 2019 09:28:31 +1000 Subject: [PATCH] refactor(common): CHECKOUT-4272 Use bigcommerce/memoize package --- package-lock.json | 23 ++- package.json | 1 + src/app/address/AddressForm.tsx | 4 +- src/app/address/getAddressValidationSchema.ts | 4 +- src/app/cart/Redeemable.tsx | 8 +- src/app/checkout/CheckoutProvider.tsx | 5 +- src/app/common/utility/index.ts | 2 - .../common/utility/memoize/CacheKeyMaps.ts | 24 --- .../utility/memoize/CacheKeyResolver.spec.ts | 123 ------------- .../utility/memoize/CacheKeyResolver.ts | 167 ------------------ .../common/utility/memoize/memoize.spec.ts | 38 ---- src/app/common/utility/memoize/memoize.ts | 24 --- src/app/locale/LocaleProvider.tsx | 5 +- src/app/locale/getLanguageService.ts | 5 +- src/app/payment/Payment.tsx | 5 +- .../creditCard/CreditCardExpiryField.tsx | 4 +- .../getCreditCardValidationSchema.ts | 4 +- .../paymentMethod/CreditCardPaymentMethod.tsx | 5 +- .../HostedWidgetPaymentMethod.tsx | 5 +- .../paymentMethod/PaymentMethodList.tsx | 2 +- .../getInstrumentValidationSchema.ts | 4 +- src/app/shipping/ShippingAddress.tsx | 5 +- src/app/ui/accordion/Accordion.tsx | 5 +- src/app/ui/form/ChecklistItem.tsx | 6 +- src/app/ui/form/Form.tsx | 7 +- 25 files changed, 62 insertions(+), 423 deletions(-) delete mode 100644 src/app/common/utility/memoize/CacheKeyMaps.ts delete mode 100644 src/app/common/utility/memoize/CacheKeyResolver.spec.ts delete mode 100644 src/app/common/utility/memoize/CacheKeyResolver.ts delete mode 100644 src/app/common/utility/memoize/memoize.spec.ts delete mode 100644 src/app/common/utility/memoize/memoize.ts diff --git a/package-lock.json b/package-lock.json index 4d45c0cb46..23b5b739ef 100644 --- a/package-lock.json +++ b/package-lock.json @@ -920,6 +920,18 @@ "resolved": "https://registry.npmjs.org/@bigcommerce/form-poster/-/form-poster-1.3.0.tgz", "integrity": "sha512-ewMghaxUyeJyBd1nMNfyJVtQ6JnZE3u72mTrxoDMxRzeugYiWMT8dQyjQdNACuKCjFEJYm1GDRFxLAH0jo4i6Q==" }, + "@bigcommerce/memoize": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@bigcommerce/memoize/-/memoize-1.0.0.tgz", + "integrity": "sha512-B3KA9hCQt+ieDqH2yPI5zpK11U9UtOfGqu19mFdS0fBSPoCIsTZ3GRG1SEdIFqCXqJnK3AqYbLet1FK/G2G7sg==", + "requires": { + "@types/lodash.memoize": "^4.1.6", + "@types/shallowequal": "^1.1.1", + "lodash.memoize": "^4.1.2", + "shallowequal": "^1.1.0", + "tslib": "^1.10.0" + } + }, "@bigcommerce/request-sender": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/@bigcommerce/request-sender/-/request-sender-0.3.0.tgz", @@ -1464,6 +1476,14 @@ "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.136.tgz", "integrity": "sha512-0GJhzBdvsW2RUccNHOBkabI8HZVdOXmXbXhuKlDEd5Vv12P7oAVGfomGp3Ne21o5D/qu1WmthlNKFaoZJJeErA==" }, + "@types/lodash.memoize": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@types/lodash.memoize/-/lodash.memoize-4.1.6.tgz", + "integrity": "sha512-mYxjKiKzRadRJVClLKxS4wb3Iy9kzwJ1CkbyKiadVxejnswnRByyofmPMscFKscmYpl36BEEhCMPuWhA1R/1ZQ==", + "requires": { + "@types/lodash": "*" + } + }, "@types/minimatch": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", @@ -10701,8 +10721,7 @@ "lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=", - "dev": true + "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=" }, "lodash.sortby": { "version": "4.7.0", diff --git a/package.json b/package.json index 667c4c33fe..006ef82c91 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "@bigcommerce/checkout-sdk": "^1.34.3", "@bigcommerce/citadel": "^2.15.1", "@bigcommerce/form-poster": "^1.2.2", + "@bigcommerce/memoize": "^1.0.0", "@bigcommerce/request-sender": "^0.3.0", "@bigcommerce/script-loader": "^0.1.5", "@sentry/browser": "^5.6.2", diff --git a/src/app/address/AddressForm.tsx b/src/app/address/AddressForm.tsx index 915941ea0c..8fd7b83dec 100644 --- a/src/app/address/AddressForm.tsx +++ b/src/app/address/AddressForm.tsx @@ -1,8 +1,8 @@ import { Address, Country, FormField } from '@bigcommerce/checkout-sdk'; +import { memoize } from '@bigcommerce/memoize'; import { forIn, noop } from 'lodash'; import React, { createRef, Component, ReactNode, RefObject } from 'react'; -import { memoize } from '../common/utility'; import { withLanguage, WithLanguageProps } from '../locale'; import { AutocompleteItem } from '../ui/autocomplete'; @@ -37,7 +37,7 @@ class AddressForm extends Component { private handleDynamicFormFieldChange: (name: string) => (value: string | string[]) => void = memoize(name => value => { this.syncNonFormikValue(name, value); - }, { maxSize: 0 }); + }); componentDidMount(): void { const { current } = this.containerRef; diff --git a/src/app/address/getAddressValidationSchema.ts b/src/app/address/getAddressValidationSchema.ts index 859335d4dc..8254e1320f 100644 --- a/src/app/address/getAddressValidationSchema.ts +++ b/src/app/address/getAddressValidationSchema.ts @@ -1,5 +1,5 @@ import { FormField, LanguageService } from '@bigcommerce/checkout-sdk'; -import { memoize } from 'lodash'; +import { memoize } from '@bigcommerce/memoize'; import { array, date, number, object, string, ArraySchema, NumberSchema, ObjectSchema, Schema, StringSchema } from 'yup'; import { AddressFormValues } from './mapAddressToFormValues'; @@ -103,4 +103,4 @@ export default memoize(function getAddressValidationSchema({ ) ).nullable(true), }) as ObjectSchema> ; -}, (...args) => JSON.stringify(args)); +}); diff --git a/src/app/cart/Redeemable.tsx b/src/app/cart/Redeemable.tsx index d9a475111f..f4d2952965 100644 --- a/src/app/cart/Redeemable.tsx +++ b/src/app/cart/Redeemable.tsx @@ -1,11 +1,11 @@ import { CheckoutSelectors, RequestError } from '@bigcommerce/checkout-sdk'; +import { memoizeOne } from '@bigcommerce/memoize'; import { withFormik, FieldProps, FormikProps } from 'formik'; import { noop } from 'lodash'; import React, { memo, useCallback, Fragment, FunctionComponent, KeyboardEvent } from 'react'; import { object, string } from 'yup'; import { preventDefault } from '../common/dom'; -import { memoize } from '../common/utility'; import { withLanguage, TranslatedString, WithLanguageProps } from '../locale'; import { Alert, AlertType } from '../ui/alert'; import { Button, ButtonVariant } from '../ui/button'; @@ -74,7 +74,7 @@ const RedeemableForm: FunctionComponent & FormikProps { - const handleKeyDown = useCallback(memoize((setSubmitted: FormContextType['setSubmitted']) => ( + const handleKeyDown = useCallback(memoizeOne((setSubmitted: FormContextType['setSubmitted']) => ( (event: KeyboardEvent) => { if (appliedRedeemableError) { clearError(appliedRedeemableError); @@ -94,7 +94,7 @@ const RedeemableForm: FunctionComponent & FormikProps ( + const handleSubmit = useCallback(memoizeOne((setSubmitted: FormContextType['setSubmitted']) => ( () => { setSubmitted(true); submitForm(); @@ -142,7 +142,7 @@ const RedeemableForm: FunctionComponent & FormikProps ( + const renderContent = useCallback(memoizeOne(({ setSubmitted }: FormContextType) => ( void; - private getContextValue = memoize((checkoutService, checkoutState) => { + private getContextValue = memoizeOne((checkoutService, checkoutState) => { return { checkoutService, checkoutState, diff --git a/src/app/common/utility/index.ts b/src/app/common/utility/index.ts index 27a1cb6820..b80e50305a 100644 --- a/src/app/common/utility/index.ts +++ b/src/app/common/utility/index.ts @@ -1,3 +1 @@ export * from './emptyData'; - -export { default as memoize } from './memoize/memoize'; diff --git a/src/app/common/utility/memoize/CacheKeyMaps.ts b/src/app/common/utility/memoize/CacheKeyMaps.ts deleted file mode 100644 index 5cb6a15c9c..0000000000 --- a/src/app/common/utility/memoize/CacheKeyMaps.ts +++ /dev/null @@ -1,24 +0,0 @@ -export interface RootCacheKeyMap { - maps: ChildCacheKeyMap[]; -} - -export interface IntermediateCacheKeyMap { - maps: ChildCacheKeyMap[]; - parentMap: RootCacheKeyMap | IntermediateCacheKeyMap; - usedCount: number; - value: any; -} - -export interface TerminalCacheKeyMap extends IntermediateCacheKeyMap { - cacheKey: string; -} - -export type ChildCacheKeyMap = IntermediateCacheKeyMap | TerminalCacheKeyMap; - -export function isTerminalCacheKeyMap(map: ChildCacheKeyMap): map is TerminalCacheKeyMap { - return map.hasOwnProperty('cacheKey'); -} - -export function isRootCacheKeyMap(map: RootCacheKeyMap | ChildCacheKeyMap): map is RootCacheKeyMap { - return map.hasOwnProperty('parentMap'); -} diff --git a/src/app/common/utility/memoize/CacheKeyResolver.spec.ts b/src/app/common/utility/memoize/CacheKeyResolver.spec.ts deleted file mode 100644 index b8e05a40f2..0000000000 --- a/src/app/common/utility/memoize/CacheKeyResolver.spec.ts +++ /dev/null @@ -1,123 +0,0 @@ -import CacheKeyResolver from './CacheKeyResolver'; - -describe('CacheKeyResolver', () => { - it('returns same cache key if params are equal', () => { - const resolver = new CacheKeyResolver(); - - expect(resolver.getKey('hello')).toEqual('1'); - expect(resolver.getKey('bye')).toEqual('2'); - expect(resolver.getKey('hello')).toEqual('1'); - expect(resolver.getKey('bye')).toEqual('2'); - }); - - it('returns same cache key if multiple params are equal', () => { - const resolver = new CacheKeyResolver(); - - expect(resolver.getKey('hello', 'world')).toEqual('1'); - expect(resolver.getKey('hello', 'good', 'bye')).toEqual('2'); - expect(resolver.getKey('hello', 'world')).toEqual('1'); - expect(resolver.getKey('hello', 'good', 'bye')).toEqual('2'); - }); - - it('returns same cache key if no params are provided', () => { - const resolver = new CacheKeyResolver(); - - expect(resolver.getKey()).toEqual('1'); - expect(resolver.getKey()).toEqual('1'); - }); - - it('works with non-primitive params', () => { - const resolver = new CacheKeyResolver(); - const personA = { name: 'Foo' }; - const personB = { name: 'Bar' }; - const personC = { name: 'Foobar' }; - - expect(resolver.getKey(personA, personB)).toEqual('1'); - expect(resolver.getKey(personB, personA)).toEqual('2'); - expect(resolver.getKey(personA, personB)).toEqual('1'); - expect(resolver.getKey(personB, personA, personC)).toEqual('3'); - }); - - it('works with functions', () => { - const resolver = new CacheKeyResolver(); - const functionA = () => 'a'; - const functionB = () => 'b'; - - expect(resolver.getKey('foobar', functionA)).toEqual('1'); - expect(resolver.getKey('foobar', functionB)).toEqual('2'); - expect(resolver.getKey('foobar', functionA)).toEqual('1'); - expect(resolver.getKey('foobar', functionB)).toEqual('2'); - }); - - it('works with unserializable objects with cyclical reference', () => { - const resolver = new CacheKeyResolver(); - const objectB: any = { child: undefined }; - const objectA: any = { child: objectB }; - - objectB.child = objectA; - - expect(resolver.getKey(objectA, objectB)).toEqual('1'); - expect(resolver.getKey(objectA, objectB)).toEqual('1'); - }); - - it('returns same key if objects are shallowly equivalent', () => { - const resolver = new CacheKeyResolver(); - const objectA = { id: 1 }; - const objectB = { id: 1 }; - - expect(resolver.getKey('foobar', objectA)).toEqual(resolver.getKey('foobar', objectB)); - }); - - it('returns different cache key for least recently used set of arguments', () => { - const resolver = new CacheKeyResolver({ maxSize: 2 }); - - expect(resolver.getKey('hello', 'world')).toEqual('1'); - // This will return the cache key - expect(resolver.getKey('hello', 'world')).toEqual('1'); - expect(resolver.getKey('hello', 'good')).toEqual('2'); - expect(resolver.getKey('bad', 'guys')).toEqual('3'); - // This will return a new cache key because the set of arguments is - // least recently used and the number of cache keys already exceed the - // maximum size - expect(resolver.getKey('hello', 'world')).toEqual('4'); - }); - - it('only expires cache key if number of unique calls exceeds max size', () => { - const resolver = new CacheKeyResolver({ maxSize: 2 }); - - expect(resolver.getKey('hello', 'world')).toEqual('1'); - expect(resolver.getKey('hello', 'world')).toEqual('1'); - // The previous call should not expire the key because it is called with - // the same set of arguments - expect(resolver.getKey('hello', 'world')).toEqual('1'); - - expect(resolver.getKey('foo', 'bar')).toEqual('2'); - expect(resolver.getKey('hello', 'bye')).toEqual('3'); - - // This call should return a new key because the previous two calls are - // made with different sets of arguments - expect(resolver.getKey('hello', 'world')).toEqual('4'); - }); - - it('does not expire cache key if number of unique calls does not exceed max size', () => { - const resolver = new CacheKeyResolver({ maxSize: 3 }); - - expect(resolver.getKey('hello', 'world')).toEqual('1'); - expect(resolver.getKey('foo', 'bar')).toEqual('2'); - expect(resolver.getKey('hello', 'bye')).toEqual('3'); - - // This call should return the same key because the number of unique - // calls is under the max size - expect(resolver.getKey('hello', 'world')).toEqual('1'); - }); - - it('returns cache key used count', () => { - const resolver = new CacheKeyResolver(); - - expect(resolver.getUsedCount('hello', 'world')).toEqual(0); - resolver.getKey('hello', 'world'); - expect(resolver.getUsedCount('hello', 'world')).toEqual(1); - resolver.getKey('hello', 'world'); - expect(resolver.getUsedCount('hello', 'world')).toEqual(2); - }); -}); diff --git a/src/app/common/utility/memoize/CacheKeyResolver.ts b/src/app/common/utility/memoize/CacheKeyResolver.ts deleted file mode 100644 index 224f018f09..0000000000 --- a/src/app/common/utility/memoize/CacheKeyResolver.ts +++ /dev/null @@ -1,167 +0,0 @@ -import { noop } from 'lodash'; -import shallowEqual from 'shallowequal'; - -import { isRootCacheKeyMap, isTerminalCacheKeyMap, ChildCacheKeyMap, IntermediateCacheKeyMap, RootCacheKeyMap, TerminalCacheKeyMap } from './CacheKeyMaps'; - -export interface CacheKeyResolverOptions { - maxSize?: number; - onExpire?(key: string): void; -} - -interface ResolveResult { - index: number; - parentMap: RootCacheKeyMap | IntermediateCacheKeyMap; - map?: TerminalCacheKeyMap; -} - -export default class CacheKeyResolver { - private _lastId = 0; - private _map: RootCacheKeyMap = { maps: [] }; - private _usedMaps: TerminalCacheKeyMap[] = []; - private _options: Required; - - constructor(options?: CacheKeyResolverOptions) { - this._options = { - maxSize: 0, - onExpire: noop, - ...options, - }; - } - - getKey(...args: any[]): string { - const result = this._resolveMap(...args); - const { index, parentMap } = result; - let { map } = result; - - if (map && map.cacheKey) { - map.usedCount++; - } else { - map = this._generateMap(parentMap, args.slice(index)); - } - - // Keep track of the least used map so we can remove it if the size of - // the stack exceeds the maximum size. - this._removeLeastUsedMap(map); - - return map.cacheKey; - } - - getUsedCount(...args: any[]): number { - const { map } = this._resolveMap(...args); - - return map ? map.usedCount : 0; - } - - private _resolveMap(...args: any[]): ResolveResult { - let index = 0; - let parentMap = this._map; - - // Traverse the tree to find the linked list of maps that match the - // arguments of the call. Each intermediate or terminal map contains a - // value that could be used to match with the arguments. The last map in - // the list (the terminal) should contain a cache key. If it can does - // not exist, we will return a falsy value so that the caller could - // handle and generate a new cache key. - while (parentMap.maps.length) { - let isMatched = false; - - for (let mapIndex = 0; mapIndex < parentMap.maps.length; mapIndex++) { - const map = parentMap.maps[mapIndex]; - - if (!shallowEqual(map.value, args[index])) { - continue; - } - - // Move the most recently used map to the top of the stack for - // quicker access - parentMap.maps.unshift(...parentMap.maps.splice(mapIndex, 1)); - - if ((args.length === 0 || index === args.length - 1) && isTerminalCacheKeyMap(map)) { - return { index, map, parentMap }; - } - - isMatched = true; - parentMap = map; - index++; - - break; - } - - if (!isMatched) { - break; - } - } - - return { index, parentMap }; - } - - private _generateMap(parent: RootCacheKeyMap | IntermediateCacheKeyMap, args: any[]): TerminalCacheKeyMap { - let index = 0; - let parentMap = parent; - let map: IntermediateCacheKeyMap; - - do { - map = { - maps: [], - parentMap, - usedCount: 1, - value: args[index], - }; - - // Continue to build the tree of maps so that it could be resolved - // next time when the function is called with the same set of - // arguments. - parentMap.maps.unshift(map); - - parentMap = map; - index++; - } while (index < args.length); - - const terminalMap = map as TerminalCacheKeyMap; - - terminalMap.cacheKey = `${++this._lastId}`; - - return terminalMap; - } - - private _removeLeastUsedMap(recentlyUsedMap: TerminalCacheKeyMap): void { - if (!this._options.maxSize) { - return; - } - - const index = this._usedMaps.indexOf(recentlyUsedMap); - - this._usedMaps.splice( - index === -1 ? 0 : index, - index === -1 ? 0 : 1, - recentlyUsedMap - ); - - if (this._usedMaps.length <= this._options.maxSize) { - return; - } - - const map = this._usedMaps.pop(); - - if (!map) { - return; - } - - this._removeMap(map); - this._options.onExpire(map.cacheKey); - } - - private _removeMap(map: ChildCacheKeyMap): void { - if (!map.parentMap) { - return; - } - - map.parentMap.maps.splice(map.parentMap.maps.indexOf(map), 1); - - if (isRootCacheKeyMap(map.parentMap)) { - return; - } - - this._removeMap(map.parentMap); - } -} diff --git a/src/app/common/utility/memoize/memoize.spec.ts b/src/app/common/utility/memoize/memoize.spec.ts deleted file mode 100644 index 82b43c1d74..0000000000 --- a/src/app/common/utility/memoize/memoize.spec.ts +++ /dev/null @@ -1,38 +0,0 @@ -import memoize from './memoize'; - -describe('memoize', () => { - it('only calls function again if parameters are different', () => { - const add = jest.fn((a: number, b: number) => (a + b)); - const memoizedAdd = memoize(add); - - memoizedAdd(1, 1); - memoizedAdd(1, 1); - - expect(add).toHaveBeenCalledTimes(1); - - memoizedAdd(2, 2); - - expect(add).toHaveBeenCalledTimes(2); - }); - - it('deletes cached result when key expires', () => { - const add = jest.fn((a: number, b: number) => (a + b)); - const memoizedAdd = memoize(add, { maxSize: 1 }); - const cache = memoizedAdd.cache as Map; - - memoizedAdd(1, 1); - - expect(cache.values().next().value) - .toEqual(2); - expect(Array.from(cache.values()).length) - .toEqual(1); - - // This call should remove the previous result from the cache - memoizedAdd(2, 2); - - expect(cache.values().next().value) - .toEqual(4); - expect(Array.from(cache.values()).length) - .toEqual(1); - }); -}); diff --git a/src/app/common/utility/memoize/memoize.ts b/src/app/common/utility/memoize/memoize.ts deleted file mode 100644 index 69218c9351..0000000000 --- a/src/app/common/utility/memoize/memoize.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { memoize as lodashMemoize } from 'lodash'; - -import CacheKeyResolver from './CacheKeyResolver'; - -export interface MemoizeOptions { - maxSize?: number; -} - -export default function memoize any>( - fn: T, - options?: MemoizeOptions -) { - const { maxSize } = { maxSize: 1, ...options }; - const cache = new Map(); - const resolver = new CacheKeyResolver({ - maxSize, - onExpire: key => cache.delete(key), - }); - const memoized = lodashMemoize(fn, (...args) => resolver.getKey(...args)); - - memoized.cache = cache; - - return memoized; -} diff --git a/src/app/locale/LocaleProvider.tsx b/src/app/locale/LocaleProvider.tsx index 7c967e5072..b090e19597 100644 --- a/src/app/locale/LocaleProvider.tsx +++ b/src/app/locale/LocaleProvider.tsx @@ -1,8 +1,7 @@ import { createCurrencyService, CheckoutService, StoreConfig } from '@bigcommerce/checkout-sdk'; +import { memoizeOne } from '@bigcommerce/memoize'; import React, { Component, ReactNode } from 'react'; -import { memoize } from '../common/utility'; - import getLanguageService from './getLanguageService'; import LocaleContext from './LocaleContext'; @@ -20,7 +19,7 @@ class LocaleProvider extends Component { private languageService = getLanguageService(); private unsubscribe?: () => void; - private getContextValue = memoize((config?: StoreConfig) => { + private getContextValue = memoizeOne((config?: StoreConfig) => { return { currency: config ? createCurrencyService(config) : undefined, language: this.languageService, diff --git a/src/app/locale/getLanguageService.ts b/src/app/locale/getLanguageService.ts index 9d8159bb70..988b279ff0 100644 --- a/src/app/locale/getLanguageService.ts +++ b/src/app/locale/getLanguageService.ts @@ -1,6 +1,5 @@ import { createLanguageService, LanguageService } from '@bigcommerce/checkout-sdk'; - -import { memoize } from '../common/utility'; +import { memoize } from '@bigcommerce/memoize'; import { DEFAULT_TRANSLATIONS } from './translations'; @@ -11,4 +10,4 @@ function getLanguageService(): LanguageService { }); } -export default memoize(getLanguageService, { maxSize: 0 }); +export default memoize(getLanguageService); diff --git a/src/app/payment/Payment.tsx b/src/app/payment/Payment.tsx index fd45c5c1b0..4f419aa884 100644 --- a/src/app/payment/Payment.tsx +++ b/src/app/payment/Payment.tsx @@ -1,11 +1,12 @@ import { CheckoutSelectors, OrderRequestBody, PaymentMethod } from '@bigcommerce/checkout-sdk'; +import { memoizeOne } from '@bigcommerce/memoize'; import { compact, find, isEmpty, noop } from 'lodash'; import React, { Component, ReactNode } from 'react'; import { ObjectSchema } from 'yup'; import { withCheckout, CheckoutContextProps } from '../checkout'; import { ErrorModal, ErrorModalOnCloseProps } from '../common/error'; -import { memoize, EMPTY_ARRAY } from '../common/utility'; +import { EMPTY_ARRAY } from '../common/utility'; import { withLanguage, WithLanguageProps } from '../locale'; import { FlashAlert, FlashMessage } from '../ui/alert'; import { LoadingOverlay } from '../ui/loading'; @@ -68,7 +69,7 @@ class Payment extends Component { + private getContextValue = memoizeOne(() => { return { disableSubmit: this.disableSubmit, setSubmit: this.setSubmit, diff --git a/src/app/payment/creditCard/CreditCardExpiryField.tsx b/src/app/payment/creditCard/CreditCardExpiryField.tsx index 4899b82497..c8900b4f18 100644 --- a/src/app/payment/creditCard/CreditCardExpiryField.tsx +++ b/src/app/payment/creditCard/CreditCardExpiryField.tsx @@ -1,7 +1,7 @@ +import { memoizeOne } from '@bigcommerce/memoize'; import { FieldProps } from 'formik'; import React, { memo, useCallback, useMemo, ChangeEvent, FunctionComponent } from 'react'; -import { memoize } from '../../common/utility'; import { TranslatedString } from '../../locale'; import { FormField, TextInput } from '../../ui/form'; @@ -12,7 +12,7 @@ export interface CreditCardExpiryFieldProps { } const CreditCardExpiryField: FunctionComponent = ({ name }) => { - const handleChange = useCallback(memoize((field: FieldProps['field'], form: FieldProps['form']) => { + const handleChange = useCallback(memoizeOne((field: FieldProps['field'], form: FieldProps['form']) => { return (event: ChangeEvent) => { form.setFieldValue(field.name, formatCreditCardExpiryDate(event.target.value)); }; diff --git a/src/app/payment/creditCard/getCreditCardValidationSchema.ts b/src/app/payment/creditCard/getCreditCardValidationSchema.ts index 9e7323f8d4..cace783264 100644 --- a/src/app/payment/creditCard/getCreditCardValidationSchema.ts +++ b/src/app/payment/creditCard/getCreditCardValidationSchema.ts @@ -1,6 +1,6 @@ import { LanguageService } from '@bigcommerce/checkout-sdk'; +import { memoize } from '@bigcommerce/memoize'; import { cvv, expirationDate, number } from 'card-validator'; -import { memoize } from 'lodash'; import { object, string, ObjectSchema } from 'yup'; import { CreditCardFieldsetValues } from './CreditCardFieldset'; @@ -48,4 +48,4 @@ export default memoize(function getCreditCardValidationSchema({ } return object(schema); -}, (...args) => JSON.stringify(args)); +}); diff --git a/src/app/payment/paymentMethod/CreditCardPaymentMethod.tsx b/src/app/payment/paymentMethod/CreditCardPaymentMethod.tsx index 07bbfa74ef..4faa416593 100644 --- a/src/app/payment/paymentMethod/CreditCardPaymentMethod.tsx +++ b/src/app/payment/paymentMethod/CreditCardPaymentMethod.tsx @@ -1,4 +1,5 @@ import { CheckoutSelectors, Instrument, PaymentInitializeOptions, PaymentMethod, PaymentRequestOptions } from '@bigcommerce/checkout-sdk'; +import { memoizeOne } from '@bigcommerce/memoize'; import { find, noop } from 'lodash'; import React, { Component, ReactNode } from 'react'; import { ObjectSchema } from 'yup'; @@ -6,7 +7,7 @@ import { ObjectSchema } from 'yup'; import { withCheckout, CheckoutContextProps } from '../../checkout'; import { connectFormik, ConnectFormikProps } from '../../common/form'; import { MapToProps } from '../../common/hoc'; -import { memoize, EMPTY_ARRAY } from '../../common/utility'; +import { EMPTY_ARRAY } from '../../common/utility'; import { withLanguage, WithLanguageProps } from '../../locale'; import { LoadingOverlay } from '../../ui/loading'; import { configureCardValidator, getCreditCardValidationSchema, CreditCardFieldset, CreditCardFieldsetValues } from '../creditCard'; @@ -223,7 +224,7 @@ function mapFromCheckoutProps(): MapToProps< WithCheckoutCreditCardPaymentMethodProps, CreditCardPaymentMethodProps & ConnectFormikProps > { - const filterInstruments = memoize((instruments: Instrument[] = EMPTY_ARRAY, method: PaymentMethod) => + const filterInstruments = memoizeOne((instruments: Instrument[] = EMPTY_ARRAY, method: PaymentMethod) => instruments.filter(({ provider }) => provider === method.id) ); diff --git a/src/app/payment/paymentMethod/HostedWidgetPaymentMethod.tsx b/src/app/payment/paymentMethod/HostedWidgetPaymentMethod.tsx index c44546efbd..49da0737f9 100644 --- a/src/app/payment/paymentMethod/HostedWidgetPaymentMethod.tsx +++ b/src/app/payment/paymentMethod/HostedWidgetPaymentMethod.tsx @@ -1,6 +1,7 @@ import { CheckoutSelectors, CustomerInitializeOptions, CustomerRequestOptions, Instrument, PaymentInitializeOptions, PaymentMethod, PaymentRequestOptions } from '@bigcommerce/checkout-sdk'; +import { memoizeOne } from '@bigcommerce/memoize'; import classNames from 'classnames'; -import { find, memoize, noop, some } from 'lodash'; +import { find, noop, some } from 'lodash'; import React, { Component, ReactNode } from 'react'; import { withCheckout, CheckoutContextProps } from '../../checkout'; @@ -280,7 +281,7 @@ function mapFromCheckoutProps(): MapToProps< WithCheckoutHostedWidgetPaymentMethodProps, HostedWidgetPaymentMethodProps & ConnectFormikProps > { - const filterInstruments = memoize((instruments: Instrument[] = EMPTY_ARRAY, method: PaymentMethod) => + const filterInstruments = memoizeOne((instruments: Instrument[] = EMPTY_ARRAY, method: PaymentMethod) => instruments.filter(({ provider }) => provider === method.id) ); diff --git a/src/app/payment/paymentMethod/PaymentMethodList.tsx b/src/app/payment/paymentMethod/PaymentMethodList.tsx index 443032b22d..15f035ed7c 100644 --- a/src/app/payment/paymentMethod/PaymentMethodList.tsx +++ b/src/app/payment/paymentMethod/PaymentMethodList.tsx @@ -1,9 +1,9 @@ import { PaymentMethod } from '@bigcommerce/checkout-sdk'; +import { memoize } from '@bigcommerce/memoize'; import { find, noop } from 'lodash'; import React, { memo, useCallback, FunctionComponent } from 'react'; import { connectFormik, ConnectFormikProps } from '../../common/form'; -import { memoize } from '../../common/utility'; import { Checklist, ChecklistItem } from '../../ui/form'; import getUniquePaymentMethodId, { parseUniquePaymentMethodId } from './getUniquePaymentMethodId'; diff --git a/src/app/payment/storedInstrument/getInstrumentValidationSchema.ts b/src/app/payment/storedInstrument/getInstrumentValidationSchema.ts index 4cbbaa26c1..cd64422973 100644 --- a/src/app/payment/storedInstrument/getInstrumentValidationSchema.ts +++ b/src/app/payment/storedInstrument/getInstrumentValidationSchema.ts @@ -1,7 +1,7 @@ import { LanguageService } from '@bigcommerce/checkout-sdk'; +import { memoize } from '@bigcommerce/memoize'; import { cvv, number } from 'card-validator'; import creditCardType from 'credit-card-type'; -import { memoize } from 'lodash'; import { object, string, ObjectSchema, StringSchema } from 'yup'; import mapFromInstrumentCardType from './mapFromInstrumentCardType'; @@ -61,4 +61,4 @@ export default memoize(function getInstrumentValidationSchema({ } return object(schema); -}, (...args) => JSON.stringify(args)); +}); diff --git a/src/app/shipping/ShippingAddress.tsx b/src/app/shipping/ShippingAddress.tsx index 4f3700525d..ed2eb1dfb1 100644 --- a/src/app/shipping/ShippingAddress.tsx +++ b/src/app/shipping/ShippingAddress.tsx @@ -1,9 +1,8 @@ import { Address, CheckoutSelectors, Consignment, Country, CustomerAddress, CustomerRequestOptions, FormField, ShippingInitializeOptions, ShippingRequestOptions } from '@bigcommerce/checkout-sdk'; +import { memoizeOne } from '@bigcommerce/memoize'; import { noop } from 'lodash'; import React, { memo, useCallback, FunctionComponent } from 'react'; -import { memoize } from '../common/utility'; - import RemoteShippingAddress from './RemoteShippingAddress'; import ShippingAddressForm from './ShippingAddressForm'; @@ -59,7 +58,7 @@ const ShippingAddress: FunctionComponent = props => { signOut, ]); - const initializeShipping = useCallback(memoize((defaultOptions: ShippingInitializeOptions) => ( + const initializeShipping = useCallback(memoizeOne((defaultOptions: ShippingInitializeOptions) => ( (options?: ShippingInitializeOptions) => initialize({ ...defaultOptions, ...options, diff --git a/src/app/ui/accordion/Accordion.tsx b/src/app/ui/accordion/Accordion.tsx index 77c6da7736..43c51887a4 100644 --- a/src/app/ui/accordion/Accordion.tsx +++ b/src/app/ui/accordion/Accordion.tsx @@ -1,8 +1,7 @@ +import { memoizeOne } from '@bigcommerce/memoize'; import { noop } from 'lodash'; import React, { Component, ReactNode } from 'react'; -import { memoize } from '../../common/utility'; - import AccordionContext from './AccordionContext'; export interface AccordionProps { @@ -19,7 +18,7 @@ export interface AccordionState { export default class Accordion extends Component { state: AccordionState = {}; - private getContextValue = memoize(selectedItemId => { + private getContextValue = memoizeOne(selectedItemId => { return { onToggle: this.handleToggleItem, selectedItemId, diff --git a/src/app/ui/form/ChecklistItem.tsx b/src/app/ui/form/ChecklistItem.tsx index e2dbd4df13..9f09923d30 100644 --- a/src/app/ui/form/ChecklistItem.tsx +++ b/src/app/ui/form/ChecklistItem.tsx @@ -1,8 +1,8 @@ +import { memoizeOne } from '@bigcommerce/memoize'; import { FieldProps } from 'formik'; import { kebabCase } from 'lodash'; import React, { memo, useCallback, useContext, FunctionComponent, ReactNode } from 'react'; -import { memoize } from '../../common/utility'; import { AccordionItem, AccordionItemHeaderProps } from '../accordion'; import BasicFormField from './BasicFormField'; @@ -25,7 +25,7 @@ const ChecklistItem: FunctionComponent = ({ }) => { const { name = '' } = useContext(ChecklistContext) || {}; - const renderInput = useCallback(memoize((isSelected: boolean) => ({ field }: FieldProps) => ( + const renderInput = useCallback(memoizeOne((isSelected: boolean) => ({ field }: FieldProps) => ( = ({ value, ]); - const handleChange = useCallback(memoize((onToggle: (id: string) => void) => (selectedValue: string) => { + const handleChange = useCallback(memoizeOne((onToggle: (id: string) => void) => (selectedValue: string) => { if (value === selectedValue) { onToggle(value); } diff --git a/src/app/ui/form/Form.tsx b/src/app/ui/form/Form.tsx index 0a1d3e8d73..416754c4b2 100644 --- a/src/app/ui/form/Form.tsx +++ b/src/app/ui/form/Form.tsx @@ -1,9 +1,8 @@ +import { memoizeOne } from '@bigcommerce/memoize'; import { Form as FormikForm, FormikFormProps } from 'formik'; import { values } from 'lodash'; import React, { createRef, memo, useCallback, useRef, FunctionComponent } from 'react'; -import { memoize } from '../../common/utility'; - import FormProvider, { FormContextType } from './FormProvider'; export interface FormProps extends FormikFormProps { @@ -37,7 +36,7 @@ const Form: FunctionComponent = ({ } }; - const handleSubmitCapture = useCallback(memoize((setSubmitted: FormContextType['setSubmitted']) => { + const handleSubmitCapture = useCallback(memoizeOne((setSubmitted: FormContextType['setSubmitted']) => { return () => { setSubmitted(true); @@ -46,7 +45,7 @@ const Form: FunctionComponent = ({ }; }), [focusOnError]); - const renderContent = useCallback(memoize(({ setSubmitted }: FormContextType) => { + const renderContent = useCallback(memoizeOne(({ setSubmitted }: FormContextType) => { return (