diff --git a/packages/ra-ui-materialui/src/input/FileInput.js b/packages/ra-ui-materialui/src/input/FileInput.js index d56282608e4..ab08a410568 100644 --- a/packages/ra-ui-materialui/src/input/FileInput.js +++ b/packages/ra-ui-materialui/src/input/FileInput.js @@ -4,6 +4,7 @@ import { shallowEqual } from 'recompose'; import Dropzone from 'react-dropzone'; import compose from 'recompose/compose'; import { withStyles } from '@material-ui/core/styles'; +import FormHelperText from '@material-ui/core/FormHelperText'; import classnames from 'classnames'; import { addField, translate } from 'ra-core'; @@ -83,9 +84,10 @@ export class FileInput extends Component { this.setState({ files: updatedFiles }); if (this.props.multiple) { - this.props.input.onChange(updatedFiles); + // Use onBlur to ensure redux-form set the input as touched + this.props.input.onBlur(updatedFiles); } else { - this.props.input.onChange(updatedFiles[0]); + this.props.input.onBlur(updatedFiles[0]); } }; @@ -96,10 +98,11 @@ export class FileInput extends Component { this.setState({ files: filteredFiles }); + // Use onBlur to ensure redux-form set the input as touched if (this.props.multiple) { - this.props.input.onChange(filteredFiles); + this.props.input.onBlur(filteredFiles); } else { - this.props.input.onChange(null); + this.props.input.onBlur(null); } }; @@ -155,10 +158,12 @@ export class FileInput extends Component { isRequired, label, maxSize, + meta, minSize, multiple, resource, source, + translate, options = {}, ...rest } = this.props; @@ -171,6 +176,7 @@ export class FileInput extends Component { source={source} resource={resource} isRequired={isRequired} + meta={meta} {...sanitizeRestProps(rest)} > @@ -204,6 +210,13 @@ export class FileInput extends Component { ))} )} + {meta && + meta.touched && + meta.error && ( + + {translate(meta.error)} + + )} ); diff --git a/packages/ra-ui-materialui/src/input/FileInput.spec.js b/packages/ra-ui-materialui/src/input/FileInput.spec.js index 9113132d336..3853baede6d 100644 --- a/packages/ra-ui-materialui/src/input/FileInput.spec.js +++ b/packages/ra-ui-materialui/src/input/FileInput.spec.js @@ -34,7 +34,7 @@ describe('', () => { }); it('should correctly update upon drop when allowing a single file', () => { - const onChange = jest.fn(); + const onBlur = jest.fn(); const wrapper = shallow( ', () => { value: { src: 'b64_picture', }, - onChange, + onBlur, }} translate={x => x} source="src" @@ -51,13 +51,13 @@ describe('', () => { wrapper.instance().onDrop([{ preview: 'new_b64_picture' }]); - assert.deepEqual(onChange.mock.calls[0][0], { + assert.deepEqual(onBlur.mock.calls[0][0], { preview: 'new_b64_picture', }); }); it('should correctly update upon removal when allowing a single file', () => { - const onChange = jest.fn(); + const onBlur = jest.fn(); const wrapper = shallow( ', () => { value: { src: 'b64_picture', }, - onChange, + onBlur, }} translate={x => x} source="src" @@ -73,11 +73,11 @@ describe('', () => { ); wrapper.instance().onRemove({ src: 'b64_picture' })(); - assert.deepEqual(onChange.mock.calls[0][0], null); + assert.deepEqual(onBlur.mock.calls[0][0], null); }); it('should correctly update upon drop when allowing multiple files', () => { - const onChange = jest.fn(); + const onBlur = jest.fn(); const wrapper = shallow( ', () => { { src: 'b64_picture' }, { src: 'another_b64_picture' }, ], - onChange, + onBlur, }} translate={x => x} source="pictures" @@ -96,7 +96,7 @@ describe('', () => { wrapper.instance().onDrop([{ preview: 'new_b64_picture' }]); - assert.deepEqual(onChange.mock.calls[0][0], [ + assert.deepEqual(onBlur.mock.calls[0][0], [ { src: 'b64_picture' }, { src: 'another_b64_picture' }, { preview: 'new_b64_picture' }, @@ -104,7 +104,7 @@ describe('', () => { }); it('should correctly update upon removal when allowing multiple files', () => { - const onChange = jest.fn(); + const onBlur = jest.fn(); const wrapper = shallow( ', () => { { src: 'b64_picture' }, { src: 'another_b64_picture' }, ], - onChange, + onBlur, }} translate={x => x} source="pictures" @@ -123,7 +123,7 @@ describe('', () => { wrapper.instance().onRemove({ src: 'another_b64_picture' })(); - assert.deepEqual(onChange.mock.calls[0][0], [{ src: 'b64_picture' }]); + assert.deepEqual(onBlur.mock.calls[0][0], [{ src: 'b64_picture' }]); }); it('should display correct label depending multiple property', () => { @@ -309,7 +309,7 @@ describe('', () => { source="picture" translate={x => x} input={{ - onChange: () => {}, + onBlur: () => {}, value: [ { url: 'http://static.acme.com/foo.jpg' }, { url: 'http://static.acme.com/bar.jpg' }, diff --git a/packages/ra-ui-materialui/src/input/ImageInput.spec.js b/packages/ra-ui-materialui/src/input/ImageInput.spec.js index 5c0342c1205..d687f384eb9 100644 --- a/packages/ra-ui-materialui/src/input/ImageInput.spec.js +++ b/packages/ra-ui-materialui/src/input/ImageInput.spec.js @@ -216,7 +216,7 @@ describe('', () => { source="picture" translate={x => x} input={{ - onChange: () => {}, + onBlur: () => {}, value: [ { url: 'http://static.acme.com/foo.jpg' }, { url: 'http://static.acme.com/bar.jpg' }, diff --git a/packages/ra-ui-materialui/src/input/Labeled.js b/packages/ra-ui-materialui/src/input/Labeled.js index 7d257cb62e0..6b152741914 100644 --- a/packages/ra-ui-materialui/src/input/Labeled.js +++ b/packages/ra-ui-materialui/src/input/Labeled.js @@ -67,7 +67,7 @@ export const Labeled = ({ className={className} margin="normal" fullWidth={fullWidth} - error={meta && meta.touched && meta.error} + error={meta && meta.touched && !!meta.error} >