Skip to content

fix(expo): TextInput not losing focus after dismissing keyboard#2267

Merged
mikib0 merged 4 commits into
developmentfrom
fix/ai-chat-input-keyboard-android
Apr 21, 2026
Merged

fix(expo): TextInput not losing focus after dismissing keyboard#2267
mikib0 merged 4 commits into
developmentfrom
fix/ai-chat-input-keyboard-android

Conversation

@mikib0
Copy link
Copy Markdown
Collaborator

@mikib0 mikib0 commented Apr 21, 2026

This pull request introduces enhanced custom TextInput and SearchInput components that automatically apply a keyboard hide blur fix, improving Android keyboard behavior across the app. All usages of the standard React Native TextInput and NativeWindUI SearchInput have 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:

  • Added new TextInput and SearchInput components in expo-app/components that wrap the standard components and apply a keyboard hide blur fix using useKeyboardHideBlur and asNonNullableRef. These serve as drop-in replacements and forward refs properly. [1] [2]

Codebase-wide adoption of new components:

Cleanup and removal of obsolete code:

  • Removed the standalone TextInputDebug screen, as it is no longer necessary with the new input components.

Authentication and utility improvements:

  • Updated the OTP field in the authentication flow to use the new keyboard blur fix, and refactored utility hooks and references to support the new components. [1] [2]

These changes standardize text input behavior across the app, particularly improving the user experience on Android devices.

@mikib0 mikib0 requested a review from Copilot April 21, 2026 10:24
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 21, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c527e30a-eae3-4d86-ad57-a30099b44755

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/ai-chat-input-keyboard-android

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions Bot added documentation Improvements or additions to documentation mobile labels Apr 21, 2026
@mikib0 mikib0 linked an issue Apr 21, 2026 that may be closed by this pull request
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 useKeyboardHideBlur hook and enhanced TextInput / SearchInput wrapper components that automatically blur on keyboardDidHide.
  • Update multiple Expo app screens to use the enhanced input wrappers instead of direct react-native / @packrat/ui inputs.
  • 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.

Comment on lines +301 to +302
- **Architecture**: [CLAUDE.md](../CLAUDE.md#L79-L96) - Mobile app architecture patterns
- **Testing**: [TESTING.md](../TESTING.md#L57-L61) - Mobile component testing patterns
Comment on lines +176 to +195
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();
Comment on lines +189 to +195
const mockRef = { current: { blur: jest.fn() } };
const { unmount } = renderHook(() => useKeyboardHideBlur(mockRef));

const removeSpy = jest.spyOn(Keyboard, 'removeAllListeners');
unmount();

expect(removeSpy).toHaveBeenCalled();
Comment thread scripts/check-android-textinput.sh Outdated

# 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)
Comment thread scripts/check-android-textinput.sh Outdated
Comment on lines +19 to +20
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)

Comment thread scripts/check-android-textinput.sh Outdated
Comment on lines +30 to +32
# 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)
Comment on lines +38 to +40
- `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
Comment on lines +42 to +45
### 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
@mikib0
Copy link
Copy Markdown
Collaborator Author

mikib0 commented Apr 21, 2026

@copilot apply changes based on the comments in this thread

Additional notes:

  • Remove all parts pointing readers to non-existent files
  • Yes switch to a multiline-capable search for TextInput import check but make sure that you test and working before yielding
  • Rewrite third-party import check without lookaheads as suggested but again make sure you test and that it’s working

- 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>
Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 21, 2026

@copilot apply changes based on the comments in this thread

Additional notes:
...

Done in commit 2cc2b3d. Here's a summary of what was changed:

scripts/check-android-textinput.sh:

  • Replaced the single-line grep regex for TextInput import detection with an awk-based approach that correctly handles both single-line and multiline import statements (e.g. import {\n TextInput,\n} from 'react-native')
  • Replaced the PCRE lookahead (?!...) in the third-party import check with a portable grep -v pipeline (compatible with POSIX/BSD grep -E)
  • Changed --diff-filter=A to --diff-filter=AM so staged checks also catch edits to existing files, not just newly added ones

docs/android-keyboard-focus-prevention-strategies.md:

  • Replaced Keyboard.emit('keyboardDidHide') (non-existent RN API) with a proper jest.spyOn(Keyboard, 'addListener') mock that captures the callback, then invokes it to simulate the keyboard hide event
  • Fixed the cleanup test to assert remove() is called on the mocked subscription object (rather than spying on Keyboard.removeAllListeners)
  • Added expect(event).toBe('keyboardDidHide') assertions in both unit and integration test examples

docs/solutions/ui-bugs/android-textinput-keyboard-focus-loss.md:

  • Fixed broken cross-reference paths: ../CLAUDE.md../../../CLAUDE.md and ../TESTING.md../../../TESTING.md

docs/android-keyboard-prevention-implementation-summary.md:

  • Removed references to non-existent test files (apps/expo/lib/hooks/__tests__/, apps/expo/components/__tests__/, apps/expo/__tests__/e2e/)
  • Removed the non-existent scripts/eslint-android-textinput-rules.js entry and its associated "Optional: Add ESLint Rules" setup section

@mikib0
Copy link
Copy Markdown
Collaborator Author

mikib0 commented Apr 21, 2026

@copilot just remove the TextInput precommit check. It's still not accurate, it's getting complicated. remember to update relevant doc areas as well.

@mikib0
Copy link
Copy Markdown
Collaborator Author

mikib0 commented Apr 21, 2026

@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
Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 21, 2026

@copilot just remove the TextInput precommit check. It's still not accurate, it's getting complicated. remember to update relevant doc areas as well.

Done in commit 52bf53b. Removed scripts/check-android-textinput.sh, dropped the android-textinput-check entry from lefthook.yml, and cleaned up all references in the docs (android-keyboard-prevention-implementation-summary.md, android-keyboard-focus-prevention-strategies.md, android-textinput-checklist.md).

@mikib0 mikib0 merged commit 33f48a0 into development Apr 21, 2026
4 checks passed
@mikib0 mikib0 deleted the fix/ai-chat-input-keyboard-android branch April 21, 2026 12:52
andrew-bierman pushed a commit that referenced this pull request May 14, 2026
…ndroid

fix(expo): TextInput not losing focus after dismissing keyboard
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation mobile

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Android- Packrat-AI Search Bar Keyboard Issue

3 participants