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

parse function not returning data correctly for DateInput component #6573

Closed
FernandoKGA opened this issue Sep 10, 2021 · 10 comments
Closed
Labels

Comments

@FernandoKGA
Copy link
Contributor

What you were expecting:

While trying to use the DateInput component, I observed that it was returning an empty string for the date, however I needed it to return null so I could send it through the dataProvider. Then I used the parse property of DateInput to make it return null when the field were empty.

What happened instead:

When using the parsing function that returns the data I wanted (null when the field is empty), it is returning an empty string "" to the field rejected_at.

Steps to reproduce:

  1. Let the console tab of the sandbox open to see logs.
  2. Access /posts/1 which is where I mocked the rejected_at (inside data.tsx) field, in the post with id 1.
  3. Remove the date from the Rejected at field.
  4. Save the post.
  5. Find the log that stand for the post object (probably would be the first log) and see that the rejected_at field is an "" and not null.

Related code:

Parsing function located at PostEdit.tsx:

const dateParser = (v) => {
  // v is a string of "YYYY-MM-DD" format
  const match = /(\d{4})-(\d{2})-(\d{2})/.exec(v);

  if (match !== null) {
    const d = new Date(match[1], parseInt(match[2], 10) - 1, match[3]);
    if (isNaN(d.valueOf())) return;
    return d;
  } else {
    return null;
  }
};

Other information:

I managed to bypass the problem using it along with format property and passing the original default code for the format function (format = getStringFromDate at the parameters) from DateInput.tsx, but instead of returning "" at the first if, I have changed it to return value. I passed it to the format function and then it returned the null value that I needed.

File ra-ui-materialui/src/input/DateInput.tsx:

const convertDateToString = (value) => {
  if (!(value instanceof Date) || isNaN(value.getDate())) return "";
  const pad = "00";
  const yyyy = value.getFullYear().toString();
  const MM = (value.getMonth() + 1).toString();
  const dd = value.getDate().toString();
  return `${yyyy}-${(pad + MM).slice(-2)}-${(pad + dd).slice(-2)}`;
};

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

const getStringFromDate = (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 "";  //<- problem
  }

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

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

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

// new getStringFromDate function where the problem is resolved
const getStringFromDate = (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 value;
  }

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

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

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

This behavior should not happen as the parse function was supposed to transform the input -> record as the documentation mentions. The format function is somehow influencing the transformation.

Environment

  • React-admin version: 3.17.3
  • React version: 17.0.2
  • Browser: Firefox 91.0.2 (64-bit) Mozilla Firefox for Ubuntu canonical - 1.0
@fzaninotto
Copy link
Member

That's a react-final-form feature:

By default, if your value is null, will convert it to '', to ensure controlled inputs.

https://final-form.org/docs/react-final-form/types/FieldProps#allownull

You can disable it with <DateField allowNull> if you want to accept null values. Note that this will create problems with the React field passing from controlled to uncontrolled, but if that's what you want...

@FernandoKGA
Copy link
Contributor Author

That's a react-final-form feature:

By default, if your value is null, will convert it to '', to ensure controlled inputs.

https://final-form.org/docs/react-final-form/types/FieldProps#allownull

You can disable it with <DateField allowNull> if you want to accept null values. Note that this will create problems with the React field passing from controlled to uncontrolled, but if that's what you want...

The allowNull property doesn't work with it. You can test it.

@FernandoKGA
Copy link
Contributor Author

That's a react-final-form feature:

By default, if your value is null, will convert it to '', to ensure controlled inputs.

https://final-form.org/docs/react-final-form/types/FieldProps#allownull

You can disable it with <DateField allowNull> if you want to accept null values. Note that this will create problems with the React field passing from controlled to uncontrolled, but if that's what you want...

Also, the problem isn't that I want the INPUT to accept null values. I want to send the null value to the dataProvider, that should be the responsability of parse, not interfering at the input itself. There is any other way to do so?

@fzaninotto
Copy link
Member

Reopening - there is something else at stake here. A simple parse funciton like v => null works for TextInput but not for DateInput. Apparently the culprit is the formatOnBlur property passed to useInput (if I remove it from DateField, the parse works).

@fzaninotto fzaninotto reopened this Sep 13, 2021
@fzaninotto
Copy link
Member

After further research, formatOnBlur is the culprit and it's a (serious IMO) react-final-form bug:

final-form/react-final-form#383
final-form/react-final-form#560

I don't know if we can solve it in react-admin.

@fzaninotto fzaninotto added the bug label Sep 13, 2021
@fzaninotto
Copy link
Member

Unit test case reproducing the bug:

    it('should accept a parse function returning null', () => {
        let formApi;
        const { getByLabelText } = render(
            <Form
                onSubmit={jest.fn()}
                initialValues={{ publishedAt: new Date('2021-09-11') }}
                render={({ form }) => {
                    formApi = form;
                    return <DateInput resource="posts" source="publishedAt" parse={val => null} />;
                }}
            />
        );
        const input = getByLabelText(
            'resources.posts.fields.publishedAt'
        ) as HTMLInputElement;
        expect(input.value).toBe('2021-09-11');
        expect(formApi.getState().values.publishedAt).toEqual(
            new Date('2021-09-11')
        );
        fireEvent.change(input, {
            target: { value: '' },
        });
        fireEvent.blur(input);
        expect(formApi.getState().values.publishedAt).toBeNull();
    });

@FernandoKGA
Copy link
Contributor Author

After further research, formatOnBlur is the culprit and it's a (serious IMO) react-final-form bug:

final-form/react-final-form#383
final-form/react-final-form#560

I don't know if we can solve it in react-admin.

I will take a look into it when possible. Probably there's an workaround to allow that.

@Jongmun-Park
Copy link

Jongmun-Park commented Oct 5, 2021

i have this problem too.
i used 2 ways.
but, not working like @FernandoKGA

  1. initialValues (defaultValue)
    <DateInput source="refunded_date" defaultValue={null} />

  2. parse
    as far as i know,
    parser is working only when value is changed(date is inputted).

so i used customToolbar temporarily.

const PurchaseHistoryEditToolbar = (props: ToolbarProps) => (
  <Toolbar {...props}>
    <SaveButton
      transform={(data) => {
        if (data.refunded_date === '') data.refunded_date = null;
        if (data.settlement_date === '') data.settlement_date = null;
        return data;
      }}
      submitOnEnter={false}
    />
  </Toolbar>
);

<Edit {...props}>
  <SimpleForm toolbar={<PurchaseHistoryEditToolbar />}>
</Edit>

@WiXSL
Copy link
Contributor

WiXSL commented Nov 17, 2021

@djhi, could this be closed?

@djhi
Copy link
Collaborator

djhi commented Nov 18, 2021

Yes, thanks

@djhi djhi closed this as completed Nov 18, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

5 participants