diff --git a/packages/core/package.json b/packages/core/package.json index 55b13b6..8786536 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,62 +1,62 @@ { - "name": "react-forms-processor", - "version": "0.0.35", - "description": "A forms processor for statically declaring forms with dynamic behaviour", - "main": "dist/index.js", - "scripts": { - "test": "jest", - "glow": "glow", - "glow:watch": "glow --watch", - "start": "webpack-dev-server --mode development", - "transpile": "babel src -d dist --copy-files", - "prepublishOnly": "npm run transpile", - "build": "webpack --mode production", - "dev:watch": "webpack --mode development --watch", - "postbuild": "yarn build:flow", - "build:flow": "../../node_modules/flow-copy-source/bin/flow-copy-source.js --verbose --ignore '**/*{story,test}.js' src dist" - }, - "license": "MIT", - "peerDependencies": { - "react": "^16.4", - "react-dom": "^16.4" - }, - "devDependencies": { - "babel-cli": "^6.26.0", - "babel-core": "^6.26.3", - "babel-jest": "^22.4.3", - "babel-loader": "^7.1.4", - "babel-plugin-transform-class-properties": "^6.24.1", - "babel-plugin-transform-object-rest-spread": "^6.26.0", - "babel-preset-env": "^1.7.0", - "babel-preset-jest": "^22.4.3", - "babel-preset-react": "^6.24.1", - "chai": "^4.2.0", - "chai-enzyme": "^1.0.0-beta.1", - "css-loader": "^0.28.11", - "enzyme": "^3.7.0", - "enzyme-adapter-react-16.3": "^1.3.0", - "flow-bin": "0.79.1", - "flow-types": "^1.0.0", - "gh-pages": "^1.1.0", - "glow": "^1.1.1", - "html-webpack-plugin": "^3.2.0", - "jest": "^22.4.3", - "react": "^16.4", - "react-dom": "^16.4", - "react-test-renderer": "^16.4", - "regenerator-runtime": "^0.11.1", - "style-loader": "^0.21.0", - "webpack": "^4.8.1", - "webpack-cli": "^2.1.3", - "webpack-dev-server": "^3.1.4" - }, - "dependencies": { - "flow-copy-source": "^2.0.2", - "lodash": "^4.17.10" - }, - "jest": { - "verbose": true, - "bail": false, - "rootDir": "src" - } + "name": "react-forms-processor", + "version": "0.0.35", + "description": "A forms processor for statically declaring forms with dynamic behaviour", + "main": "dist/index.js", + "scripts": { + "test": "jest", + "glow": "glow", + "glow:watch": "glow --watch", + "start": "webpack-dev-server --mode development", + "transpile": "babel src -d dist --copy-files", + "prepublishOnly": "npm run transpile", + "build": "webpack --mode production", + "dev:watch": "webpack --mode development --watch", + "postbuild": "yarn build:flow", + "build:flow": "../../node_modules/flow-copy-source/bin/flow-copy-source.js --verbose --ignore '**/*{story,test}.js' src dist" + }, + "license": "MIT", + "peerDependencies": { + "react": "^16.4", + "react-dom": "^16.4" + }, + "devDependencies": { + "babel-cli": "^6.26.0", + "babel-core": "^6.26.3", + "babel-jest": "^22.4.3", + "babel-loader": "^7.1.4", + "babel-plugin-transform-class-properties": "^6.24.1", + "babel-plugin-transform-object-rest-spread": "^6.26.0", + "babel-preset-env": "^1.7.0", + "babel-preset-jest": "^22.4.3", + "babel-preset-react": "^6.24.1", + "chai": "^4.2.0", + "chai-enzyme": "^1.0.0-beta.1", + "css-loader": "^0.28.11", + "enzyme": "^3.7.0", + "enzyme-adapter-react-16.3": "^1.3.0", + "flow-bin": "0.79.1", + "flow-types": "^1.0.0", + "gh-pages": "^1.1.0", + "glow": "^1.1.1", + "html-webpack-plugin": "^3.2.0", + "jest": "^22.4.3", + "react": "^16.4", + "react-dom": "^16.4", + "react-test-renderer": "^16.4", + "regenerator-runtime": "^0.11.1", + "style-loader": "^0.21.0", + "webpack": "^4.8.1", + "webpack-cli": "^2.1.3", + "webpack-dev-server": "^3.1.4" + }, + "dependencies": { + "flow-copy-source": "^2.0.2", + "lodash": "^4.17.10" + }, + "jest": { + "verbose": false, + "bail": false, + "rootDir": "src" + } } diff --git a/packages/core/src/components/Form.js b/packages/core/src/components/Form.js index 29e5702..00750e5 100644 --- a/packages/core/src/components/Form.js +++ b/packages/core/src/components/Form.js @@ -33,11 +33,6 @@ const valueHasChanged = ( prevState: FormComponentState ) => nextProps.value && nextProps.value !== prevState.value; -const defaultValueHasChanged = ( - nextProps: FormComponentProps, - prevState: FormComponentState -) => nextProps.value && nextProps.value !== prevState.defaultValue; - const formDisabledStateHasChanged = ( nextProps: FormComponentProps, prevState: FormComponentState @@ -59,8 +54,8 @@ export default class Form extends Component< super(props); this.state = { fields: [], - defaultValue: props.value || {}, - value: props.value || {}, + defaultValue: props.defaultValue || {}, + value: props.value || props.defaultValue || {}, isValid: false, defaultFields: [], disabled: props.disabled || false, @@ -90,17 +85,22 @@ export default class Form extends Component< prevState: FormComponentState ) { const defaultFieldsChange = defaultFieldsHaveChanged(nextProps, prevState); - const defaultValueChange = defaultValueHasChanged(nextProps, prevState); + const valueChange = valueHasChanged(nextProps, prevState); if ( defaultFieldsChange || - valueHasChanged(nextProps, prevState) || + valueChange || formDisabledStateHasChanged(nextProps, prevState) || formTouchedBehaviourHasChanged(nextProps, prevState) ) { - const { fields: fieldsFromState, value: valueFromState } = prevState; + const { + fields: fieldsFromState, + defaultValue: defaultValueFromState, + value: valueFromState + } = prevState; let { defaultFields: defaultFieldsFromProps, + defaultValue: defaultValueFromProps, value: valueFromProps, disabled = false } = nextProps; @@ -111,10 +111,23 @@ export default class Form extends Component< showValidationBeforeTouched = false } = nextProps; - // If a new value has been passed to the Form as a prop then it should take precedence over the last calculated state - const value = valueFromProps || valueFromState || {}; + const value = { + ...defaultValueFromProps, + ...valueFromState, + ...valueFromProps + }; const defaultFields = defaultFieldsFromProps || fieldsFromState; + + // TODO: Don't use the field value if the value prop has changed... + if (!valueChange) { + defaultFields.forEach(field => { + if (field.value) { + value[field.name] = field.value; + } + }); + } + let fields; if (defaultFieldsFromProps && defaultFieldsChange) { fields = registerFields(defaultFieldsFromProps, value); @@ -125,7 +138,7 @@ export default class Form extends Component< // We should reset the touched state of all the fields if the value passed as a prop to the form // changes... - const resetTouchedState = defaultValueChange; + const resetTouchedState = valueChange; const nextState = getNextStateFromFields({ fields, @@ -139,12 +152,13 @@ export default class Form extends Component< return { ...nextState, defaultFields: defaultFieldsFromProps, + defaultValue: defaultValueFromProps || defaultValueFromState, disabled, - showValidationBeforeTouched + showValidationBeforeTouched, + value }; - } else { - return null; } + return null; } onFieldChange(id: string, value: Value) { @@ -158,6 +172,7 @@ export default class Form extends Component< let { fields } = this.state; fields = updateFieldTouchedState(id, true, fields); fields = updateFieldValue(id, value, fields); + const nextState = getNextStateFromFields({ fields, lastFieldUpdated: id, @@ -254,7 +269,7 @@ export default class Form extends Component< } createFormContext() { - const { fields, value, isValid } = this.state; + const { fields, defaultValue, value, isValid } = this.state; const { renderer = defaultRenderer, optionsHandler, @@ -271,6 +286,7 @@ export default class Form extends Component< const context: FormContextData = { fields, isValid, + defaultValue, value, registerField: this.registerField.bind(this), renderer, diff --git a/packages/core/src/components/Form.test.js b/packages/core/src/components/Form.test.js index d7eeb0f..c376961 100644 --- a/packages/core/src/components/Form.test.js +++ b/packages/core/src/components/Form.test.js @@ -31,7 +31,7 @@ describe("Context", () => {
{context => { @@ -58,7 +58,7 @@ describe("Context", () => { const propValue = { prop1: "value1" }; let value; const form = mount( - + ); @@ -91,7 +91,7 @@ describe("validation warnings", () => { describe("shown immediately", () => { const form = mount( -
+ @@ -116,7 +116,7 @@ describe("validation warnings", () => { describe("shown when touched", () => { const form = mount( -
+ @@ -199,7 +199,7 @@ describe("changing form value prop", () => { }; const form = mount( -
+ ); @@ -264,7 +264,7 @@ describe("Form and FormFragment behave the same with defaultFields and value", ( test("Form field has correct value from value", () => { const form = mount( -
+ ); const fieldValue = form.find("select").prop("value"); expect(fieldValue).toEqual(["melon"]); @@ -272,7 +272,7 @@ describe("Form and FormFragment behave the same with defaultFields and value", ( test("FormFragment field has correct value from value", () => { const form = mount( - + ); @@ -280,3 +280,27 @@ describe("Form and FormFragment behave the same with defaultFields and value", ( expect(fieldValue).toEqual(["melon"]); }); }); + +describe("Form with initial value updates value on field change", () => { + const fields: FieldDef[] = [ + { + id: "FIELD1", + type: "text", + name: "field1" + } + ]; + const formValue1 = { + field1: "value1" + }; + + const form = mount( +
+ + + ); + + test("moomin", () => { + form.find("input").prop("onChange")({ target: { value: "updated" } }); + expect(form.state().value.field1).toBe("updated"); + }); +}); diff --git a/packages/core/src/components/FormFragment.js b/packages/core/src/components/FormFragment.js index b45abeb..460bc46 100644 --- a/packages/core/src/components/FormFragment.js +++ b/packages/core/src/components/FormFragment.js @@ -75,14 +75,18 @@ class FormFragment extends Component< > { constructor(props: FormFragmentComponentWithContextProps) { super(props); - const { defaultFields = [], registerField, fields = [] } = props; - defaultFields.forEach(field => - registerFieldIfNew(field, fields, registerField) - ); + const { defaultFields = [], registerField, fields = [], value } = props; + defaultFields.forEach(field => { + field.defaultValue = + value[field.name] !== undefined + ? value[field.name] + : field.defaultValue; + registerFieldIfNew(field, fields, registerField); + }); } render() { - const { defaultFields = [] } = this.props; + const { defaultFields = [], value } = this.props; const renderedFields = defaultFields.map(field => renderFieldIfVisible(field, this.props) ); diff --git a/packages/core/src/types.js b/packages/core/src/types.js index c06366d..16b4a4c 100644 --- a/packages/core/src/types.js +++ b/packages/core/src/types.js @@ -342,6 +342,7 @@ export type OmitFieldValue = FieldDef => boolean; export type FormContextData = { fields: FieldDef[], + defaultValue: FormValue, value: FormValue, isValid: boolean, options: { diff --git a/packages/core/src/types/components.js b/packages/core/src/types/components.js index dc37189..82aa1e5 100644 --- a/packages/core/src/types/components.js +++ b/packages/core/src/types/components.js @@ -14,6 +14,7 @@ import type { export type FormComponentProps = { defaultFields?: FieldDef[], + defaultValue?: FormValue, value?: FormValue, onChange?: OnFormChange, onFieldFocus?: OnFieldFocus,