Skip to content

Commit 84d7ec2

Browse files
authored
Merge pull request #5850 from marmelab/fix-array-input-validation
Fix Regression on ArrayInput children validation
2 parents 51f7262 + 758b11f commit 84d7ec2

File tree

6 files changed

+38
-15
lines changed

6 files changed

+38
-15
lines changed

cypress/integration/edit.js

+11
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,17 @@ describe('Edit Page', () => {
6969
);
7070
});
7171

72+
it('should validate inputs inside ArrayInput', () => {
73+
EditPostPage.gotoTab(3);
74+
75+
cy.get(EditPostPage.elements.addBacklinkButton).click();
76+
77+
EditPostPage.clickInput('backlinks[0].url');
78+
cy.get(EditPostPage.elements.input('backlinks[0].url')).blur();
79+
80+
cy.contains('Required');
81+
});
82+
7283
it('should change reference list correctly when changing filter', () => {
7384
const EditPostTagsPage = editPageFactory('/#/posts/13');
7485
EditPostTagsPage.navigate();

cypress/support/EditPage.js

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ export default url => ({
22
elements: {
33
body: 'body',
44
deleteButton: '.ra-delete-button',
5+
addBacklinkButton: '.button-add-backlinks',
56
input: (name, type = 'input') => {
67
if (type === 'rich-text-input') {
78
return `.ra-input-${name} .ql-editor`;

examples/simple/src/posts/PostEdit.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ const PostEdit = ({ permissions, ...props }) => (
144144
<ArrayInput source="backlinks">
145145
<SimpleFormIterator>
146146
<DateInput source="date" />
147-
<TextInput source="url" />
147+
<TextInput source="url" validate={required()} />
148148
</SimpleFormIterator>
149149
</ArrayInput>
150150
<DateInput source="published_at" options={{ locale: 'pt' }} />

packages/ra-core/src/form/useFormGroup.ts

+21-11
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@ import { useFormContext } from './useFormContext';
55
import { FieldState } from 'final-form';
66

77
type FormGroupState = {
8+
dirty: boolean;
89
errors: object;
9-
valid: boolean;
1010
invalid: boolean;
1111
pristine: boolean;
12-
dirty: boolean;
12+
touched: boolean;
13+
valid: boolean;
1314
};
1415

1516
/**
@@ -58,18 +59,23 @@ export const useFormGroup = (name: string): FormGroupState => {
5859
const form = useForm();
5960
const formContext = useFormContext();
6061
const [state, setState] = useState<FormGroupState>({
62+
dirty: false,
6163
errors: undefined,
62-
valid: true,
6364
invalid: false,
6465
pristine: true,
65-
dirty: false,
66+
touched: false,
67+
valid: true,
6668
});
6769

6870
useEffect(() => {
6971
const unsubscribe = form.subscribe(
7072
() => {
7173
const fields = formContext.getGroupFields(name);
72-
const fieldStates = fields.map(form.getFieldState);
74+
const fieldStates = fields
75+
.map(field => {
76+
return form.getFieldState(field);
77+
})
78+
.filter(fieldState => fieldState != undefined); // eslint-disable-line
7379
const newState = getFormGroupState(fieldStates);
7480

7581
setState(oldState => {
@@ -86,6 +92,7 @@ export const useFormGroup = (name: string): FormGroupState => {
8692
dirty: true,
8793
pristine: true,
8894
valid: true,
95+
touched: true,
8996
}
9097
);
9198
return unsubscribe;
@@ -102,8 +109,8 @@ export const useFormGroup = (name: string): FormGroupState => {
102109
*/
103110
export const getFormGroupState = (
104111
fieldStates: FieldState<any>[]
105-
): FormGroupState =>
106-
fieldStates.reduce(
112+
): FormGroupState => {
113+
return fieldStates.reduce(
107114
(acc, fieldState) => {
108115
let errors = acc.errors || {};
109116

@@ -112,20 +119,23 @@ export const getFormGroupState = (
112119
}
113120

114121
const newState = {
122+
dirty: acc.dirty || fieldState.dirty,
115123
errors,
116-
valid: acc.valid && fieldState.valid,
117124
invalid: acc.invalid || fieldState.invalid,
118125
pristine: acc.pristine && fieldState.pristine,
119-
dirty: acc.dirty || fieldState.dirty,
126+
touched: acc.touched || fieldState.touched,
127+
valid: acc.valid && fieldState.valid,
120128
};
121129

122130
return newState;
123131
},
124132
{
133+
dirty: false,
125134
errors: undefined,
126-
valid: true,
127135
invalid: false,
128136
pristine: true,
129-
dirty: false,
137+
valid: true,
138+
touched: false,
130139
}
131140
);
141+
};

packages/ra-ui-materialui/src/form/FormTab.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ export const FormTabHeader = ({
9393
className={classnames('form-tab', className, {
9494
[classes.errorTabButton]:
9595
formGroup.invalid &&
96-
formGroup.dirty &&
96+
formGroup.touched &&
9797
location.pathname !== value,
9898
})}
9999
component={Link}

packages/ra-ui-materialui/src/input/TranslatableInputsTab.tsx

+3-2
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ export const TranslatableInputsTab = (
1313
props: TranslatableInputsTabProps & TabProps
1414
) => {
1515
const { groupKey = '', locale, classes: classesOverride, ...rest } = props;
16-
const { invalid } = useFormGroup(`${groupKey}${locale}`);
16+
const { invalid, touched } = useFormGroup(`${groupKey}${locale}`);
17+
1718
const classes = useStyles(props);
1819
const translate = useTranslate();
1920

@@ -23,7 +24,7 @@ export const TranslatableInputsTab = (
2324
label={translate(`ra.locales.${locale}`, {
2425
_: capitalize(locale),
2526
})}
26-
className={invalid ? classes.error : undefined}
27+
className={invalid && touched ? classes.error : undefined}
2728
{...rest}
2829
/>
2930
);

0 commit comments

Comments
 (0)