Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Regression on ArrayInput children validation #5850

Merged
merged 2 commits into from
Feb 2, 2021
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
11 changes: 11 additions & 0 deletions cypress/integration/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,17 @@ describe('Edit Page', () => {
);
});

it('should validate inputs inside ArrayInput', () => {
EditPostPage.gotoTab(3);

cy.get(EditPostPage.elements.addBacklinkButton).click();

EditPostPage.clickInput('backlinks[0].url');
cy.get(EditPostPage.elements.input('backlinks[0].url')).blur();

cy.contains('Required');
});

it('should change reference list correctly when changing filter', () => {
const EditPostTagsPage = editPageFactory('/#/posts/13');
EditPostTagsPage.navigate();
Expand Down
1 change: 1 addition & 0 deletions cypress/support/EditPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export default url => ({
elements: {
body: 'body',
deleteButton: '.ra-delete-button',
addBacklinkButton: '.button-add-backlinks',
input: (name, type = 'input') => {
if (type === 'rich-text-input') {
return `.ra-input-${name} .ql-editor`;
Expand Down
2 changes: 1 addition & 1 deletion examples/simple/src/posts/PostEdit.js
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ const PostEdit = ({ permissions, ...props }) => (
<ArrayInput source="backlinks">
<SimpleFormIterator>
<DateInput source="date" />
<TextInput source="url" />
<TextInput source="url" validate={required()} />
</SimpleFormIterator>
</ArrayInput>
<DateInput source="published_at" options={{ locale: 'pt' }} />
Expand Down
32 changes: 21 additions & 11 deletions packages/ra-core/src/form/useFormGroup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ import { useFormContext } from './useFormContext';
import { FieldState } from 'final-form';

type FormGroupState = {
dirty: boolean;
errors: object;
valid: boolean;
invalid: boolean;
pristine: boolean;
dirty: boolean;
touched: boolean;
valid: boolean;
};

/**
Expand Down Expand Up @@ -58,18 +59,23 @@ export const useFormGroup = (name: string): FormGroupState => {
const form = useForm();
const formContext = useFormContext();
const [state, setState] = useState<FormGroupState>({
dirty: false,
errors: undefined,
valid: true,
invalid: false,
pristine: true,
dirty: false,
touched: false,
valid: true,
});

useEffect(() => {
const unsubscribe = form.subscribe(
() => {
const fields = formContext.getGroupFields(name);
const fieldStates = fields.map(form.getFieldState);
const fieldStates = fields
.map(field => {
return form.getFieldState(field);
})
.filter(fieldState => fieldState != undefined); // eslint-disable-line
const newState = getFormGroupState(fieldStates);

setState(oldState => {
Expand All @@ -86,6 +92,7 @@ export const useFormGroup = (name: string): FormGroupState => {
dirty: true,
pristine: true,
valid: true,
touched: true,
}
);
return unsubscribe;
Expand All @@ -102,8 +109,8 @@ export const useFormGroup = (name: string): FormGroupState => {
*/
export const getFormGroupState = (
fieldStates: FieldState<any>[]
): FormGroupState =>
fieldStates.reduce(
): FormGroupState => {
return fieldStates.reduce(
(acc, fieldState) => {
let errors = acc.errors || {};

Expand All @@ -112,20 +119,23 @@ export const getFormGroupState = (
}

const newState = {
dirty: acc.dirty || fieldState.dirty,
errors,
valid: acc.valid && fieldState.valid,
invalid: acc.invalid || fieldState.invalid,
pristine: acc.pristine && fieldState.pristine,
dirty: acc.dirty || fieldState.dirty,
touched: acc.touched || fieldState.touched,
valid: acc.valid && fieldState.valid,
};

return newState;
},
{
dirty: false,
errors: undefined,
valid: true,
invalid: false,
pristine: true,
dirty: false,
valid: true,
touched: false,
}
);
};
2 changes: 1 addition & 1 deletion packages/ra-ui-materialui/src/form/FormTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ export const FormTabHeader = ({
className={classnames('form-tab', className, {
[classes.errorTabButton]:
formGroup.invalid &&
formGroup.dirty &&
formGroup.touched &&
location.pathname !== value,
})}
component={Link}
Expand Down
5 changes: 3 additions & 2 deletions packages/ra-ui-materialui/src/input/TranslatableInputsTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ export const TranslatableInputsTab = (
props: TranslatableInputsTabProps & TabProps
) => {
const { groupKey = '', locale, classes: classesOverride, ...rest } = props;
const { invalid } = useFormGroup(`${groupKey}${locale}`);
const { invalid, touched } = useFormGroup(`${groupKey}${locale}`);

const classes = useStyles(props);
const translate = useTranslate();

Expand All @@ -23,7 +24,7 @@ export const TranslatableInputsTab = (
label={translate(`ra.locales.${locale}`, {
_: capitalize(locale),
})}
className={invalid ? classes.error : undefined}
className={invalid && touched ? classes.error : undefined}
{...rest}
/>
);
Expand Down