Drop the controlled-input warning and adopt controlled inputs
`TextField.Input`'s `value` prop carried a `@deprecated` tag steering
everyone toward `defaultValue`. That was an old-architecture performance
concern; we're on the New Architecture now, so the warning is gone and
CLAUDE.md says controlled inputs are the default choice.
Audited the uncontrolled inputs that warning produced. The interesting
ones were carrying workarounds to paper over the fact that the input and
the state describing it could drift apart:
- Advanced search: `ClearableInput` kept its own `showClear` state and
cleared itself through a ref, and `FilterBlock` remounted it (and the
already-controlled `AutocompleteInput`) with `key={filter.field}` to
reseed it. All three are gone.
- Group chat: `EditNamePrompt` took an `inputKey` that
`ConversationSettings` bumped on every open to remount the input,
because the native bottom sheet keeps children mounted across opens.
- Follow dialog and GIF picker: both cleared their search field through
a ref alongside the state update. The GIF picker's field had no
`value` at all, so its clear button was driven by a separate prop.
- Login and signup: both mirrored every field into a ref so submit could
read it, keeping two copies of the same string. Signup's
`prevEmailValueRef` stays - it tracks the last email we warned about,
not the input.
- The email dialog froze its field by dropping `onChangeText` after a
successful update, which stopped recording edits but didn't stop them
being typed. Now it's `editable={false}`.
The rest were plain `defaultValue` + `setState` pairs where the state
was already the source of truth for validation, character counters and
dirty checks: profile and list editing, both alt text dialogs, appeal
reason, handle change, app password name.
Also fixed `OTPInput`, which was controlled but cleared itself through
`clear()` on tap - the digit row renders from `value`, so the code
appeared to survive a clear that had already emptied the input.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016imsvP5dtEKBr6hCy1X62K
This commit is contained in:
@@ -288,8 +288,11 @@ existing usages across the app for a canonical example.
|
||||
### TextField
|
||||
|
||||
Compound component at `#/components/forms/TextField` (`TextField.LabelText`,
|
||||
`TextField.Root`, `TextField.Icon`, `TextField.Input`). Prefer `defaultValue` over
|
||||
`value` (see Footguns).
|
||||
`TextField.Root`, `TextField.Icon`, `TextField.Input`). Controlled inputs
|
||||
(`value` + `onChangeText`) are fine and are usually what you want - the old
|
||||
advice to reach for `defaultValue` was a New Architecture migration concern and
|
||||
no longer applies. Reach for `defaultValue` only when nothing outside the input
|
||||
needs to read the text.
|
||||
|
||||
### Typography
|
||||
|
||||
@@ -507,24 +510,6 @@ This applies to:
|
||||
|
||||
The Menu component on iOS specifically uses this pattern – see `src/components/Menu/index.tsx:151`.
|
||||
|
||||
### Controlled vs Uncontrolled Inputs
|
||||
|
||||
Prefer `defaultValue` over `value` for TextInput on the old architecture:
|
||||
|
||||
```tsx
|
||||
// Preferred - uncontrolled
|
||||
<TextField.Input
|
||||
defaultValue={initialEmail}
|
||||
onChangeText={setEmail}
|
||||
/>
|
||||
|
||||
// Avoid when possible - controlled (can cause performance issues)
|
||||
<TextField.Input
|
||||
value={email}
|
||||
onChangeText={setEmail}
|
||||
/>
|
||||
```
|
||||
|
||||
### Platform-Specific Behavior
|
||||
|
||||
Some components behave differently across platforms:
|
||||
|
||||
Reference in New Issue
Block a user