Skip to content

Commit 023f1cc

Browse files
authored
Merge pull request #5984 from marmelab/fix-form-tab-header-error-state-display
Fix FormTabHeader Error State Display
2 parents 34e214b + f422aa1 commit 023f1cc

File tree

2 files changed

+43
-6
lines changed

2 files changed

+43
-6
lines changed

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

+9-3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import classnames from 'classnames';
77
import { useTranslate, useFormGroup } from 'ra-core';
88
import { useTabbedFormViewStyles } from './TabbedFormView';
99
import { ClassesOverride } from '../types';
10+
import { useFormState } from 'react-final-form';
1011

1112
export const FormTabHeader = ({
1213
classes,
@@ -19,6 +20,7 @@ export const FormTabHeader = ({
1920
}: FormTabHeaderProps): ReactElement => {
2021
const translate = useTranslate();
2122
const location = useLocation();
23+
const { submitFailed } = useFormState(UseFormStateOptions);
2224
const formGroup = useFormGroup(value.toString());
2325
const propsForLink = {
2426
component: Link,
@@ -32,9 +34,7 @@ export const FormTabHeader = ({
3234
icon={icon}
3335
className={classnames('form-tab', className, {
3436
[classes.errorTabButton]:
35-
formGroup.invalid &&
36-
formGroup.touched &&
37-
location.pathname !== value,
37+
formGroup.invalid && (formGroup.touched || submitFailed),
3838
})}
3939
{...(syncWithLocation ? propsForLink : {})} // to avoid TypeScript screams, see https://github.com/mui-org/material-ui/issues/9106#issuecomment-451270521
4040
id={`tabheader-${value}`}
@@ -44,6 +44,12 @@ export const FormTabHeader = ({
4444
);
4545
};
4646

47+
const UseFormStateOptions = {
48+
subscription: {
49+
submitFailed: true,
50+
},
51+
};
52+
4753
interface FormTabHeaderProps {
4854
basePath?: string;
4955
className?: string;

packages/ra-ui-materialui/src/form/TabbedForm.spec.tsx

+34-3
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,37 @@ describe('<TabbedForm />', () => {
108108
expect(tabs[1].classList.contains('error')).toEqual(true);
109109
});
110110

111-
it('should not set the style of an active Tab button with errors', () => {
111+
it('should set the style of an active Tab button with errors', () => {
112+
const { getAllByRole, getByLabelText } = renderWithRedux(
113+
<MemoryRouter initialEntries={['/posts/1']} initialIndex={0}>
114+
<SaveContextProvider value={saveContextValue}>
115+
<TabbedForm
116+
classes={{ errorTabButton: 'error' }}
117+
resource="posts"
118+
>
119+
<FormTab label="tab1">
120+
<TextInput source="title" validate={required()} />
121+
</FormTab>
122+
<FormTab label="tab2">
123+
<TextInput
124+
source="description"
125+
validate={required()}
126+
/>
127+
</FormTab>
128+
</TabbedForm>
129+
</SaveContextProvider>
130+
</MemoryRouter>
131+
);
132+
133+
const tabs = getAllByRole('tab');
134+
fireEvent.click(tabs[1]);
135+
const input = getByLabelText('resources.posts.fields.description *');
136+
fireEvent.blur(input);
137+
expect(tabs[0].classList.contains('error')).toEqual(false);
138+
expect(tabs[1].classList.contains('error')).toEqual(true);
139+
});
140+
141+
it('should set the style of any Tab button with errors on submit', () => {
112142
const { getAllByRole, getByLabelText } = renderWithRedux(
113143
<MemoryRouter initialEntries={['/posts/1']} initialIndex={0}>
114144
<SaveContextProvider value={saveContextValue}>
@@ -133,9 +163,10 @@ describe('<TabbedForm />', () => {
133163
const tabs = getAllByRole('tab');
134164
fireEvent.click(tabs[1]);
135165
const input = getByLabelText('resources.posts.fields.description');
136-
fireEvent.change(input, { target: { value: 'foo' } });
137166
fireEvent.blur(input);
138-
expect(tabs[0].classList.contains('error')).toEqual(false);
167+
fireEvent.change(input, { target: { value: 'fooooooooo' } });
168+
fireEvent.click(getByLabelText('ra.action.save'));
169+
expect(tabs[0].classList.contains('error')).toEqual(true);
139170
expect(tabs[1].classList.contains('error')).toEqual(false);
140171
});
141172

0 commit comments

Comments
 (0)