Skip to content
This repository has been archived by the owner on Jan 10, 2025. It is now read-only.

🔥 Remove lodash in react-form-state #475

Merged
merged 3 commits into from
Jan 25, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions packages/react-form-state/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@
},
"homepage": "https://github.com/Shopify/quilt/blob/master/packages/react-form-state/README.md",
"dependencies": {
"lodash": "^4.17.10",
"lodash-decorators": "^6.0.0",
"tslib": "^1.9.3"
},
"peerDependencies": {
Expand Down
38 changes: 19 additions & 19 deletions packages/react-form-state/src/FormState.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
/* eslint-disable no-case-declarations */
import * as React from 'react';
import isEqual from 'lodash/isEqual';
import isArray from 'lodash/isArray';
import set from 'lodash/set';
import {memoize, bind} from 'lodash-decorators';

import {mapObject} from './utilities';
import {mapObject, set, isEqual} from './utilities';
TzviPM marked this conversation as resolved.
Show resolved Hide resolved
import {
FieldDescriptors,
FieldState,
Expand Down Expand Up @@ -93,6 +89,7 @@ export default class FormState<

state = createFormState(this.props.initialValues);
private mounted = false;
private fieldsWithHandlers = new WeakMap();

componentDidMount() {
this.mounted = true;
Expand Down Expand Up @@ -133,15 +130,14 @@ export default class FormState<
});
}

@bind()
public reset() {
public reset = () => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bind can be replaced with class members and arrow functions.

return new Promise(resolve => {
this.setState(
(_state, props) => createFormState(props.initialValues),
() => resolve(),
);
});
}
};

private get dirty() {
return this.state.dirtyFields.length > 0;
Expand Down Expand Up @@ -172,8 +168,7 @@ export default class FormState<
return fieldDescriptors;
}

@bind()
private async submit(event?: Event) {
private submit = async (event?: Event) => {
const {onSubmit, validateOnSubmit} = this.props;
const {formData} = this;

Expand Down Expand Up @@ -212,21 +207,26 @@ export default class FormState<
} else {
this.setState({submitting: false, errors});
}
}
};

@memoize()
@bind()
private fieldWithHandlers<Key extends keyof Fields>(
private fieldWithHandlers = <Key extends keyof Fields>(
field: FieldStates<Fields>[Key],
fieldPath: Key,
) {
return {
) => {
if (this.fieldsWithHandlers.has(field)) {
// eslint-disable-next-line typescript/no-non-null-assertion
TzviPM marked this conversation as resolved.
Show resolved Hide resolved
return this.fieldsWithHandlers.get(field)!;
}

const result = {
...(field as FieldState<Fields[Key]>),
name: fieldPath,
name: String(fieldPath),
onChange: this.updateField.bind(this, fieldPath),
onBlur: this.blurField.bind(this, fieldPath),
};
}
this.fieldsWithHandlers.set(field, result);
return result;
};

private updateField<Key extends keyof Fields>(
fieldPath: Key,
Expand Down Expand Up @@ -446,7 +446,7 @@ function runValidator<T, F>(
return validate(value, fields);
}

if (!isArray(validate)) {
if (!Array.isArray(validate)) {
TzviPM marked this conversation as resolved.
Show resolved Hide resolved
// eslint-disable-next-line consistent-return
return;
}
Expand Down
24 changes: 14 additions & 10 deletions packages/react-form-state/src/components/List.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import * as React from 'react';
import get from 'lodash/get';
import {memoize, bind} from 'lodash-decorators';

import {FieldDescriptor, FieldDescriptors, ValueMapper} from '../types';
import {mapObject, replace} from '../utilities';
Expand All @@ -15,6 +13,8 @@ export default class List<Fields> extends React.Component<
Props<Fields>,
never
> {
private changeHandlers = new Map<string, {(newValue: any): void}>();

shouldComponentUpdate(nextProps) {
const {
field: {
Expand Down Expand Up @@ -45,14 +45,14 @@ export default class List<Fields> extends React.Component<
const innerFields: FieldDescriptors<Fields> = mapObject(
fieldValues,
(value, fieldPath) => {
const initialFieldValue = get(initialValue, [index, fieldPath]);
const initialFieldValue = initialValue[index][fieldPath];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not a transparent replacement.
get(initialValue, [index, fieldPath]) this returns undefined
whereas
initialValue[index][fieldPath]; throws an exception
if initialValue[index] yields undefined

return {
value,
onBlur,
name: `${name}.${index}.${fieldPath}`,
initialValue: initialFieldValue,
dirty: value !== initialFieldValue,
error: get(error, [index, fieldPath]),
error: error && error[index] && error[index][fieldPath],
TzviPM marked this conversation as resolved.
Show resolved Hide resolved
onChange: this.handleChange({index, key: fieldPath}),
};
},
Expand All @@ -69,16 +69,18 @@ export default class List<Fields> extends React.Component<
});
}

@memoize()
@bind()
private handleChange<Key extends keyof Fields>({
private handleChange = <Key extends keyof Fields>({
index,
key,
}: {
index: number;
key: Key;
}) {
return (newValue: Fields[Key] | ValueMapper<Fields[Key]>) => {
}) => {
const hashKey = `${index}:${key}`;
if (this.changeHandlers.has(hashKey)) {
return this.changeHandlers.get(hashKey);
}
const handler = (newValue: Fields[Key] | ValueMapper<Fields[Key]>) => {
const {
field: {onChange},
} = this.props;
Expand All @@ -95,5 +97,7 @@ export default class List<Fields> extends React.Component<
return replace(value, index, newItem);
});
};
}
this.changeHandlers.set(hashKey, handler);
return handler;
};
}
21 changes: 12 additions & 9 deletions packages/react-form-state/src/components/Nested.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import * as React from 'react';
import get from 'lodash/get';
import {memoize, bind} from 'lodash-decorators';

import {FieldDescriptor, FieldDescriptors, ValueMapper} from '../types';
import {mapObject} from '../utilities';
Expand All @@ -14,6 +12,8 @@ export default class Nested<Fields> extends React.Component<
Props<Fields>,
never
> {
private changeHandlers = new Map<keyof Fields, Function>();

shouldComponentUpdate(nextProps) {
const {
field: {
Expand Down Expand Up @@ -49,7 +49,7 @@ export default class Nested<Fields> extends React.Component<
name: `${name}.${fieldPath}`,
initialValue: initialFieldValue,
dirty: value !== initialFieldValue,
error: get(error, fieldPath),
error: error && error[fieldPath],
onChange: this.handleChange(fieldPath),
};
},
Expand All @@ -58,10 +58,11 @@ export default class Nested<Fields> extends React.Component<
return children(innerFields);
}

@memoize()
@bind()
private handleChange<Key extends keyof Fields>(key: Key) {
return (newValue: Fields[Key] | ValueMapper<Fields>) => {
private handleChange = <Key extends keyof Fields>(key: Key) => {
if (this.changeHandlers.has(key)) {
return this.changeHandlers.get(key);
}
const handler = (newValue: Fields[Key] | ValueMapper<Fields>) => {
const {
field: {onChange},
} = this.props;
Expand All @@ -71,10 +72,12 @@ export default class Nested<Fields> extends React.Component<
...(value as any),
[key]:
typeof newValue === 'function'
? newValue(value[key as string])
? (newValue as Function)(value[key as string])
: newValue,
};
});
};
}
this.changeHandlers.set(key, handler);
return handler;
};
}
74 changes: 68 additions & 6 deletions packages/react-form-state/src/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,10 @@ export function mapObject<Input, Output>(
input: Input,
mapper: (value: any, key: keyof Input) => any,
) {
return Object.keys(input)
.map(key => [key, input[key]])
.reduce((accumulator: any, [key, value]) => {
accumulator[key] = mapper(value, key as keyof Input);
return accumulator;
}, {}) as Output;
return Object.entries(input).reduce((accumulator: any, [key, value]) => {
accumulator[key] = mapper(value, key as keyof Input);
return accumulator;
}, {}) as Output;
}

export function push<T>(array: T[], ...values: T[]) {
Expand All @@ -27,3 +25,67 @@ export function replace<T>(array: T[], targetIndex: number, newValue: T) {
return newValue;
});
}

export function set<InputType extends Object>(
rootObject: InputType,
path: string[],
value: any,
) {
if (path.length === 0) {
return rootObject;
} else if (path.length === 1) {
return {
...(rootObject as any),
[path[0]]: value,
};
} else {
const [current, ...rest] = path;
return {
...(rootObject as any),
[current]: set(rootObject[current], rest, value),
} as InputType;
}
}

export function isEqual(value: any, baseline: any) {
if (value === baseline) {
return true;
}
if (typeof value !== typeof baseline) {
return false;
}
if (Array.isArray(value)) {
if (!Array.isArray(baseline)) {
return false;
}
if (value.length !== baseline.length) {
return false;
}
for (let iter = 0; iter < value.length; iter++) {
if (!isEqual(value[iter], baseline[iter])) {
return false;
}
}
return true;
}
if (typeof value === 'object') {
if (value === null) {
return baseline === null;
}
if (baseline === null) {
return false;
}
const keysInValue = Object.keys(value);
const keysInBaseline = Object.keys(baseline);
if (!isEqual(keysInValue, keysInBaseline)) {
return false;
}
for (const key of keysInValue) {
if (!isEqual(value[key], baseline[key])) {
TzviPM marked this conversation as resolved.
Show resolved Hide resolved
return false;
}
}
return true;
}
return false;
}
11 changes: 8 additions & 3 deletions packages/react-form-state/src/validators.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import toString from 'lodash/toString';
import isArray from 'lodash/isArray';
import {mapObject} from './utilities';

interface Matcher<Input, Fields> {
Expand Down Expand Up @@ -53,7 +51,7 @@ export function validateNested<Input extends Object, Fields>(
return validate(value, fields);
}

if (!isArray(validate)) {
if (!Array.isArray(validate)) {
// eslint-disable-next-line consistent-return
return;
}
Expand Down Expand Up @@ -179,4 +177,11 @@ const validators = {
},
};

function toString(obj) {
if (obj == null) {
return '';
}
return obj.toString();
}

export default validators;
14 changes: 1 addition & 13 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5245,13 +5245,6 @@ lodash-decorators@^4.3.5:
dependencies:
tslib "^1.7.1"

lodash-decorators@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/lodash-decorators/-/lodash-decorators-6.0.0.tgz#4e0639ba639d738e5f4993acf54bf292cc95d851"
integrity sha512-rGNmvlPs/hmXM53Bso2+OAj/x7k6MiQxI2GIW537vuQ2ojfCJdohjzM+WC7r3glJgC5yBNzD5IdXkF+vluTr0A==
dependencies:
tslib "^1.9.2"

lodash._reinterpolate@~3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d"
Expand Down Expand Up @@ -5322,11 +5315,6 @@ lodash@^4.13.1, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.4, lodash@^4.17.5,
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.5.tgz#99a92d65c0272debe8c96b6057bc8fbfa3bed511"
integrity sha512-svL3uiZf1RwhH+cWrfZn3A4+U58wbP0tGVTLQPbjplZxZ8ROD9VLuNgsRniTlLe7OlSqR79RUehXgpBW/s0IQw==

lodash@^4.17.10:
version "4.17.10"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7"
integrity sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==

log-symbols@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a"
Expand Down Expand Up @@ -7788,7 +7776,7 @@ ts-jest@^23.10.4:
semver "^5.5"
yargs-parser "10.x"

tslib@^1.7.1, tslib@^1.9.2, tslib@^1.9.3:
tslib@^1.7.1, tslib@^1.9.3:
version "1.9.3"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286"
integrity sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==
Expand Down