'Validate raw data from the field in react-final-form

I have a field in react-final-form where the user enters a date. For the internal value this gets normalized to YYYY-MM-DD but the user may enter it as DD.MM.YYYY.

For valid data this is all fine, I can use parse to normalize and format to convert back. However, if a user enters garbage, there's not much I can do in parse... I ended up doing this awful hack which works, but I wonder if there's a cleaner way that allows me to separate the parsed data that will be fed into the form values, and the data that will be used to display the component and validate the user input.

const formatDate = (value) => {
  // console.log(`format: ${value}`);
  if (!value) {
    return '';
  }
  // something invalid => keep as-is
  if (value.startsWith('INVALID:')) {
    return value.substr(8);
  }
  // we have a valid value => format using our preferred display format
  const m = value.match(/^(\d{4})-(\d{2})-(\d{2})$/);
  return `${m[3]}.${m[2]}.${m[1]}`;
};

const parseDate = (value) => {
  if (!value) {
    return undefined;
  }
  // console.log(`parse: ${value}`);
  const m = value.match(/^(\d{2})\.(\d{2})\.(\d{4})$/);
  if (m) {
    return `${m[3]}-${m[2]}-${m[1]}`;
  }
  return 'INVALID:' + value;
};

const validateDate = (value) => {
  // console.log(`validate: ${value}`);
  if (value && value.startsWith('INVALID:')) {
    return 'Invalid date';
  }
};
<Field
  name="date"
  component="input"
  type="text"
  format={formatDate}
  parse={parseDate}
  validate={validateDate}
  placeholder="dd.mm.yyyy"
/>

Here's an executable codesandbox: https://codesandbox.io/s/react-final-form-format-on-blur-example-forked-5oowz?file=/src/index.js

Note: I'm NOT looking for date pickers or similar widgets that would rely on the field not being directly editable.

Another kind of field where the current behavior feels a bit lacking is for number inputs:

  • If i parse them to an actual number, I can no longer distinguish null/empty because the field is empty (valid) or because the field contains garbage (invalid)
  • I can kind of work around this if he field is required (empty is invalid as well), but otherwise I'd once again need a hack like above...


Solution 1:[1]

You may keep both raw and parsed strings as a value for the field:

const formatDate = (value) => {
  // console.log(`format: ${value}`);
  if (!value) {
    return "";
  }
  // something invalid => keep as-is
  if (!value.parsed) {
    return value.raw;
  }
  // we have a valid value => format using our preferred display format
  const m = value.parsed.match(/^(\d{4})-(\d{2})-(\d{2})$/);
  return `${m[3]}.${m[2]}.${m[1]}`;
};

const parseDate = (value) => {
  if (!value) {
    return undefined;
  }
  // console.log(`parse: ${value}`);
  const m = value.match(/^(\d{2})\.(\d{2})\.(\d{4})$/);
  if (m) {
    return { parsed: `${m[3]}-${m[2]}-${m[1]}`, raw: value };
  }
  return { parsed: null, raw: value };
};

const validateDate = (value) => {
  // console.log(`validate: ${value}`);
  if (value && !value.parsed) {
    return "Invalid date";
  }
};

So the value of the field is actually an object of shape { raw:string, parsed:string}. When parsed is empty means the date is invalid.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Evgeny Timoshenko