diff --git a/README.md b/README.md index 5eae3b63..117b0935 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,8 @@ We use typescript to create the Type definition. You can view directly in IDE. B | Prop | Description | Type | Default | | --- | --- | --- | --- | -| name | Field name path | [NamePath](#namepath)[] | - | +| dependencies | Will re-render if dependencies changed | [NamePath](#namepath)[] | - | +| name | Field name path | [NamePath](#namepath) | - | | rules | Validate rules | [Rule](#rule)[] | - | | shouldUpdate | Check if Field should update | (prevValues, nextValues): boolean | - | | trigger | Collect value update by event trigger | string | onChange | diff --git a/examples/StateForm-basic.tsx b/examples/StateForm-basic.tsx index df68c7d9..c6745840 100644 --- a/examples/StateForm-basic.tsx +++ b/examples/StateForm-basic.tsx @@ -35,7 +35,7 @@ export default class Demo extends React.Component {

Show additional field when `username` is `111`

- prev.username !== next.username}> + {(control, meta, context) => { const { username } = context.getFieldsValue(); return username === '111' ? : null; diff --git a/package.json b/package.json index 56db8736..bbb0d89c 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "devDependencies": { "@types/react": "^16.8.19", "@types/react-dom": "^16.8.4", + "@types/warning": "^3.0.0", "enzyme": "^3.1.0", "enzyme-adapter-react-16": "^1.0.2", "enzyme-to-json": "^3.1.4", @@ -50,7 +51,6 @@ "react-dom": "^16.8.6", "react-redux": "^4.4.10", "react-router": "^3.0.0", - "react-test-renderer": "^16.1.1", "redux": "^3.7.2" }, "pre-commit": [ @@ -58,7 +58,6 @@ ], "dependencies": { "async-validator": "^1.11.2", - "babel-runtime": "6.x", "lodash": "^4.17.4", "rc-util": "^4.6.0", "warning": "^4.0.3" diff --git a/src/Field.tsx b/src/Field.tsx index 8a3f529d..654847ca 100644 --- a/src/Field.tsx +++ b/src/Field.tsx @@ -36,7 +36,10 @@ export interface FieldProps { | React.ReactElement | ((control: ChildProps, meta: Meta, form: FormInstance) => React.ReactNode); rules?: Rule[]; - /** Set up `dependencies` field. When dependencies field update and current field is touched, will trigger validate rules. */ + /** + * Set up `dependencies` field. + * When dependencies field update and current field is touched, will trigger validate rules and render. + */ dependencies?: NamePath[]; trigger?: string; validateTrigger?: string | string[]; @@ -123,7 +126,7 @@ class Field extends React.Component implements FieldEnti namePathList: InternalNamePath[] | null, info: NotifyInfo, ) => { - const { shouldUpdate } = this.props; + const { shouldUpdate, dependencies = [] } = this.props; const { getFieldsValue, getFieldError }: FormInstance = this.context; const values = getFieldsValue(); const namePath = this.getNamePath(); @@ -169,11 +172,15 @@ class Field extends React.Component implements FieldEnti default: /** * - If `namePath` exists in `namePathList`, means it's related value and should update. + * - If `dependencies` exists in `namePathList`, means upstream trigger update. * - If `shouldUpdate` provided, use customize logic to update the field * - else to check if value changed */ if ( (namePathList && containsNamePath(namePathList, namePath)) || + dependencies.some(dependency => + containsNamePath(namePathList, getNamePath(dependency)), + ) || (shouldUpdate ? shouldUpdate(prevStore, values, info) : prevValue !== curValue) ) { this.forceUpdate(); @@ -327,7 +334,8 @@ class Field extends React.Component implements FieldEnti const { child, isFunction } = this.getOnlyChild(children); if (!child) { - return children; + // Return origin `children` if is not a function + return isFunction ? child : children; } // Not need to `cloneElement` since user can handle this in render function self diff --git a/src/useForm.ts b/src/useForm.ts index 146e9aa7..fa6bf91a 100644 --- a/src/useForm.ts +++ b/src/useForm.ts @@ -324,7 +324,7 @@ export class FormStore { onValuesChange(changedValues, this.store); } - this.triggerOnFieldsChange([namePath]); + this.triggerOnFieldsChange([namePath, ...childrenFields]); }; // Let all child Field get update. @@ -365,8 +365,8 @@ export class FormStore { if (!children.has(field)) { children.add(field); - if (field.isFieldTouched()) { - const fieldNamePath = field.getNamePath(); + const fieldNamePath = field.getNamePath(); + if (field.isFieldTouched() && fieldNamePath.length) { childrenFields.push(fieldNamePath); fillChildren(fieldNamePath); } diff --git a/tests/common/index.js b/tests/common/index.js index df37b29b..477f5174 100644 --- a/tests/common/index.js +++ b/tests/common/index.js @@ -1,4 +1,5 @@ import timeout from './timeout'; +import InfoField from './InfoField'; export async function changeValue(wrapper, value) { wrapper.find('input').simulate('change', { target: { value } }); @@ -17,3 +18,7 @@ export function matchError(wrapper, error) { expect(wrapper.find('.errors li').text()).toBe(error); } } + +export function getField(wrapper, index = 0) { + return wrapper.find(InfoField).at(index); +} diff --git a/tests/dependencies.test.js b/tests/dependencies.test.js new file mode 100644 index 00000000..1033c60b --- /dev/null +++ b/tests/dependencies.test.js @@ -0,0 +1,33 @@ +import React from 'react'; +import { mount } from 'enzyme'; +import Form from '../src'; +import InfoField from './common/InfoField'; +import { changeValue, matchError, getField } from './common'; + +describe('dependencies', () => { + it('touched', async () => { + let form = null; + + const wrapper = mount( +
+
{ + form = instance; + }} + > + + + +
, + ); + + // Not trigger if not touched + await changeValue(getField(wrapper, 0), ''); + matchError(getField(wrapper, 1), false); + + // Trigger if touched + form.setFields([{ name: 'field_2', touched: true }]); + await changeValue(getField(wrapper, 0), ''); + matchError(getField(wrapper, 1), true); + }); +});