You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React Native切换Tabs时TextInput失焦及首次点击键盘闪烁问题

React Native TabView内TextInput焦点异常问题

问题现象

  • 切换标签时,TextInput失去焦点,键盘自动收起;
  • 首次点击Websites标签下的TextInput时,键盘短暂闪烁后消失,需第二次点击才能正常唤起;
  • 仅在从Apps标签切换到Websites标签时出现上述问题,其他标签切换组合无异常。

相关代码

TextInputSelectionScene 子组件

import * as React from 'react'
import { useState } from 'react'
import { FlatList, StyleSheet, TextInput } from 'react-native'
import { T } from '../../../design-system/theme.ts'
import { SelectableSirenCard } from '../SelectableSirenCard.tsx'
import { SirenType } from '../../../../core/blocklist/blocklist.ts'

export function TextInputSelectionScene(props: {
  onSubmitEditing: (event: { nativeEvent: { text: string } }) => void
  placeholder: string
  sirenType: SirenType.WEBSITES | SirenType.KEYWORDS
  data: string[]
  toggleSiren: (sirenType: SirenType, sirenId: string) => void
  isSirenSelected: (sirenType: SirenType, sirenId: string) => boolean
}) {
  const [isFocused, setIsFocused] = useState(true)

  return (
    <>
      <TextInput
        style={[
          styles.addWebsiteInput,
          { borderColor: isFocused ? randomColor : T.color.white },
        ]}
        placeholder={props.placeholder}
        placeholderTextColor={T.color.white}
        onFocus={() => setIsFocused(true)}
        onBlur={() => setIsFocused(false)}
        onSubmitEditing={props.onSubmitEditing}
      />
      <FlatList
        data={props.data}
        keyExtractor={(item) => item}
        renderItem={({ item }) => (
          <SelectableSirenCard
            sirenType={props.sirenType}
            siren={item}
            onPress={() => props.toggleSiren(props.sirenType, item)}
            isSelected={props.isSirenSelected(props.sirenType, item)}
          />
        )}
      />
    </>
  )
}

const colors = [
  '#FF0000',
  '#00FF00',
  '#0000FF',
  '#FFFF00',
  '#00FFFF',
  '#FF00FF',
]
const randomColor = colors[Math.floor(Math.random() * colors.length)]

const styles = StyleSheet.create({
  addWebsiteInput: {
    borderBottomWidth: 2,
    borderBottomColor: randomColor,
    padding: T.spacing.small,
    color: T.color.white,
  },
})

CreateBlocklistScreen 父组件

import { Dimensions, StyleSheet, Text } from 'react-native'
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
import { ScreenList } from '../../../navigators/screen-lists/screenLists.ts'
import { TabScreens } from '../../../navigators/screen-lists/TabScreens.ts'
import { TiedSLinearBackground } from '../../../design-system/components/TiedSLinearBackground.tsx'
import { TiedSBlurView } from '../../../design-system/components/TiedSBlurView.tsx'
import { T } from '../../../design-system/theme'
import { TiedSTextInput } from '../../../design-system/components/TiedSTextInput.tsx'
import * as React from 'react'
import { useEffect, useState } from 'react'
import { installedAppsRepository } from '../../../dependencies.ts'
import { InstalledApp } from '../../../../core/installed-app/InstalledApp.ts'
import { Route, SceneMap, TabBarProps, TabView } from 'react-native-tab-view'
import { TiedSButton } from '../../../design-system/components/TiedSButton.tsx'
import { BlocklistsStackScreens } from '../../../navigators/screen-lists/BlocklistsStackScreens.ts'
import { ChooseBlockTabBar } from './ChooseBlockTabBar.tsx'
import { useDispatch } from 'react-redux'
import { createBlocklist } from '../../../../core/blocklist/usecases/create-blocklist.usecase.ts'
import { AppDispatch } from '../../../../core/_redux_/createStore.ts'
import {
  Blocklist,
  Sirens,
  SirenType,
} from '../../../../core/blocklist/blocklist.ts'
import { AppsSelectionScene } from './AppsSelectionScene.tsx'
import { TextInputSelectionScene } from './TextInputSelectionScene.tsx'

type BlocklistScreenProps = {
  navigation: NativeStackNavigationProp<ScreenList, TabScreens.BLOCKLIST>
}

export function CreateBlocklistScreen({
  navigation,
}: Readonly<BlocklistScreenProps>) {
  const dispatch = useDispatch<AppDispatch>()

  const [installedApps, setInstalledApps] = useState<InstalledApp[]>([])
  const [websites, setWebsites] = useState<string[]>([])
  const [keywords, setKeywords] = useState<string[]>([])

  const [blocklist, setBlocklist] = useState<Omit<Blocklist, 'id'>>({
    name: '',
    sirens: {
      android: [],
      ios: [],
      windows: [],
      macos: [],
      linux: [],
      websites: [],
      keywords: [],
    },
  })
  const [index, setIndex] = useState(0)
  const routes = [
    { key: 'apps', title: 'Apps' },
    { key: 'websites', title: 'Websites' },
    { key: 'keywords', title: 'Keywords' },
  ]

  useEffect(() => {
    installedAppsRepository.getInstalledApps().then((apps) => {
      setInstalledApps(apps)
    })
  }, [])

  function toggleSiren(sirenType: keyof Sirens, sirenId: string) {
    setBlocklist((prevBlocklist) => {
      const updatedSirens = { ...prevBlocklist.sirens }

      updatedSirens[sirenType] = updatedSirens[sirenType].includes(sirenId)
        ? updatedSirens[sirenType].filter(
            (selectedSiren) => selectedSiren !== sirenId,
          )
        : [...updatedSirens[sirenType], sirenId]

      return {
        ...prevBlocklist,
        sirens: updatedSirens,
      }
    })
  }

  function isSirenSelected(sirenType: SirenType, sirenId: string) {
    return blocklist.sirens[sirenType].includes(sirenId)
  }

  const renderScene = SceneMap({
    apps: () => (
      <AppsSelectionScene
        data={installedApps}
        toggleSiren={toggleSiren}
        isSirenSelected={isSirenSelected}
      />
    ),
    websites: () => (
      <TextInputSelectionScene
        onSubmitEditing={(event) =>
          setWebsites([...websites, event.nativeEvent.text])
        }
        sirenType={SirenType.WEBSITES}
        placeholder={'Add websites...'}
        data={websites}
        toggleSiren={toggleSiren}
        isSirenSelected={isSirenSelected}
      />
    ),
    keywords: () => (
      <TextInputSelectionScene
        onSubmitEditing={(event) =>
          setKeywords([...keywords, event.nativeEvent.text])
        }
        sirenType={SirenType.KEYWORDS}
        placeholder={'Add keywords...'}
        data={keywords}
        toggleSiren={toggleSiren}
        isSirenSelected={isSirenSelected}
      />
    ),
  })

  return (
    <TiedSLinearBackground>
      <Text style={styles.title}>Name</Text>
      <TiedSBlurView>
        <TiedSTextInput
          placeholder="Blocklist name"
          onChangeText={(text) => setBlocklist({ ...blocklist, name: text })}
        />
      </TiedSBlurView>

      <TabView
        navigationState={{ index, routes }}
        renderScene={renderScene}
        lazy={false}
        onIndexChange={setIndex}
        initialLayout={{ width: Dimensions.get('window').width }}
        renderTabBar={(props: TabBarProps<Route>) => (
          <ChooseBlockTabBar {...props} />
        )}
      />

      <TiedSButton
        text={'Save Blocklist'}
        onPress={() => {
          dispatch(createBlocklist(blocklist))
          navigation.navigate(BlocklistsStackScreens.MAIN_BLOCKLIST)
        }}
      />
    </TiedSLinearBackground>
  )
}

const styles = StyleSheet.create({
  title: {
    fontWeight: T.font.weight.bold,
    color: T.color.text,
    fontFamily: T.font.family.primary,
    fontSize: T.size.small,
    marginTop: T.spacing.small,
    marginBottom: T.spacing.small,
  },
})

已尝试无效的方案

  • 设置TabView的lazy={false},避免切换标签时卸载TextInput组件;
  • 使用useEffect钩子管理TextInput的焦点状态,尝试在isFocused状态变化时聚焦组件。

内容的提问来源于stack exchange,提问作者A Mehmeto

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 08:50:55