Skip to content

Commit

Permalink
Merge pull request #81 from davidchin/optimise_billing
Browse files Browse the repository at this point in the history
CHECKOUT-4272: Avoid passing arrow function to billing components
  • Loading branch information
davidchin authored Aug 27, 2019
2 parents 1e5a4d5 + dcb3965 commit 9446fa6
Show file tree
Hide file tree
Showing 19 changed files with 396 additions and 224 deletions.
23 changes: 15 additions & 8 deletions src/app/address/AddressForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Address, Country, FormField } from '@bigcommerce/checkout-sdk';
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';

Expand Down Expand Up @@ -34,6 +35,10 @@ class AddressForm extends Component<AddressFormProps & WithLanguageProps> {
private containerRef: RefObject<HTMLElement> = createRef();
private nextElement?: HTMLElement | null;

private handleDynamicFormFieldChange: (name: string) => (value: string | string[]) => void = memoize(name => value => {
this.syncNonFormikValue(name, value);
}, { maxSize: 0 });

componentDidMount(): void {
const { current } = this.containerRef;

Expand Down Expand Up @@ -67,13 +72,9 @@ class AddressForm extends Component<AddressFormProps & WithLanguageProps> {
countryCode={ countryCode }
supportedCountries={ countriesWithAutocomplete }
field={ field }
onSelect={ this.onAutocompleteSelect }
onSelect={ this.handleAutocompleteSelect }
onToggleOpen={ onAutocompleteToggle }
onChange={ (value, isOpen) => {
if (!isOpen) {
this.syncNonFormikValue(AUTOCOMPLETE_FIELD_NAME, value);
}
} }
onChange={ this.handleAutocompleteChange }
apiKey={ googleMapsApiKey }
nextElement={ this.nextElement || undefined }
/>
Expand All @@ -82,7 +83,7 @@ class AddressForm extends Component<AddressFormProps & WithLanguageProps> {

return (
<DynamicFormField
onChange={ value => this.syncNonFormikValue(addressFieldName, value) }
onChange={ this.handleDynamicFormFieldChange(addressFieldName) }
// stateOrProvince can sometimes be a dropdown or input, so relying on id is not sufficient
key={ `${field.id}-${field.name}` }
parentFieldName={ field.custom ?
Expand Down Expand Up @@ -129,7 +130,13 @@ class AddressForm extends Component<AddressFormProps & WithLanguageProps> {
return fieldType as DynamicFormFieldType;
}

private onAutocompleteSelect: (
private handleAutocompleteChange: (value: string, isOpen: boolean) => void = (value, isOpen) => {
if (!isOpen) {
this.syncNonFormikValue(AUTOCOMPLETE_FIELD_NAME, value);
}
};

private handleAutocompleteSelect: (
place: google.maps.places.PlaceResult,
item: AutocompleteItem
) => void = (place, { value: autocompleteValue }) => {
Expand Down
22 changes: 15 additions & 7 deletions src/app/address/AddressSelect.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Address, CustomerAddress } from '@bigcommerce/checkout-sdk';
import React, { Component, FunctionComponent, ReactNode } from 'react';
import React, { memo, FunctionComponent, PureComponent, ReactNode } from 'react';

import { preventDefault } from '../common/dom';
import { TranslatedString } from '../locale';
Expand All @@ -15,12 +15,11 @@ export interface AddressSelectProps {
onUseNewAddress(currentAddress?: Address): void;
}

class AddressSelect extends Component<AddressSelectProps> {
class AddressSelect extends PureComponent<AddressSelectProps> {
render(): ReactNode {
const {
addresses,
selectedAddress,
onUseNewAddress,
} = this.props;

return (
Expand All @@ -29,8 +28,8 @@ class AddressSelect extends Component<AddressSelectProps> {
<DropdownTrigger dropdown={
<AddressSelectMenu
addresses={ addresses }
onSelectAddress={ this.onSelectAddress }
onUseNewAddress={ () => onUseNewAddress(selectedAddress) }
onSelectAddress={ this.handleSelectAddress }
onUseNewAddress={ this.handleUseNewAddress }
selectedAddress={ selectedAddress }
/>
}>
Expand All @@ -44,7 +43,7 @@ class AddressSelect extends Component<AddressSelectProps> {
);
}

private onSelectAddress: (newAddress: Address) => void = (newAddress: Address) => {
private handleSelectAddress: (newAddress: Address) => void = (newAddress: Address) => {
const {
onSelectAddress,
selectedAddress,
Expand All @@ -54,6 +53,15 @@ class AddressSelect extends Component<AddressSelectProps> {
onSelectAddress(newAddress);
}
};

private handleUseNewAddress: () => void = () => {
const {
selectedAddress,
onUseNewAddress,
} = this.props;

onUseNewAddress(selectedAddress);
};
}

const AddressSelectMenu: FunctionComponent<AddressSelectProps> = ({
Expand Down Expand Up @@ -102,4 +110,4 @@ const AddressSelectButton: FunctionComponent<AddressSelectButtonProps> = ({
</a>
);

export default AddressSelect;
export default memo(AddressSelect);
176 changes: 122 additions & 54 deletions src/app/address/CheckboxGroupFormField.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { FormFieldItem } from '@bigcommerce/checkout-sdk';
import { getIn, FieldArray } from 'formik';
import { difference, kebabCase, noop } from 'lodash';
import React, { FunctionComponent, ReactNode } from 'react';
import { getIn, FieldArray, FieldArrayRenderProps } from 'formik';
import { difference, kebabCase, noop, pick } from 'lodash';
import React, { memo, useCallback, ChangeEvent, FunctionComponent, ReactNode } from 'react';

import { FormFieldContainer, FormFieldError } from '../ui/form';

Expand All @@ -10,66 +10,134 @@ import DynamicInput from './DynamicInput';
import MultiCheckboxControl from './MultiCheckboxControl';

export interface CheckboxGroupFormFieldProps {
name: string;
id: string;
label: ReactNode;
name: string;
options: FormFieldItem[];
onChange?(values: string[]): void;
}

const CheckboxGroupFormField: FunctionComponent<CheckboxGroupFormFieldProps> = ({
type MultiCheckboxFormFieldProps = (
CheckboxGroupFormFieldProps &
Pick<FieldArrayRenderProps, 'push' | 'remove' | 'pop' | 'form'>
);

const MultiCheckboxFormField: FunctionComponent<MultiCheckboxFormFieldProps> = ({
form: { values, errors },
id,
label,
name,
onChange = noop,
options,
pop,
push,
remove,
}) => {
const handleSelectAll = useCallback(() => {
const checkedValues: string[] = getIn(values, name) || [];

difference(options.map(({ value }) => value), checkedValues)
.forEach(val => push(val));

onChange(getIn(values, name));
}, [
name,
onChange,
options,
push,
values,
]);

const handleSelectNone = useCallback(() => {
const checkedValues: string[] = getIn(values, name) || [];

checkedValues.forEach(() => pop());

onChange(getIn(values, name));
}, [
name,
onChange,
pop,
values,
]);

const handleInputChange = useCallback((event: ChangeEvent<HTMLInputElement>) => {
const checkedValues: string[] = getIn(values, name) || [];
const { value, checked } = event.target;

if (checked) {
push(value);
} else {
remove(checkedValues.indexOf(value));
}

onChange(getIn(values, name));
}, [
name,
onChange,
push,
remove,
values,
]);

return <FormFieldContainer hasError={ getIn(errors, name) && getIn(errors, name).length }>
{ label }

<MultiCheckboxControl
testId={ id }
onSelectedAll={ handleSelectAll }
onSelectedNone={ handleSelectNone }
/>

<DynamicInput
name={ name }
value={ getIn(values, name) || [] }
onChange={ handleInputChange }
fieldType={ DynamicFormFieldType.checkbox }
options={ options }
id={ id }
/>

<FormFieldError
name={ name }
testId={ `${kebabCase(name)}-field-error-message` }
/>
</FormFieldContainer>;
};

const CheckboxGroupFormField: FunctionComponent<CheckboxGroupFormFieldProps> = ({
id,
label,
name,
onChange,
options,
onChange = noop,
}) => (
<FieldArray
}) => {
const renderField = useCallback((renderProps: FieldArrayRenderProps) => (
<MultiCheckboxFormField
id={ id }
label={ label }
name={ name }
onChange={ onChange }
options={ options }
{ ...pick(renderProps, [
'form',
'pop',
'push',
'remove',
]) }
/>
), [
id,
label,
name,
onChange,
options,
]);

return <FieldArray
name={ name }
render={ ({ push, remove, pop, form: { values, errors } }) => (
<FormFieldContainer hasError={ getIn(errors, name) && getIn(errors, name).length }>
{ label }
<MultiCheckboxControl
testId={ id }
onSelectedAll={ () => {
const checkedValues: string[] = getIn(values, name) || [];
difference(options.map(({ value }) => value), checkedValues)
.forEach(val => push(val));

onChange(getIn(values, name));
}}
onSelectedNone={ () => {
const checkedValues: string[] = getIn(values, name) || [];
checkedValues.forEach(() => pop());
onChange(getIn(values, name));
}}
/>
<DynamicInput
name={ name }
value={ getIn(values, name) || [] }
onChange={e => {
const checkedValues: string[] = getIn(values, name) || [];
const { value, checked } = e.target;

if (checked) {
push(value);
} else {
remove(checkedValues.indexOf(value));
}

onChange(getIn(values, name));
} }
fieldType={ DynamicFormFieldType.checkbox }
options={ options }
id={ id }
/>
<FormFieldError
name={ name }
testId={ `${kebabCase(name)}-field-error-message` }
/>
</FormFieldContainer>
)}
/>
);
render={ renderField }
/>;
};

export default CheckboxGroupFormField;
export default memo(CheckboxGroupFormField);
Loading

0 comments on commit 9446fa6

Please sign in to comment.