Skip to content

Commit

Permalink
Merge pull request #3511 from marmelab/date-input-use-input
Browse files Browse the repository at this point in the history
[RFR] Migrate DateInput to use useInput hook
  • Loading branch information
djhi committed Aug 13, 2019
2 parents 4d8dc15 + 823121b commit ded57c0
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 56 deletions.
2 changes: 1 addition & 1 deletion examples/simple/src/posts/PostCreate.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ const PostCreateToolbar = props => (

const backlinksDefaultValue = [
{
date: new Date().toISOString(),
date: new Date(),
url: 'http://google.com',
},
];
Expand Down
1 change: 1 addition & 0 deletions packages/ra-ui-materialui/src/input/BooleanInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ const BooleanInput: FunctionComponent<
source,
type: 'checkbox',
validate,
...rest,
});

const handleChange = useCallback(
Expand Down
60 changes: 27 additions & 33 deletions packages/ra-ui-materialui/src/input/DateInput.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { useCallback } from 'react';
import React from 'react';
import PropTypes from 'prop-types';
import TextField from '@material-ui/core/TextField';
import { addField, FieldTitle } from 'ra-core';
import { useInput, FieldTitle } from 'ra-core';

import sanitizeRestProps from './sanitizeRestProps';
import InputHelperText from './InputHelperText';
Expand All @@ -12,7 +12,7 @@ import InputHelperText from './InputHelperText';
* @param {Date} v value to convert
* @returns {String} A standardized date (yyyy-MM-dd), to be passed to an <input type="date" />
*/
const dateFormatter = v => {
const convertDateToString = v => {
if (!(v instanceof Date) || isNaN(v.getDate())) return;
const pad = '00';
const yyyy = v.getFullYear().toString();
Expand All @@ -23,59 +23,59 @@ const dateFormatter = v => {

const dateRegex = /^\d{4}-\d{2}-\d{2}$/;

const sanitizeValue = value => {
const format = value => {
// null, undefined and empty string values should not go through dateFormatter
// otherwise, it returns undefined and will make the input an uncontrolled one.
if (value == null || value === '') {
return '';
}

if (value instanceof Date) {
return dateFormatter(value);
return convertDateToString(value);
}

// valid dates should not be converted
if (dateRegex.test(value)) {
return value;
}

return dateFormatter(new Date(value));
return convertDateToString(new Date(value));
};

export const DateInput = ({
className,
meta,
input,
isRequired,
label,
options,
source,
resource,
helperText,
onBlur,
onChange,
onFocus,
validate,
...rest
}) => {
const handleChange = useCallback(
event => {
input.onChange(event.target.value);
},
[input]
);

if (typeof meta === 'undefined') {
throw new Error(
"The DateInput component wasn't called within a react-final-form <Field>. Did you decorate it and forget to add the addField prop to your component? See https://marmelab.com/react-admin/Inputs.html#writing-your-own-input-component for details."
);
}
const { touched, error } = meta;
const value = sanitizeValue(input.value);
const {
id,
input,
isRequired,
meta: { error, touched },
} = useInput({
format,
onBlur,
onChange,
onFocus,
resource,
source,
validate,
...rest,
});

return (
<TextField
id={id}
{...input}
className={className}
type="date"
margin="normal"
id={`${resource}_${source}_date_input`}
error={!!(touched && error)}
helperText={
<InputHelperText
Expand All @@ -97,19 +97,13 @@ export const DateInput = ({
}}
{...options}
{...sanitizeRestProps(rest)}
value={value}
onChange={handleChange}
/>
);
};

DateInput.propTypes = {
classes: PropTypes.object,
className: PropTypes.string,
input: PropTypes.object,
isRequired: PropTypes.bool,
label: PropTypes.string,
meta: PropTypes.object,
options: PropTypes.object,
resource: PropTypes.string,
source: PropTypes.string,
Expand All @@ -119,4 +113,4 @@ DateInput.defaultProps = {
options: {},
};

export default addField(DateInput);
export default DateInput;
67 changes: 45 additions & 22 deletions packages/ra-ui-materialui/src/input/DateInput.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,53 +2,76 @@ import React from 'react';
import expect from 'expect';
import { render, fireEvent, cleanup } from '@testing-library/react';

import { DateInput } from './DateInput';
import DateInput from './DateInput';
import { Form } from 'react-final-form';
import { required } from 'ra-core';

describe('<DateInput />', () => {
const defaultProps = {
resource: 'bar',
source: 'foo',
meta: {},
input: {},
translate: x => x,
resource: 'posts',
source: 'publishedAt',
};

afterEach(cleanup);

it('should render a date input', () => {
const { getByLabelText } = render(<DateInput {...defaultProps} />);
expect(getByLabelText('resources.bar.fields.foo').type).toBe('date');
const { getByLabelText } = render(
<Form
onSubmit={jest.fn}
render={() => <DateInput {...defaultProps} />}
/>
);
expect(getByLabelText('resources.posts.fields.publishedAt').type).toBe(
'date'
);
});

it('should call `input.onChange` method when changed', () => {
const onChange = jest.fn();
let formApi;
const { getByLabelText } = render(
<DateInput {...defaultProps} input={{ onChange }} />
<Form
onSubmit={jest.fn()}
render={({ form }) => {
formApi = form;
return <DateInput {...defaultProps} />;
}}
/>
);
const input = getByLabelText('resources.bar.fields.foo');
fireEvent.change(input, { target: { value: '2010-01-04' } });
expect(onChange.mock.calls[0][0]).toBe('2010-01-04');
const input = getByLabelText('resources.posts.fields.publishedAt');
fireEvent.change(input, {
target: { value: '2010-01-04' },
});
expect(formApi.getState().values.publishedAt).toEqual('2010-01-04');
});

describe('error message', () => {
it('should not be displayed if field is pristine', () => {
const { queryByText } = render(
<DateInput
{...defaultProps}
meta={{ touched: false, error: 'Required field.' }}
<Form
onSubmit={jest.fn}
render={() => (
<DateInput {...defaultProps} validate={required()} />
)}
/>
);
expect(queryByText('Required field.')).toBeNull();
expect(queryByText('ra.validation.required')).toBeNull();
});

it('should be displayed if field has been touched and is invalid', () => {
const { queryByText } = render(
<DateInput
{...defaultProps}
meta={{ touched: true, error: 'Required field.' }}
const { getByLabelText, queryByText } = render(
<Form
onSubmit={jest.fn}
validateOnBlur
render={() => (
<DateInput {...defaultProps} validate={required()} />
)}
/>
);
expect(queryByText('Required field.')).toBeDefined();
const input = getByLabelText(
'resources.posts.fields.publishedAt *'
);
fireEvent.blur(input);
expect(queryByText('ra.validation.required')).not.toBeNull();
});
});
});

0 comments on commit ded57c0

Please sign in to comment.