// The rule for a required FREE-TEXT field, and where a trimmed value comes from. // // React Hook Form's `required: true` tests truthiness, and `" "` is truthy. So every required text box in // this plugin accepted a value made only of whitespace, and the save succeeded: an empty name is refused // by the server with a validation dump, a single space is stored. Nothing errored, and every screen that // lists the resource then rendered a gap where the name should be. // // TRIM BEFORE VALIDATING, NEVER AFTER. Validating first and trimming on the way out is the same defect // with an extra step - the whitespace passes the gate and becomes the empty string the server rejects, so // the user gets the raw dump instead of a field-level message. // // FREE TEXT ONLY. A field whose value comes from a picker (a state code, a status, a coding) cannot hold // whitespace, and adding this to one buys nothing while implying its options might. /** The rule a `Controller` should carry instead of a bare `required: true`. */ export const requiredText = { required: true, validate: (value: string | undefined | null): true | string => (value ?? '').trim().length > 0 ? true : 'Enter a value — spaces alone are not enough.', }; /** * The same rule applied to a field that is required only in some positions - the first address line is * required and later ones are not, and an optional line that is left as spaces must still not be stored. */ export const requiredTextWhen = (isRequired: boolean) => ( isRequired ? requiredText : { required: false, validate: () => true as const } ); /** * What to STORE. `undefined` rather than `''` on purpose: an empty string is a value the server refuses * with a raw OperationOutcome, while omitting the element says the same thing in FHIR's own terms. */ export const trimmedOrUndefined = (value: string | undefined | null): string | undefined => { const trimmed = (value ?? '').trim(); return trimmed.length > 0 ? trimmed : undefined; };