Skip to content

Incorrect TextArea behavior when additional params are specified in t…#4564

Open
ashklianko wants to merge 1 commit into
masterfrom
issue-4544-fix
Open

Incorrect TextArea behavior when additional params are specified in t…#4564
ashklianko wants to merge 1 commit into
masterfrom
issue-4544-fix

Conversation

@ashklianko

@ashklianko ashklianko commented Jun 24, 2026

Copy link
Copy Markdown
Member

…he content type #4544

Problem

Two related regressions in the form2 leaf input components, both rooted in how the form layer reconciles the parsed Value with what the user actually typed.

The first regression affected DateInput, DateTimeInput, TimeInput, InstantInput, LongInput, DoubleInput, GeoPointInput, and TextLineInput. Typing fast into any of them could intermittently wipe what was just typed. Each of those components carried a local useState(rawInput) that mirrored the input text, plus a useRef(isLocalChange) flag whose only purpose was to suppress one re-render from the parent right after onChange. That flag was one-shot: a single re-render consumed it, and any subsequent re-render — Strict Mode double-effects, a parent re-render emitting a fresh null Value instance, validation cycles re-emitting the same value — would skip the suppression and overwrite rawInput with the empty string returned by valueToString(null). The faster the user typed, the more chances those extra re-renders had to slip in.

The second regression affected TextAreaInput. Pasting a string longer than the configured maximum length — or typing past the limit when showCounter is enabled, which removes the browser's own length enforcement — wiped the textarea contents and showed no error message. TextAreaInput was fully controlled by value with no local raw-input fallback. When InputField.handleChange ran descriptor.validate and saw an error, it stored newNullValue() in PropertyArray; the next render set the textarea's value to the empty string and the DOM was cleared. The localized "Text is too long" message did not appear either, because TextAreaDescriptor.validate short-circuited on value.isNull() without ever looking at rawValue, and used a hardcoded English string instead of i18n('field.value.breaks.maxlength', …) — the same localization helper that TextLineDescriptor already used for the equivalent error.

Both symptoms share the same architectural root: leaf inputs cannot reconstruct user-typed text from value alone once the form layer has decided to nullify invalid input.

Solution

Promote raw user input to a first-class property on the form/leaf boundary and make every leaf input stateless with respect to display.

OccurrenceManager already stored a rawValues array privately alongside values. The public OccurrenceManagerState now exposes rawValues: (string | undefined)[] as a read-only field. InputTypeComponentProps gets a new rawValue?: string property. InputField (single-occurrence mode) and OccurrenceList (single + list-item paths) thread state.rawValues[index] through to each leaf. The onChange contract is unchanged — leaves call onChange(parsedValue, rawText?), which InputField.handleChange already forwarded to manager.set(index, value, rawValue).

A new utility module form2/utils/displayValue.ts resolves what each input renders: when rawValue is present it wins; otherwise toDisplay(value) is used for non-null values; otherwise the empty string.

Picker confirmation handlers in DateInput, DateTimeInput, TimeInput, and InstantInput pass their formatted display string as rawValue alongside the parsed Value, so the picker's user-readable form (for example 2025-06-15 14:30 without seconds) survives storage normalisation to 2025-06-15T14:30:00.

All eight affected components lose their useState(rawInput), their useRef(isLocalChange), and their value-sync useEffect. TextAreaInput adopts the same displayValue pattern and now passes inputValue as rawValue on every change. That is what makes the maximum-length bug go away: even when descriptor.validate flags the input and InputField stores null in PropertyArray, the user's text is preserved in state.rawValues[index] and reaches the textarea through display.

TextAreaDescriptor.validate was aligned with TextLineDescriptor.validate: it now accepts rawValue?: string, falls back to rawValue when value.isNull(), and emits the localized message i18n('field.value.breaks.maxlength', config.maxLength) instead of the hardcoded English string. Behaviour and error wording are now exactly symmetric with TextLine.

InputField's value-sync effect previously called sync(values) with a closure snapshot of the values array. Fast typing could schedule multiple effect runs whose closures still pointed to older snapshots. OccurrenceManager.setValues compares incoming values against this.values by reference, so a stale snapshot fed into it cleared rawValues at the affected index — making typed text disappear whenever the parsed value was null. The effect now reads the current values fresh via propertyArray.getProperties().map(p => p.getValue()) and uses the closure's values only as a dependency trigger.

Closes #4544

@ashklianko

ashklianko commented Jun 24, 2026

Copy link
Copy Markdown
Member Author

Found a problem, hold on
UPD, problem solved

@ashklianko ashklianko marked this pull request as ready for review June 25, 2026 07:26
@ashklianko ashklianko requested a review from alansemenov June 25, 2026 07:26
@ashklianko

Copy link
Copy Markdown
Member Author

Can be checked, @edloidas

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Incorrect TextArea behavior when additional params are specified in the content type

2 participants