FlatList 中新创建的 TextInputs 在第 10 行后失去焦点 - initialNumToRender

Newly created TextInputs in FlatList lose focus after 10th row - initialNumToRender

我正在尝试为应用程序构建待办事项列表功能;但是,我正在努力做到正确。

基本上,我希望用户能够在键入时通过“提交”键或按钮(均已完成)创建新的列表项。创建新列表项时,我希望 FlatList 中相应的 TextInput 自动获得焦点。这在一定程度上是有效的。创建 10 个列表项后,该行为将停止正常工作,并且焦点不再位于下一​​个新创建的 FlatList 项上。

我发现如果我更改 FlatList 中的“initialNumToRender”属性 并将其增加到 10 以上,它将暂时解决问题,直到达到该上限。但是,我不想降低我的 FlatList 的性能,我想找到一个不同的解决方案。在我看来,整个 FlatList 正在重新呈现过去的 10 个项目并且 TextInput 失去焦点,但我还无法掌握如何关注第 10 行之后创建的 TextInputs。

有问题的 FlatList:

      <FlatList
        key="flatlist"
        ListHeaderComponent={listHeader()}
        initialNumToRender={10}
        data={listItems}
        // TODO: Will need to change to use actual IDs at some point as opposed to just indexing
        renderItem={({item, index}) => (
          <>
            <View style={listStyles.itemView}>
              <Pressable onPress={() => radioButtonPressed(index)}>
                <MainListRadioButton completed={getRadioButtonState(index)} />
              </Pressable>
              {listItems[index].text.length > 0 ? (
                <TextInput
                  style={listStyles.itemText}
                  placeholder={`Item ${index + 1}`}
                  defaultValue={listItems[index].text}
                  value={listItems[index].text}
                  onChangeText={text => listItemTextChanged(text, index)}
                  ref={index === listItems.length - 1 ? lastListItemRef : null}
                  onSubmitEditing={() => addListItem()}
                />
              ) : (
                // Text input for "empty" list items so that a backspace can delete the entire row.
                <TextInput
                  style={listStyles.itemText}
                  placeholder={`Item ${index + 1}`}
                  defaultValue={listItems[index].text}
                  value={listItems[index].text}
                  onChangeText={text => listItemTextChanged(text, index)}
                  ref={index === listItems.length - 1 ? lastListItemRef : null}
                  onKeyPress={({nativeEvent}) => {
                    if (nativeEvent.key === 'Backspace') {
                      deleteListItem(index);
                    }
                  }}
                />
              )}
            </View>
            <View style={styles.divider} />
          </>
        )}
      />

到目前为止,我是如何实现将焦点转移到下一行的行为的:

  useEffect(() => {
    if (lastListItemRef.current) {
      lastListItemRef.current.focus();
    }
  }, [listItems]);

您可能最好使用 scrollToIndex() along with useLayoutEffect,因为您希望在视觉变化时触发滚动。

useLayoutEffect(() => {
  flatlistRef.current?.scrollToIndex(listItems.length - 1)
}, [flatlistRef, listItems])

与其在父级别控制 TextInput 焦点,不如将 listItem 函数转换为 ListItem 组件。通过这样做,您可以将引用存储到每个组件实例中的每个新 ListItem 组件,并在每个组件的挂载上聚焦它。

所以你的 DraggableFlatList 声明变成这样:

<DraggableFlatList
  ...
  renderItem={({ item, index, drag, isActive }) => (
    <ListItem
      item={item}
      index={index}
      drag={drag}
      isActive={isActive}
      addListItem={addListItem}
      deleteListItem={deleteListItem}
      completed={getRadioButtonState(index)}
      text={listItems[index].text}
      onChange={listItemTextChanged}
      radioButtonPressed={radioButtonPressed}
    />
  )}
  ...
/>;

您的 ListItem 组件定义如下所示:

const ListItem = ({
  item,
  index,
  drag,
  isActive,
  text,
  completed,
  onChange,
  radioButtonPressed,
  addListItem,
  deleteListItem,
} = props) => {
  // Create ref to this TextInput
  const textRef = useRef(null);

  // On mount, set the TextInput focus
  useEffect(() => {
    if (textRef.current) {
      textRef.current.focus();
    }
  }, []);

  return (
    <>
      <Pressable style={listStyles.itemView} onLongPress={drag}>
        <Pressable onPress={() => radioButtonPressed(index)}>
          <MainListRadioButton completed={completed} />
        </Pressable>
        <TextInput
          ref={textRef}
          style={listStyles.itemText}
          placeholder={`Item ${index + 1}`}
          defaultValue={text}
          value={text}
          onChangeText={(text) => onChange(text, index)}
          onSubmitEditing={() => addListItem()}
          onKeyPress={({ nativeEvent }) => {
            if (nativeEvent.key === 'Backspace') {
              deleteListItem(index);
            }
          }}
        />
      </Pressable>
      <View style={listStyles.divider} />
    </>
  );
};

我已将您的零食更新为 POC:https://snack.expo.dev/TZk_48CHF

In this case you have to use the flatlist scrollToIndex function along with viewPosition property properly for the selected changes.Also you have to use the onScrollToIndexFailed prop in flatlist along with setTimeout.

viewPosition: 0.5 ---> put the selected item in middle,0 and 1 are other values

const const scrollToIndex = (ref, index = 0) => {
  if (ref && ref.current && index > -1) {
    ref.current.scrollToIndex({ index, viewPosition: 0.5 });
  }
};

const flatListRef = useRef();

useEffect(() => {
    const selectedIndex = getSelectedIndex();
    setTimeout(() => {
      scrollToIndex(flatListRef, selectedIndex);
    }, 50);
  }, [title, flatListRef]);

<FlatList
        ref={flatListRef}
        initialNumToRender={categoryFilters.length + 1}
        onScrollToIndexFailed={info => {
          if (flatListRef !== null && info.index > -1) {
            setTimeout(
              () =>
                flatListRef.current.scrollToIndex({
                  index: info.index,
                  animated: true,
                  viewPosition: 0.5,
                }),
              50
            );
          }
        }}
        refreshing={false}
        data={listItems}
        listKey={(_, index) => index.toString()}
        renderItem={()=>{}}
      />

您需要知道 listItems 是否不是空数组,因此您需要创建此函数 Frechet() 来评估它。

useEffect(() => {
   const goToFocus=await ()=>{
     if (lastListItemRef.current) {
      lastListItemRef.current.focus();
    }
   }
    
   if(isFechet(listItems)){
     goToFocus()
}
  }, [listItems]);

谢谢大家。我实际上最终自己弄清楚了。我采取的步骤如下:

  1. 完全删除 TextInput 和组件中的 setRef 和 ref。

  2. 将 autoFocus=true 作为 属性 添加到 TextInput。以前我有一个错字,没有把 F 大写。

  3. 将 onTextChange 更改为 onEndEditing,这样列表项仅在提交文本后更新,而不是在每次文本更改时更新。