fix(expo): TextInput not losing focus after dismissing keyboard#2267
Conversation
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Pull request overview
This PR addresses an Android-specific issue where a TextInput can remain focused after the keyboard is dismissed, preventing the keyboard from reopening on subsequent taps (issue #1921). It introduces a shared keyboard-hide blur mechanism, migrates app screens to use enhanced input wrappers, and adds documentation + a pre-commit safeguard to prevent regressions.
Changes:
- Add
useKeyboardHideBlurhook and enhancedTextInput/SearchInputwrapper components that automatically blur onkeyboardDidHide. - Update multiple Expo app screens to use the enhanced input wrappers instead of direct
react-native/@packrat/uiinputs. - Add docs and a lefthook pre-commit script intended to detect risky input patterns early.
Reviewed changes
Copilot reviewed 26 out of 26 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| scripts/check-android-textinput.sh | Adds a pre-commit checker for risky input patterns (currently has regex/coverage issues). |
| lefthook.yml | Runs the new Android TextInput check on pre-commit. |
| docs/solutions/ui-bugs/android-textinput-keyboard-focus-loss.md | New root-cause + solution writeup for the Android focus/keyboard issue (has broken relative links). |
| docs/android-textinput-checklist.md | New checklist for implementing/reviewing input-related changes. |
| docs/android-keyboard-prevention-implementation-summary.md | Summary of the rollout (currently references files not added in this PR). |
| docs/android-keyboard-focus-prevention-strategies.md | Prevention strategies + testing examples (testing snippets currently don’t match actual RN API/hook behavior). |
| apps/expo/lib/hooks/useKeyboardHideBlur.tsx | New hook that blurs an input ref on keyboardDidHide. |
| apps/expo/components/TextInput.tsx | Enhanced TextInput wrapper that applies the hook automatically. |
| apps/expo/components/SearchInput.tsx | Enhanced SearchInput wrapper that applies the hook automatically. |
| apps/expo/features/wildlife/screens/IdentificationScreen.tsx | Switches to enhanced TextInput. |
| apps/expo/features/weather/screens/LocationsScreen.tsx | Switches to enhanced SearchInput. |
| apps/expo/features/weather/screens/LocationSearchScreen.tsx | Switches to enhanced SearchInput. |
| apps/expo/features/trail-conditions/components/SubmitConditionReportForm.tsx | Switches to enhanced TextInput and simplifies RN imports. |
| apps/expo/features/feed/screens/PostDetailScreen.tsx | Switches to enhanced TextInput; ref typing adjusted to RN type alias. |
| apps/expo/features/feed/screens/CreatePostScreen.tsx | Switches to enhanced TextInput and simplifies RN imports. |
| apps/expo/features/catalog/screens/PackSelectionScreen.tsx | Switches to enhanced SearchInput. |
| apps/expo/features/catalog/screens/AddCatalogItemDetailsScreen.tsx | Switches to enhanced TextInput. |
| apps/expo/features/catalog/components/CatalogBrowserModal.tsx | Switches to enhanced SearchInput. |
| apps/expo/features/ai/components/ReportModal.tsx | Switches to enhanced TextInput. |
| apps/expo/features/ai-packs/screens/AIPacksScreen.tsx | Switches to enhanced TextInput and minor formatting adjustment. |
| apps/expo/app/auth/one-time-password.tsx | Applies useKeyboardHideBlur directly to the OTP field ref. |
| apps/expo/app/(app)/trip/location-search.tsx | Switches to enhanced SearchInput. |
| apps/expo/app/(app)/textinputdebug.tsx | Removes debug screen file. |
| apps/expo/app/(app)/messages/chat.tsx | Switches to enhanced TextInput. |
| apps/expo/app/(app)/messages/chat.android.tsx | Switches to enhanced TextInput. |
| apps/expo/app/(app)/ai-chat.tsx | Switches to enhanced TextInput. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| - **Architecture**: [CLAUDE.md](../CLAUDE.md#L79-L96) - Mobile app architecture patterns | ||
| - **Testing**: [TESTING.md](../TESTING.md#L57-L61) - Mobile component testing patterns |
| it('should blur input when keyboard hides', () => { | ||
| const mockBlur = jest.fn(); | ||
| const mockRef = { current: { blur: mockBlur } }; | ||
|
|
||
| renderHook(() => useKeyboardHideBlur(mockRef)); | ||
|
|
||
| // Simulate keyboard hide event | ||
| Keyboard.emit('keyboardDidHide'); | ||
|
|
||
| expect(mockBlur).toHaveBeenCalled(); | ||
| }); | ||
|
|
||
| it('should clean up listener on unmount', () => { | ||
| const mockRef = { current: { blur: jest.fn() } }; | ||
| const { unmount } = renderHook(() => useKeyboardHideBlur(mockRef)); | ||
|
|
||
| const removeSpy = jest.spyOn(Keyboard, 'removeAllListeners'); | ||
| unmount(); | ||
|
|
||
| expect(removeSpy).toHaveBeenCalled(); |
| const mockRef = { current: { blur: jest.fn() } }; | ||
| const { unmount } = renderHook(() => useKeyboardHideBlur(mockRef)); | ||
|
|
||
| const removeSpy = jest.spyOn(Keyboard, 'removeAllListeners'); | ||
| unmount(); | ||
|
|
||
| expect(removeSpy).toHaveBeenCalled(); |
|
|
||
| # 3. Check for third-party input component imports that might need wrapping | ||
| echo " Checking for third-party input components..." | ||
| THIRD_PARTY_INPUTS=$(find apps/expo -name "*.tsx" -o -name "*.ts" | xargs grep -H -E "import.*[Ii]nput.*from ['\"](?!react-native|expo-app|@packrat)" 2>/dev/null || true) |
| DIRECT_IMPORTS=$(find apps/expo -name "*.tsx" -o -name "*.ts" | grep -v "components/TextInput.tsx" | grep -v "components/SearchInput.tsx" | xargs grep -H "import.*TextInput.*from ['\"]react-native['\"]" 2>/dev/null || true) | ||
|
|
| # 2. Check for new input components without useKeyboardHideBlur | ||
| echo " Checking for input components without keyboard fix..." | ||
| STAGED_FILES=$(git diff --cached --name-only --diff-filter=A | grep -E '\.(tsx?)$' || true) |
| - `apps/expo/lib/hooks/__tests__/useKeyboardHideBlur.test.ts` - Unit tests for the hook | ||
| - `apps/expo/components/__tests__/TextInput.test.tsx` - Component tests | ||
| - `apps/expo/__tests__/e2e/android-keyboard-behavior.e2e.ts` - E2E test examples |
| ### Automation | ||
| - `scripts/check-android-textinput.sh` - Pre-commit hook script | ||
| - `scripts/eslint-android-textinput-rules.js` - ESLint rules for prevention | ||
| - `lefthook.yml` - Updated with Android TextInput checks |
|
@copilot apply changes based on the comments in this thread Additional notes:
|
- Fix check-android-textinput.sh: - Use awk for multiline-capable TextInput import detection - Replace PCRE lookahead with portable grep -v pipeline for third-party check - Change --diff-filter=A to --diff-filter=AM to catch modifications too - Fix android-keyboard-focus-prevention-strategies.md: - Replace non-existent Keyboard.emit() with proper addListener mock pattern - Fix cleanup test to assert remove() on subscription object - Add event type assertion to both unit and integration test examples - Fix android-textinput-keyboard-focus-loss.md: - Correct broken cross-reference paths (../CLAUDE.md -> ../../../CLAUDE.md) - Fix android-keyboard-prevention-implementation-summary.md: - Remove references to non-existent test files - Remove reference to non-existent eslint-android-textinput-rules.js Agent-Logs-Url: https://github.com/PackRat-AI/PackRat/sessions/804a88c4-bdca-492f-b2e8-a62b2174dfbe Co-authored-by: mikib0 <54102880+mikib0@users.noreply.github.com>
Done in commit
|
|
@copilot just remove the TextInput precommit check. It's still not accurate, it's getting complicated. remember to update relevant doc areas as well. |
|
@copilot and searchinput check too. the whole import check thing. |
- Delete scripts/check-android-textinput.sh - Remove android-textinput-check entry from lefthook.yml - Update all docs to remove references to the deleted script: - android-keyboard-prevention-implementation-summary.md - android-keyboard-focus-prevention-strategies.md - android-textinput-checklist.md Agent-Logs-Url: https://github.com/PackRat-AI/PackRat/sessions/4d463d8b-d0b7-4c89-943d-fb9c1b4f8177
Done in commit |
…ndroid fix(expo): TextInput not losing focus after dismissing keyboard
This pull request introduces enhanced custom
TextInputandSearchInputcomponents that automatically apply a keyboard hide blur fix, improving Android keyboard behavior across the app. All usages of the standard React NativeTextInputand NativeWindUISearchInputhave been replaced with these new components, ensuring consistent behavior and maintainability. Additionally, some related refactoring and cleanup were performed to support these changes.Keyboard behavior improvements:
TextInputandSearchInputcomponents inexpo-app/componentsthat wrap the standard components and apply a keyboard hide blur fix usinguseKeyboardHideBlurandasNonNullableRef. These serve as drop-in replacements and forward refs properly. [1] [2]Codebase-wide adoption of new components:
TextInputand NativeWindUI'sSearchInputwith the new custom components throughout the app, including in AI chat, messages, catalog, feed, trail conditions, weather, wildlife, and authentication screens. [1] [2] [3] [4] [5] [6] [7] [8] F02a4ca1L1, [9] [10] [11] [12] [13] [14] [15] [16] [17] [18] [19] [20] [21]Cleanup and removal of obsolete code:
TextInputDebugscreen, as it is no longer necessary with the new input components.Authentication and utility improvements:
These changes standardize text input behavior across the app, particularly improving the user experience on Android devices.