@shopify/react-form-state
Advanced tools
Comparing version 0.5.6 to 0.6.0
@@ -7,5 +7,11 @@ # Changelog | ||
## [0.6] | ||
### Added | ||
- You can control how `<FormState />` reacts to changes in the initialValue prop using onInitialValueChanged. | ||
## [0.5] | ||
## Added | ||
### Added | ||
@@ -16,3 +22,3 @@ - `<List />` supports `getChildKey` to provide custom `key`s for it's children. [#387](https://github.com/Shopify/quilt/pull/387) | ||
## Fixed | ||
### Fixed | ||
@@ -19,0 +25,0 @@ - `<List />` no longer breaks on name generation. |
@@ -33,2 +33,3 @@ import * as React from 'react'; | ||
validateOnSubmit?: boolean; | ||
onInitialValuesChange?: 'reset-all' | 'reset-where-changed' | 'ignore'; | ||
children(form: FormDetails<Fields>): React.ReactNode; | ||
@@ -45,3 +46,3 @@ } | ||
static Nested: typeof Nested; | ||
static getDerivedStateFromProps<T>(newProps: Props<T>, oldState?: State<T>): State<T> | null; | ||
static getDerivedStateFromProps<T>(newProps: Props<T>, oldState: State<T>): State<T> | null; | ||
state: State<Fields>; | ||
@@ -54,2 +55,3 @@ private mounted; | ||
validateForm(): Promise<{}>; | ||
reset(): Promise<{}>; | ||
private readonly dirty; | ||
@@ -60,3 +62,2 @@ private readonly valid; | ||
private submit; | ||
private reset; | ||
private fieldWithHandlers; | ||
@@ -63,0 +64,0 @@ private updateField; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var tslib_1 = require("tslib"); | ||
/* eslint-disable no-case-declarations */ | ||
var React = tslib_1.__importStar(require("react")); | ||
@@ -20,12 +21,17 @@ var isEqual_1 = tslib_1.__importDefault(require("lodash/isEqual")); | ||
FormState.getDerivedStateFromProps = function (newProps, oldState) { | ||
var newInitialValues = newProps.initialValues; | ||
if (oldState == null) { | ||
return createFormState(newInitialValues); | ||
var initialValues = newProps.initialValues, onInitialValuesChange = newProps.onInitialValuesChange; | ||
switch (onInitialValuesChange) { | ||
case 'ignore': | ||
return null; | ||
case 'reset-where-changed': | ||
return reconcileFormState(initialValues, oldState); | ||
case 'reset-all': | ||
default: | ||
var oldInitialValues = initialValuesFromFields(oldState.fields); | ||
var valuesMatch = isEqual_1.default(oldInitialValues, initialValues); | ||
if (valuesMatch) { | ||
return null; | ||
} | ||
return createFormState(initialValues); | ||
} | ||
var oldInitialValues = initialValuesFromFields(oldState.fields); | ||
var shouldReinitialize = !isEqual_1.default(oldInitialValues, newInitialValues); | ||
if (shouldReinitialize) { | ||
return createFormState(newInitialValues); | ||
} | ||
return null; | ||
}; | ||
@@ -66,2 +72,8 @@ FormState.prototype.componentDidMount = function () { | ||
}; | ||
FormState.prototype.reset = function () { | ||
var _this = this; | ||
return new Promise(function (resolve) { | ||
_this.setState(function (_state, props) { return createFormState(props.initialValues); }, function () { return resolve(); }); | ||
}); | ||
}; | ||
Object.defineProperty(FormState.prototype, "dirty", { | ||
@@ -131,10 +143,13 @@ get: function () { | ||
case 3: | ||
errors = _b.sent(); | ||
errors = (_b.sent()) || []; | ||
if (!this.mounted) { | ||
return [2 /*return*/]; | ||
} | ||
if (errors) { | ||
if (errors.length > 0) { | ||
this.updateRemoteErrors(errors); | ||
this.setState({ submitting: false }); | ||
} | ||
this.setState({ submitting: false }); | ||
else { | ||
this.setState({ submitting: false, errors: errors }); | ||
} | ||
return [2 /*return*/]; | ||
@@ -145,5 +160,2 @@ } | ||
}; | ||
FormState.prototype.reset = function () { | ||
this.setState(function (_state, props) { return createFormState(props.initialValues); }); | ||
}; | ||
FormState.prototype.fieldWithHandlers = function (field, fieldPath) { | ||
@@ -255,6 +267,6 @@ return tslib_1.__assign({}, field, { name: fieldPath, onChange: this.updateField.bind(this, fieldPath), onBlur: this.blurField.bind(this, fieldPath) }); | ||
lodash_decorators_1.bind() | ||
], FormState.prototype, "submit", null); | ||
], FormState.prototype, "reset", null); | ||
tslib_1.__decorate([ | ||
lodash_decorators_1.bind() | ||
], FormState.prototype, "reset", null); | ||
], FormState.prototype, "submit", null); | ||
tslib_1.__decorate([ | ||
@@ -267,2 +279,19 @@ lodash_decorators_1.memoize(), | ||
exports.default = FormState; | ||
function reconcileFormState(values, oldState) { | ||
var oldFields = oldState.fields; | ||
var dirtyFields = new Set(oldState.dirtyFields); | ||
var fields = utilities_1.mapObject(values, function (value, key) { | ||
var oldField = oldFields[key]; | ||
if (value === oldField.initialValue) { | ||
return oldField; | ||
} | ||
dirtyFields.delete(key); | ||
return { | ||
value: value, | ||
initialValue: value, | ||
dirty: false, | ||
}; | ||
}); | ||
return tslib_1.__assign({}, oldState, { dirtyFields: Array.from(dirtyFields), fields: fields }); | ||
} | ||
function createFormState(values) { | ||
@@ -269,0 +298,0 @@ var fields = utilities_1.mapObject(values, function (value) { |
@@ -20,3 +20,3 @@ # FAQ | ||
You can do this by setting a `ref` on your `<FormState />`, and calling `validateForm` on the instance passed in. | ||
You can do this by setting a [`ref`](https://reactjs.org/docs/refs-and-the-dom.html#creating-refs) on your `<FormState />`, and calling `validateForm` on the instance passed in. | ||
@@ -42,4 +42,51 @@ ```typescript | ||
## My form keeps resetting for no reason! / My form is resetting whenever I change an input! | ||
By default `<FormState />` resets whenever any value in your `initialValues` changes. If you are basing your initial values on existing state, this lets it update when your state changes (usually this would be the result of submitting). | ||
If this is happening on each rerender, it is likely that you are generating your `initialValues` in some way that is different each time. This can happen when you construct `Date` objects, `UUID`s, or other dynamic values inline. You can solve this by `memoize`ing your initial value creation or creating dates and other dynamic data only once outside of your component's `render` method. | ||
```typescript | ||
// Bad! | ||
function MyForm() { | ||
return ( | ||
<FormState | ||
initialValues={ | ||
publicationDate: new Date(), | ||
text: '', | ||
} | ||
> | ||
{({fields}) => /* markup*/ } | ||
</FormState> | ||
); | ||
} | ||
// Good! | ||
const today = new Date(); | ||
function MyForm() { | ||
return ( | ||
<FormState | ||
initialValues={ | ||
publicationDate: today, | ||
text: '', | ||
} | ||
> | ||
{({fields}) => /* markup*/ } | ||
</FormState> | ||
); | ||
} | ||
``` | ||
## Can I have more control over what happens when initialValues change? | ||
You can control how `<FormState />` reacts to changes in the `initialValue` prop using `onInitialValueChanged`. This prop has three options: | ||
- (default) `reset-all`: Reset the entire form when `initialValues` changes. | ||
- `reset-where-changed`: Reset only the changed field objects when `initialValues` changes. | ||
- `ignore`: Ignore changes to the `initialValues` prop. This option makes `<FormState />` behave like a [fully controlled component](https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html#recommendation-fully-controlled-component). You will generally want to accompany this option with a [`key`](https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html#recommendation-fully-uncontrolled-component-with-a-key) or [`ref`](https://reactjs.org/docs/refs-and-the-dom.html#creating-refs). | ||
## More questions | ||
Have a question that you think should be included in our FAQ? Please help us by creating an [issue](https://github.com/Shopify/quilt/issues/new?template=ENHANCEMENT.md) or opening a pull request. |
{ | ||
"name": "@shopify/react-form-state", | ||
"version": "0.5.6", | ||
"version": "0.6.0", | ||
"license": "MIT", | ||
@@ -5,0 +5,0 @@ "description": "Manage react forms tersely and type-safe with no magic.", |
@@ -0,1 +1,2 @@ | ||
/* eslint-disable no-case-declarations */ | ||
import * as React from 'react'; | ||
@@ -55,2 +56,3 @@ import isEqual from 'lodash/isEqual'; | ||
validateOnSubmit?: boolean; | ||
onInitialValuesChange?: 'reset-all' | 'reset-where-changed' | 'ignore'; | ||
children(form: FormDetails<Fields>): React.ReactNode; | ||
@@ -72,17 +74,21 @@ } | ||
static getDerivedStateFromProps<T>(newProps: Props<T>, oldState?: State<T>) { | ||
const newInitialValues = newProps.initialValues; | ||
static getDerivedStateFromProps<T>(newProps: Props<T>, oldState: State<T>) { | ||
const {initialValues, onInitialValuesChange} = newProps; | ||
if (oldState == null) { | ||
return createFormState(newInitialValues); | ||
} | ||
switch (onInitialValuesChange) { | ||
case 'ignore': | ||
return null; | ||
case 'reset-where-changed': | ||
return reconcileFormState(initialValues, oldState); | ||
case 'reset-all': | ||
default: | ||
const oldInitialValues = initialValuesFromFields(oldState.fields); | ||
const valuesMatch = isEqual(oldInitialValues, initialValues); | ||
const oldInitialValues = initialValuesFromFields(oldState.fields); | ||
const shouldReinitialize = !isEqual(oldInitialValues, newInitialValues); | ||
if (valuesMatch) { | ||
return null; | ||
} | ||
if (shouldReinitialize) { | ||
return createFormState(newInitialValues); | ||
return createFormState(initialValues); | ||
} | ||
return null; | ||
} | ||
@@ -132,2 +138,12 @@ | ||
@bind() | ||
public reset() { | ||
return new Promise(resolve => { | ||
this.setState( | ||
(_state, props) => createFormState(props.initialValues), | ||
() => resolve(), | ||
); | ||
}); | ||
} | ||
private get dirty() { | ||
@@ -190,3 +206,3 @@ return this.state.dirtyFields.length > 0; | ||
const errors = await onSubmit(formData); | ||
const errors = (await onSubmit(formData)) || []; | ||
@@ -197,14 +213,10 @@ if (!this.mounted) { | ||
if (errors) { | ||
if (errors.length > 0) { | ||
this.updateRemoteErrors(errors); | ||
this.setState({submitting: false}); | ||
} else { | ||
this.setState({submitting: false, errors}); | ||
} | ||
this.setState({submitting: false}); | ||
} | ||
@bind() | ||
private reset() { | ||
this.setState((_state, props) => createFormState(props.initialValues)); | ||
} | ||
@memoize() | ||
@@ -381,2 +393,32 @@ @bind() | ||
function reconcileFormState<Fields>( | ||
values: Fields, | ||
oldState: State<Fields>, | ||
): State<Fields> { | ||
const {fields: oldFields} = oldState; | ||
const dirtyFields = new Set(oldState.dirtyFields); | ||
const fields: FieldStates<Fields> = mapObject(values, (value, key) => { | ||
const oldField = oldFields[key]; | ||
if (value === oldField.initialValue) { | ||
return oldField; | ||
} | ||
dirtyFields.delete(key); | ||
return { | ||
value, | ||
initialValue: value, | ||
dirty: false, | ||
}; | ||
}); | ||
return { | ||
...oldState, | ||
dirtyFields: Array.from(dirtyFields), | ||
fields, | ||
}; | ||
} | ||
function createFormState<Fields>(values: Fields): State<Fields> { | ||
@@ -383,0 +425,0 @@ const fields: FieldStates<Fields> = mapObject(values, value => { |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
167375
3589