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

React Native中状态更新时useEffect未触发的问题排查

问题

在React Native组件中使用useState()更新状态,希望状态设置完成后将详情保存到服务器,于是在useEffect()钩子中实现了该逻辑。状态设置有效,能在屏幕上看到对应值,但尽管已将note作为useEffect()依赖数组的依赖项,状态更新时该函数从未触发。

相关代码片段:

const [note, setNote] = useState('');
const dispatch = useDispatch();
useEffect(() => {
  if (note) {
    console.log('updating note...');
    dispatch(updateNote(props.client.id, note));
  }
}, [note]);

TextInput更新状态的代码(因在TabNavigator的screenOptions中传入组件,使用onChangeText()或onChange()会出现React失去焦点问题,故使用onBlur):

<TextInput
  key='note'
  style={{
    color: '#fff',
    fontSize: 16,
    width: '100%',
    textAlign: 'center',
  }}
  placeholder='Tap here to share something...'
  placeholderTextColor={styles.colors.textPlaceholder}
  maxLength={50}
  onBlur={(text) => setNote(text)}
  defaultValue={note || props?.client?.note?.value}
/>

将父组件内的CustomHeader函数传入TabNavigator的screenOptions的代码:

screenOptions={{
  headerShown: true,
  headerStyle: {
    backgroundColor: Colors.primary,
    elevation: 0,
    shadowOpacity: 0,
    shadowColor: 'transparent',
    height: 170,
  },
  key: 'patient-tab',
  headerShadowVisible: false,
  tabBarStyle: { backgroundColor: Colors.primary },
  headerTintColor: Colors.light,
  headerTitle: (props) => <CustomHeader {...props} />, // Passed in here
  tabBarActiveTintColor: Colors.light,
  tabBarInactiveTintColor: Colors.lightInactive,
  tabBarActiveBackgroundColor: Colors.primary,
  tabBarInactiveBackgroundColor: Colors.primary,
}}

完整代码:

export const ClientBottomTabNavigator = (props) => {
  const [note, setNote] = useState('');
  const dispatch = useDispatch();
  useEffect(() => {
    if (props.client.id && note) {
      console.log('updating note...');
      dispatch(updateNote(props.client.id, note));
    }
  }, [props.client.id, note]);

  const CustomHeader = () => {
    return (
      <View>
        <View style={{ width: '100%', flexDirection: 'row', justifyContent: 'space-between', marginBottom: 6 }}>
          <Feather
            name='chevron-left'
            size={24}
            onPress={() => props.navigation.goBack()}
            color={styles.colors.textInverse}
            style={{ justifySelf: 'flex-start', alignSelf: 'flex-start', width: '32%', marginBottom: 6 }}
          />
          <Text
            style={{
              color: '#fff',
              fontSize: 18,
              alignSelf: 'center',
              justifySelf: 'center',
              fontWeight: 'bold',
            }}
          >
            {props?.client?.firstName} {props?.client?.lastName}
          </Text>
          <Feather
            name='' // Leave this blank
            style={{ justifySelf: 'flex-end', alignSelf: 'flex-end', width: '32%' }}
          />
        </View>

        <View style={{ alignItems: 'center', marginBottom: 6 }}>
          <Text style={{ color: '#fff', fontSize: 18, marginBottom: 6 }}>
            {convertDiscipline(props?.client?.discipline)}
          </Text>
          <View>
            <TextInput
              key='note'
              style={{
                color: '#fff',
                fontSize: 16,
                width: '100%',
                textAlign: 'center',
              }}
              placeholder='Tap here to share something…'
              placeholderTextColor={styles.colors.textPlaceholder}
              maxLength={50}
              onBlur={(text) => setNote(text)}
              defaultValue={note || props?.client?.note?.value}
            />
          </View>
        </View>
      </View>
    );
  };

  const Tab = createBottomTabNavigator();
  return (
    <Tab.Navigator
      screenOptions={{
        headerShown: true,
        headerStyle: {
          backgroundColor: Colors.primary,
          elevation: 0,
          shadowOpacity: 0,
          shadowColor: 'transparent',
          height: 170,
        },
        key: 'client-tab',
        headerShadowVisible: false,
        tabBarStyle: { backgroundColor: Colors.primary },
        headerTintColor: Colors.light,
        headerTitle: (props) => <CustomHeader {...props} />, // Passed in here
        tabBarActiveTintColor: Colors.light,
        tabBarInactiveTintColor: Colors.lightInactive,
        tabBarActiveBackgroundColor: Colors.primary,
        tabBarInactiveBackgroundColor: Colors.primary,
      }}
      initialRouteName={'Visits'}
    >
      <Tab.Screen
        name='Visits'
        component={VisitsTab}
        options={{
          tabBarLabel: 'VISITS',
          tabBarIcon: ({ color, size }) => <FontAwesome name='street-view' color={color} size={size} />,
        }}
      />
      <Tab.Screen
        name='Chart'
        component={ChartTab}
        options={{
          tabBarLabel: 'CHARTS',
          tabBarIcon: ({ color, size }) => <FontAwesome name='id-badge' color={color} size={size} />,
        }}
      />
      <Tab.Screen
        name='Goals'
        component={GoalsTab}
        options={{
          tabBarLabel: 'EDIT GOALS',
          tabBarIcon: ({ color, size }) => <FontAwesome name='trophy' color={color} size={size} />,
        }}
      />
    </Tab.Navigator>
  );
};
解决方案

1. 修复onBlur参数错误

TextInput的onBlur回调接收的是FocusEvent对象,不是输入框的文本值。你直接把事件对象传给setNote,导致note变成了对象类型,而非预期的字符串,这会导致useEffect的逻辑异常。

修改代码,通过event.nativeEvent.text获取输入框文本:

onBlur={(event) => setNote(event.nativeEvent.text)}

2. 解决CustomHeader重复创建导致的状态隔离

你在ClientBottomTabNavigator内部定义CustomHeader,每次父组件重渲染时,CustomHeader都会被重新定义。React会认为这是全新组件,导致setNote更新的是旧组件实例关联的状态,而useEffect监听的是新实例的状态,最终出现状态更新但useEffect不触发的情况。

有两种修复方式:

方式一:将CustomHeader提取到组件外部

// 提取到外部,通过props传递所需数据
const CustomHeader = ({ note, setNote, client, navigation }) => {
  return (
    <View>
      <View style={{ width: '100%', flexDirection: 'row', justifyContent: 'space-between', marginBottom: 6 }}>
        <Feather
          name='chevron-left'
          size={24}
          onPress={() => navigation.goBack()}
          color={styles.colors.textInverse}
          style={{ justifySelf: 'flex-start', alignSelf: 'flex-start', width: '32%', marginBottom: 6 }}
        />
        <Text
          style={{
            color: '#fff',
            fontSize: 18,
            alignSelf: 'center',
            justifySelf: 'center',
            fontWeight: 'bold',
          }}
        >
          {client?.firstName} {client?.lastName}
        </Text>
        <Feather
          name=''
          style={{ justifySelf: 'flex-end', alignSelf: 'flex-end', width: '32%' }}
        />
      </View>

      <View style={{ alignItems: 'center', marginBottom: 6 }}>
        <Text style={{ color: '#fff', fontSize: 18, marginBottom: 6 }}>
          {convertDiscipline(client?.discipline)}
        </Text>
        <View>
          <TextInput
            key='note'
            style={{
              color: '#fff',
              fontSize: 16,
              width: '100%',
              textAlign: 'center',
            }}
            placeholder='Tap here to share something…'
            placeholderTextColor={styles.colors.textPlaceholder}
            maxLength={50}
            onBlur={(event) => setNote(event.nativeEvent.text)}
            value={note || client?.note?.value}
          />
        </View>
      </View>
    </View>
  );
};

export const ClientBottomTabNavigator = (props) => {
  const [note, setNote] = useState('');
  const dispatch = useDispatch();
  useEffect(() => {
    if (props.client.id && note) {
      console.log('updating note...');
      dispatch(updateNote(props.client.id, note));
    }
  }, [props.client.id, note, dispatch]); // 补充dispatch到依赖数组,避免lint警告

  const Tab = createBottomTabNavigator();
  return (
    <Tab.Navigator
      screenOptions={{
        // 原有配置不变
        headerTitle: () => <CustomHeader 
          note={note} 
          setNote={setNote} 
          client={props.client} 
          navigation={props.navigation} 
        />,
      }}
      initialRouteName={'Visits'}
    >
      {/* 原有Tab.Screen配置不变 */}
    </Tab.Navigator>
  );
};

方式二:用useCallback缓存CustomHeader

export const ClientBottomTabNavigator = (props) => {
  const [note, setNote] = useState('');
  const dispatch = useDispatch();
  useEffect(() => {
    if (props.client.id && note) {
      console.log('updating note...');
      dispatch(updateNote(props.client.id, note));
    }
  }, [props.client.id, note, dispatch]);

  // 使用useCallback缓存组件,避免重复创建
  const CustomHeader = useCallback(() => {
    return (
      <View>
        {/* 原有代码不变,仅修正onBlur参数 */}
        <TextInput
          key='note'
          style={{
            color: '#fff',
            fontSize: 16,
            width: '100%',
            textAlign: 'center',
          }}
          placeholder='Tap here to share something…'
          placeholderTextColor={styles.colors.textPlaceholder}
          maxLength={50}
          onBlur={(event) => setNote(event.nativeEvent.text)}
          value={note || props?.client?.note?.value}
        />
      </View>
    );
  }, [note, props.client, props.navigation]); // 添加所有用到的依赖

  const Tab = createBottomTabNavigator();
  return (
    <Tab.Navigator
      screenOptions={{
        // 原有配置不变
        headerTitle: (headerProps) => <CustomHeader {...headerProps} />,
      }}
      initialRouteName={'Visits'}
    >
      {/* 原有Tab.Screen配置不变 */}
    </Tab.Navigator>
  );
};

3. 修正defaultValue为value

defaultValue仅在组件初始化时生效,状态更新后输入框不会同步显示新值。改用value属性实现双向绑定:

<TextInput
  // 其他属性不变
  value={note || props?.client?.note?.value}
  onBlur={(event) => setNote(event.nativeEvent.text)}
/>

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 14:30:42