Files
bsky-social-app/src/screens/Search/components/AdvancedSearchDialog/ClearableInput.tsx
T
Claude b111676744 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
2026-09-02 22:15:43 +00:00

62 lines
1.5 KiB
TypeScript

import {View} from 'react-native'
import {useLingui} from '@lingui/react/macro'
import {atoms as a, useTheme} from '#/alf'
import {Button, ButtonIcon} from '#/components/Button'
import * as Dialog from '#/components/Dialog'
import * as TextField from '#/components/forms/TextField'
import {TimesLarge_Stroke2_Corner0_Rounded as XIcon} from '#/components/icons/Times'
/**
* A text input with a clear (X) button inside it on the right.
*/
export function ClearableInput({
label,
value,
placeholder,
onChangeText,
onSubmitEditing,
}: {
label: string
value: string
placeholder?: string
onChangeText: (text: string) => void
onSubmitEditing?: () => void
}) {
const t = useTheme()
const {t: l} = useLingui()
return (
<View style={[a.relative]}>
<TextField.Root>
<Dialog.Input
label={label}
value={value}
placeholder={placeholder}
keyboardAppearance={t.scheme}
autoCorrect={false}
autoComplete="off"
autoCapitalize="none"
style={[a.pr_2xl]}
onChangeText={onChangeText}
onSubmitEditing={onSubmitEditing}
/>
</TextField.Root>
{value.length > 0 && (
<View
style={[a.absolute, a.justify_center, {top: 0, bottom: 0, right: 8}]}>
<Button
label={l`Clear`}
onPress={() => onChangeText('')}
size="tiny"
color="secondary"
shape="round">
<ButtonIcon icon={XIcon} />
</Button>
</View>
)}
</View>
)
}