ThiefMaster
ThiefMaster

Reputation: 318518

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:

Upvotes: 1

Views: 512

Answers (1)

Evgeny Timoshenko
Evgeny Timoshenko

Reputation: 3259

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.

Upvotes: 0

Related Questions