diff --git a/packages/common/config/webpack.config.js b/packages/common/config/webpack.config.js
index 7b4816d33..0633ecdd3 100644
--- a/packages/common/config/webpack.config.js
+++ b/packages/common/config/webpack.config.js
@@ -15,10 +15,15 @@ const devConfig = {
filename: '[name].[hash].js'
},
devtool: 'eval-source-map',
+ resolve: {
+ fallback: {
+ process: 'process/browser.js',
+ },
+ },
plugins: [
htmlPlugin,
new webpack.ProvidePlugin({
- process: 'process/browser'
+ process: 'process/browser.js'
})
],
module: {
diff --git a/packages/react-form-renderer/demo/index.js b/packages/react-form-renderer/demo/index.js
index 0158fcd8f..28f428583 100644
--- a/packages/react-form-renderer/demo/index.js
+++ b/packages/react-form-renderer/demo/index.js
@@ -7,41 +7,27 @@ import FormTemplate from './form-template';
import mapper from './form-fields-mapper';
const schema = {
- title: 'Sequence condition',
+ title: 'Set action',
fields: [
{
component: 'text-field',
- name: 'field-1',
- label: 'first name',
+ name: 'useDefaultNickName',
+ label: 'Do you want to use default nickname?',
+ description: 'set: {} is used to reset the setter',
},
{
component: 'text-field',
- name: 'field-2',
- label: 'last name',
- },
- {
- component: 'text-field',
- name: 'field-3',
- label: 'occupation',
+ name: 'nickname',
+ label: 'Nickname',
condition: {
- sequence: [
- {
- and: [
- { when: 'field-1', is: 'james' },
- { when: 'field-2', is: 'bond' },
- ],
- then: { set: { 'field-3': 'SPY' } },
- else: { visible: true },
- },
- {
- and: [
- { when: 'field-1', is: 'steve' },
- { when: 'field-2', is: 'jobs' },
- ],
- then: { set: { 'field-3': 'CEO' } },
- else: { visible: true },
+ when: 'useDefaultNickName',
+ is: 'a',
+ then: {
+ set: (formState, getFieldState) => {
+ return { nickname: formState.values.useDefaultNickName };
},
- ],
+ },
+ else: { visible: true, set: {} },
},
},
],
@@ -49,17 +35,7 @@ const schema = {
const App = () => {
return (
-
+
);
};
diff --git a/packages/react-form-renderer/package.json b/packages/react-form-renderer/package.json
index 2ce4306e2..da6bfc150 100644
--- a/packages/react-form-renderer/package.json
+++ b/packages/react-form-renderer/package.json
@@ -27,7 +27,9 @@
"forms",
"javascript"
],
- "devDependencies": {},
+ "devDependencies": {
+ "process": "^0.11.10"
+ },
"dependencies": {
"final-form": "^4.20.4",
"final-form-arrays": "^3.0.2",
diff --git a/packages/react-form-renderer/src/condition/condition.d.ts b/packages/react-form-renderer/src/condition/condition.d.ts
index bacfefb4c..4213959cb 100644
--- a/packages/react-form-renderer/src/condition/condition.d.ts
+++ b/packages/react-form-renderer/src/condition/condition.d.ts
@@ -1,8 +1,9 @@
import Field from "../common-types/field";
-
+import type { FormState, FormApi } from "final-form";
+
export interface ActionResolution {
visible?: boolean;
- set?: object; // TO DO specify this
+ set?: object | ((formState:FormState>, getFieldState:FormApi["getFieldState"]) => object) ; // TO DO specify this
}
export type InnerWhenFunction = (currentField: string) => string;
diff --git a/packages/react-form-renderer/src/condition/condition.js b/packages/react-form-renderer/src/condition/condition.js
index ae1e5da60..5e87c457b 100644
--- a/packages/react-form-renderer/src/condition/condition.js
+++ b/packages/react-form-renderer/src/condition/condition.js
@@ -1,10 +1,20 @@
-import React, { useEffect, useReducer } from 'react';
+import React, { useCallback, useEffect, useReducer } from 'react';
import PropTypes from 'prop-types';
import isEqual from 'lodash/isEqual';
import useFormApi from '../use-form-api';
import parseCondition from '../parse-condition';
+const setterValueCheck = (setterValue) => {
+ if (setterValue === null || Array.isArray(setterValue)) {
+ console.error('Received invalid setterValue. Expected object, received: ', setterValue);
+
+ return false;
+ }
+
+ return typeof setterValue === 'object';
+};
+
export const reducer = (state, { type, sets }) => {
switch (type) {
case 'formResetted':
@@ -42,6 +52,12 @@ const Condition = React.memo(
}
}, [dirty]);
+ const setValue = useCallback((setter) => {
+ Object.entries(setter).forEach(([name, value]) => {
+ formOptions.change(name, value);
+ });
+ }, []);
+
useEffect(() => {
if (setters && setters.length > 0 && (state.initial || !isEqual(setters, state.sets))) {
setters.forEach((setter, index) => {
@@ -60,9 +76,17 @@ const Condition = React.memo(
*/
if (!meta || isFormModified || typeof meta.initial === 'undefined') {
formOptions.batch(() => {
- Object.entries(setter).forEach(([name, value]) => {
- formOptions.change(name, value);
- });
+ if (typeof setter !== 'function') {
+ setValue(setter);
+ } else {
+ const setterValue = setter(formOptions.getState(), formOptions.getFieldState);
+
+ if (setterValueCheck(setterValue)) {
+ setValue(setterValue);
+ } else {
+ console.error('Received invalid setterValue. Expected object, received: ', setterValue);
+ }
+ }
});
}
});
@@ -101,11 +125,11 @@ const conditionProps = {
notMatch: PropTypes.any,
then: PropTypes.shape({
visible: PropTypes.bool,
- set: PropTypes.object,
+ set: PropTypes.oneOfType([PropTypes.object, PropTypes.func]),
}),
else: PropTypes.shape({
visible: PropTypes.bool,
- set: PropTypes.object,
+ set: PropTypes.oneOfType([PropTypes.object, PropTypes.func]),
}),
};
diff --git a/packages/react-form-renderer/src/default-schema-validator/default-schema-validator.js b/packages/react-form-renderer/src/default-schema-validator/default-schema-validator.js
index 55492339f..4bb3de37e 100644
--- a/packages/react-form-renderer/src/default-schema-validator/default-schema-validator.js
+++ b/packages/react-form-renderer/src/default-schema-validator/default-schema-validator.js
@@ -24,7 +24,7 @@ const checkConditionalAction = (type, action, fieldName) => {
`);
}
- if (action.hasOwnProperty('set') && (typeof action.set !== 'object' || Array.isArray(action.set))) {
+ if (action.hasOwnProperty('set') && ((typeof action.set !== 'object' && typeof action.set !== 'function') || Array.isArray(action.set))) {
throw new DefaultSchemaError(`
Error occured in field definition with "name" property: "${fieldName}".
'set' property in action "${type}" has to be a object! Received: ${typeof action.visible}, isArray: ${Array.isArray(action.set)}.
diff --git a/packages/react-form-renderer/src/tests/form-renderer/condition.test.js b/packages/react-form-renderer/src/tests/form-renderer/condition.test.js
index 7aa5fc4ab..ff8ca2713 100644
--- a/packages/react-form-renderer/src/tests/form-renderer/condition.test.js
+++ b/packages/react-form-renderer/src/tests/form-renderer/condition.test.js
@@ -531,6 +531,150 @@ describe('condition test', () => {
await waitFor(() => expect(screen.getByLabelText('field2')).toHaveValue('set with then'));
});
+ it('should change fields value by set funciton', async () => {
+ const schema = {
+ fields: [
+ {
+ component: 'text-field',
+ name: 'field1',
+ label: 'field1',
+ },
+ {
+ component: 'text-field',
+ name: 'field2',
+ label: 'field2',
+ condition: {
+ when: 'field1',
+ is: 'foo',
+ then: {
+ set: (formState) => {
+ return { field2: formState.values.field1 };
+ },
+ },
+ else: { visible: true, set: {} },
+ },
+ },
+ ],
+ };
+
+ render();
+ expect(screen.getByLabelText('field2')).toHaveValue('');
+
+ await userEvent.type(screen.getByLabelText('field1'), 'foo');
+ await waitFor(() => expect(screen.getByLabelText('field2')).toHaveValue('foo'));
+ });
+
+ it('check the set function received valid arguments', async () => {
+ const setSpy = jest.fn();
+
+ setSpy.mockImplementation(() => ({}));
+
+ const schema = {
+ fields: [
+ {
+ component: 'text-field',
+ name: 'field1',
+ label: 'field1',
+ },
+ {
+ component: 'text-field',
+ name: 'field2',
+ label: 'field2',
+ condition: {
+ when: 'field1',
+ is: 'foo',
+ then: {
+ set: setSpy,
+ },
+ else: { visible: true, set: {} },
+ },
+ },
+ ],
+ };
+
+ render();
+
+ await userEvent.type(screen.getByLabelText('field1'), 'foo');
+
+ await waitFor(() => expect(setSpy).toHaveBeenCalledTimes(1));
+
+ const expected = { active: 'field1', dirty: true };
+
+ await waitFor(() => expect(setSpy).toHaveBeenCalledWith(expect.objectContaining(expected), expect.any(Function)));
+ });
+
+ it('check the object', async () => {
+ const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
+
+ const schema = {
+ fields: [
+ {
+ component: 'text-field',
+ name: 'field1',
+ label: 'field1',
+ },
+ {
+ component: 'text-field',
+ name: 'field2',
+ label: 'field2',
+ condition: {
+ when: 'field1',
+ is: 'foo',
+ then: {
+ set: (formState) => {
+ return null;
+ },
+ },
+ else: { visible: true, set: {} },
+ },
+ },
+ ],
+ };
+
+ render();
+
+ await userEvent.type(screen.getByLabelText('field1'), 'foo');
+
+ await waitFor(() => {
+ expect(errorSpy).toHaveBeenCalled();
+ expect(console.error.mock.calls[0][0]).toContain('Received invalid setterValue. Expected object, received: ');
+ });
+ });
+
+ it('check the getFieldState object', async () => {
+ const schema = {
+ fields: [
+ {
+ component: 'text-field',
+ name: 'field1',
+ label: 'field1',
+ },
+ {
+ component: 'text-field',
+ name: 'field2',
+ label: 'field2',
+ condition: {
+ when: 'field1',
+ is: 'foo',
+ then: {
+ set: (_formState, getFieldState) => {
+ return { field2: getFieldState('field1').value };
+ },
+ },
+ else: { visible: true, set: {} },
+ },
+ },
+ ],
+ };
+ render();
+
+ await userEvent.type(screen.getByLabelText('field1'), 'foo');
+
+ await waitFor(() => {
+ expect(screen.getByLabelText('field2')).toHaveValue('foo');
+ });
+ });
+
describe('reducer', () => {
it('returns default', () => {
const initialState = {
diff --git a/packages/react-renderer-demo/src/examples/components/conditions/set-function.js b/packages/react-renderer-demo/src/examples/components/conditions/set-function.js
new file mode 100644
index 000000000..1889f42e6
--- /dev/null
+++ b/packages/react-renderer-demo/src/examples/components/conditions/set-function.js
@@ -0,0 +1,42 @@
+import React from 'react';
+import FormRenderer from '@data-driven-forms/react-form-renderer/form-renderer';
+import componentTypes from '@data-driven-forms/react-form-renderer/component-types';
+
+import Checkbox from '@data-driven-forms/mui-component-mapper/checkbox';
+import TextField from '@data-driven-forms/mui-component-mapper/text-field';
+import FormTemplate from '@data-driven-forms/mui-component-mapper/form-template';
+
+const schema = {
+ fields: [
+ {
+ component: componentTypes.TEXT_FIELD,
+ name: 'firstname',
+ label: 'Firstname',
+ description: 'Type Bob to set nickname to Bob',
+ },
+ {
+ component: componentTypes.TEXT_FIELD,
+ name: 'nickname',
+ label: 'Nickname',
+ condition: {
+ when: 'firstname',
+ is: 'Bob',
+ then: {
+ set: (formState) => {
+ return { nickname: formState.values.firstname };
+ },
+ },
+ else: { visible: true, set: {} },
+ },
+ },
+ ],
+};
+
+const componentMapper = {
+ [componentTypes.CHECKBOX]: Checkbox,
+ [componentTypes.TEXT_FIELD]: TextField,
+};
+
+const SetFunction = () => ;
+
+export default SetFunction;
diff --git a/packages/react-renderer-demo/src/next.config.js b/packages/react-renderer-demo/src/next.config.js
index ff6e7f99e..24dc5737c 100644
--- a/packages/react-renderer-demo/src/next.config.js
+++ b/packages/react-renderer-demo/src/next.config.js
@@ -57,16 +57,16 @@ module.exports = withBundleAnalyzer(
...config.resolve.alias,
...(process.env.DEPLOY === 'true'
? {
- "@emotion/react": path.resolve(__dirname, '../node_modules/@emotion/react'),
- "@emotion/server": path.resolve(__dirname, '../node_modules/@emotion/server'),
- "@emotion/styled": path.resolve(__dirname, '../node_modules/@emotion/styled'),
+ '@emotion/react': path.resolve(__dirname, '../node_modules/@emotion/react'),
+ '@emotion/server': path.resolve(__dirname, '../node_modules/@emotion/server'),
+ '@emotion/styled': path.resolve(__dirname, '../node_modules/@emotion/styled'),
}
: {
react: path.resolve(__dirname, '../../../node_modules/react'),
'react-dom': path.resolve(__dirname, '../../../node_modules/react-dom'),
- "@emotion/react": path.resolve(__dirname, '../../../node_modules/@emotion/react'),
- "@emotion/server": path.resolve(__dirname, '../../../node_modules/@emotion/server'),
- "@emotion/styled": path.resolve(__dirname, '../../../node_modules/@emotion/styled'),
+ '@emotion/react': path.resolve(__dirname, '../../../node_modules/@emotion/react'),
+ '@emotion/server': path.resolve(__dirname, '../../../node_modules/@emotion/server'),
+ '@emotion/styled': path.resolve(__dirname, '../../../node_modules/@emotion/styled'),
}),
'@docs/doc-components': path.resolve(__dirname, './doc-components'),
diff --git a/packages/react-renderer-demo/src/pages/schema/condition-set.md b/packages/react-renderer-demo/src/pages/schema/condition-set.md
index 962850a2c..13a5c9fc9 100644
--- a/packages/react-renderer-demo/src/pages/schema/condition-set.md
+++ b/packages/react-renderer-demo/src/pages/schema/condition-set.md
@@ -1,4 +1,5 @@
import DocPage from '@docs/doc-page';
+import CodeExample from '@docs/code-example'
@@ -6,6 +7,8 @@ import DocPage from '@docs/doc-page';
Setter allows to change form values according to selected values in different fields.
+## Set object
+
```jsx
// Single value
condition: { when: 'x', is: 'y', then: { set: { [field]: value } } }
@@ -22,4 +25,17 @@ Set is a object consists of field names as keys and values as values. You can ch
When the field containing a condition has some defined initial value, the setter is not triggered until the setter is retriggered with a different value.
+
+
+## Set function
+
+To enable dynamic set action you can define set as a function.
+
+```TS
+type set = (formState: FormState, getFieldState: ((fieldName: string) => FieldState)): void
+```
+
+
+
+
diff --git a/yarn.lock b/yarn.lock
index f54067c9e..e313a00d8 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -16565,6 +16565,11 @@ process-nextick-args@~2.0.0:
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==
+process@^0.11.10:
+ version "0.11.10"
+ resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182"
+ integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==
+
progress@^2.0.0, progress@^2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"