-
Notifications
You must be signed in to change notification settings - Fork 222
✨ better behaviour for <FormState /> initialValue changes #446
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
/* eslint-disable no-case-declarations */ | ||
import * as React from 'react'; | ||
import isEqual from 'lodash/isEqual'; | ||
import isArray from 'lodash/isArray'; | ||
|
@@ -52,6 +53,7 @@ interface Props<Fields> { | |
validators?: Partial<ValidatorDictionary<Fields>>; | ||
onSubmit?: SubmitHandler<Fields>; | ||
validateOnSubmit?: boolean; | ||
onInitialValuesChange?: 'reset-all' | 'reset-where-changed' | 'ignore'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm thinking it would be helpful/safer to make these values constants or maybe an exported enum? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For those with typescript supporting editors string unions autocomplete just as well either way, for those without they feel like a more naturalJS API. Personally for component apis I enjoy the convenience provided by string unions. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
TIL There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
children(form: FormDetails<Fields>): React.ReactNode; | ||
} | ||
|
||
|
@@ -68,21 +70,25 @@ export default class FormState< | |
static List = List; | ||
static Nested = Nested; | ||
|
||
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; | ||
} | ||
|
||
state = createFormState(this.props.initialValues); | ||
|
@@ -127,6 +133,16 @@ export default class FormState< | |
}); | ||
} | ||
|
||
@bind() | ||
public reset() { | ||
return new Promise(resolve => { | ||
this.setState( | ||
(_state, props) => createFormState(props.initialValues), | ||
() => resolve(), | ||
); | ||
}); | ||
} | ||
|
||
private get dirty() { | ||
return this.state.dirtyFields.length > 0; | ||
} | ||
|
@@ -184,22 +200,18 @@ export default class FormState< | |
} | ||
} | ||
|
||
const errors = await onSubmit(formData); | ||
const errors = (await onSubmit(formData)) || []; | ||
|
||
if (!this.mounted) { | ||
return; | ||
} | ||
|
||
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() | ||
|
@@ -373,6 +385,36 @@ export default class FormState< | |
} | ||
} | ||
|
||
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> { | ||
const fields: FieldStates<Fields> = mapObject(values, value => { | ||
return { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is probably the most asked question I get, so I snuck it in here since it's sorta on theme