Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion packages/common/config/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
52 changes: 14 additions & 38 deletions packages/react-form-renderer/demo/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,59 +7,35 @@ 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: {} },
},
},
],
};
const App = () => {
return (
<div style={{ padding: 20 }}>
<FormRenderer
initialValues={{
'field-1': 'steve',
'field-2': 'jobs',
'field-3': 'RETIRED',
}}
componentMapper={mapper}
onSubmit={console.log}
FormTemplate={FormTemplate}
schema={schema}
/>
<FormRenderer componentMapper={mapper} onSubmit={console.log} FormTemplate={FormTemplate} schema={schema} />
</div>
);
};
Expand Down
4 changes: 3 additions & 1 deletion packages/react-form-renderer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@
"forms",
"javascript"
],
"devDependencies": {},
"devDependencies": {
"process": "^0.11.10"
},
"dependencies": {
"final-form": "^4.20.4",
"final-form-arrays": "^3.0.2",
Expand Down
5 changes: 3 additions & 2 deletions packages/react-form-renderer/src/condition/condition.d.ts
Original file line number Diff line number Diff line change
@@ -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<Record<string, any>>, getFieldState:FormApi["getFieldState"]) => object) ; // TO DO specify this
}

export type InnerWhenFunction = (currentField: string) => string;
Expand Down
36 changes: 30 additions & 6 deletions packages/react-form-renderer/src/condition/condition.js
Original file line number Diff line number Diff line change
@@ -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':
Expand Down Expand Up @@ -42,6 +52,12 @@ const Condition = React.memo(
}
}, [dirty]);

const setValue = useCallback((setter) => {
Object.entries(setter).forEach(([name, value]) => {
formOptions.change(name, value);
});
}, []);

useEffect(() => {
Comment thread
rvsia marked this conversation as resolved.
if (setters && setters.length > 0 && (state.initial || !isEqual(setters, state.sets))) {
setters.forEach((setter, index) => {
Expand All @@ -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);
}
}
});
}
});
Expand Down Expand Up @@ -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]),
}),
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)}.
Expand Down
144 changes: 144 additions & 0 deletions packages/react-form-renderer/src/tests/form-renderer/condition.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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(<FormRenderer {...initialProps} schema={schema} />);
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(<FormRenderer {...initialProps} schema={schema} />);

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(<FormRenderer {...initialProps} schema={schema} />);

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(<FormRenderer {...initialProps} schema={schema} />);

await userEvent.type(screen.getByLabelText('field1'), 'foo');

await waitFor(() => {
expect(screen.getByLabelText('field2')).toHaveValue('foo');
});
});

describe('reducer', () => {
it('returns default', () => {
const initialState = {
Expand Down
Original file line number Diff line number Diff line change
@@ -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 = () => <FormRenderer FormTemplate={FormTemplate} componentMapper={componentMapper} schema={schema} onSubmit={console.log} />;

export default SetFunction;
Loading