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

React Native Hook状态在函数内不更新,列表长度判断导航异常

Why Your clickGo Function is Getting Old State & How to Fix It

Got it, let's break down what's happening here and how to fix that stale state issue in your clickGo function.

The Root Cause: Closure Trap + Stale Navigation Options

Your problem boils down to two key issues:

  1. Closure Capture: When you assign clickGo inside a useLayoutEffect that depends on customers, each new customers update creates a new clickGo function that captures the latest customers value. But the onPress function (and the ActionSheet callback inside it) isn't being updated when customers changes.
  2. Stale Header Right: The useLayoutEffect that sets your headerRight only runs when props.navigation changes. So even when customers updates, the header's onPress remains the old version—still referencing the initial clickGo function that holds the stale customers value.

Fix 1: Simplify by Moving Logic Directly to the ActionSheet Callback

The easiest fix is to cut out the clickGo middleman entirely and run your state check directly in the ActionSheet's button callback. Just make sure to update your header options whenever customers changes so the onPress function uses the latest state.

Here's the adjusted code:

export default function CustomersList(props) {
  const userId = useSelector(state => state.loggedReducer.userId);
  const customers = useSelector(state => state.customers.customers);
  const dispatch = useDispatch();
  const [refreshing, setRefreshing] = React.useState(false);

  const onRefresh = React.useCallback(() => {
    dispatch(fetchCustomers(userId));
  }, [userId, dispatch]); // Added missing dependencies here too!

  /**
   * Add Right icon on header
   * */
  React.useLayoutEffect(() => {
    props.navigation.setOptions({
      headerRight: () => (
        <Ionicons 
          name="ios-more" 
          size={24} 
          color="white" 
          onPress={onPress} 
          style={{marginRight: 20}}
        />
      ),
    });
  }, [props.navigation, customers]); // Added customers to dependencies!

  const onPress = () => {
    console.log("rendered");
    ActionSheetIOS.showActionSheetWithOptions(
      { options: ["Cancel", "go"], cancelButtonIndex: 0 },
      buttonIndex => {
        switch (buttonIndex) {
          case 0: // Cancel Action
            break;
          case 1: // Go
            // Use the latest customers directly here
            console.log("state", customers.length);
            if(customers.length < 10){
              props.navigation.navigate("NewCustomer");
            }else{
              alert("upgrade to premium")
            }
            break;
        }
      }
    );
  };

  // ... rest of your component code
}

Key changes:

  • Added customers to the useLayoutEffect dependency array so the header's onPress gets updated with the latest state whenever customers changes.
  • Removed the clickGo variable and moved its logic directly into the ActionSheet's button callback, where it uses the latest customers value from the component's render scope.
  • Fixed the onRefresh callback's missing dependencies (userId and dispatch) to avoid potential stale values there too.

Fix 2: Use a Ref to Track the Latest State (For More Complex Logic)

If you need to keep clickGo as a separate function (e.g., for reusability), use a React ref to store the latest customers value. Refs bypass closure issues because their .current property is mutable and always holds the latest value.

Here's how to implement this:

export default function CustomersList(props) {
  const userId = useSelector(state => state.loggedReducer.userId);
  const customers = useSelector(state => state.customers.customers);
  const dispatch = useDispatch();
  const [refreshing, setRefreshing] = React.useState(false);
  
  // Create a ref to hold the latest customers value
  const customersRef = React.useRef(customers);

  const onRefresh = React.useCallback(() => {
    dispatch(fetchCustomers(userId));
  }, [userId, dispatch]);

  // Sync the ref with the latest customers whenever it updates
  React.useLayoutEffect(() => {
    customersRef.current = customers;
  }, [customers]);

  // clickGo now uses the ref to get the latest state
  const clickGo = React.useCallback(() => {
    const currentCustomerCount = customersRef.current.length;
    console.log("state", currentCustomerCount);
    if(currentCustomerCount < 10){
      props.navigation.navigate("NewCustomer");
    }else{
      alert("upgrade to premium")
    }
  }, []); // No dependencies needed since we're using the ref

  React.useLayoutEffect(() => {
    props.navigation.setOptions({
      headerRight: () => (
        <Ionicons 
          name="ios-more" 
          size={24} 
          color="white" 
          onPress={onPress} 
          style={{marginRight: 20}}
        />
      ),
    });
  }, [props.navigation, clickGo]); // Add clickGo to dependencies if using useCallback

  const onPress = () => {
    console.log("rendered");
    ActionSheetIOS.showActionSheetWithOptions(
      { options: ["Cancel", "go"], cancelButtonIndex: 0 },
      buttonIndex => {
        switch (buttonIndex) {
          case 0:
            break;
          case 1:
            clickGo();
            break;
        }
      }
    );
  };

  // ... rest of your component code
}

This works because:

  • The customersRef is updated every time customers changes, so .current always has the latest state.
  • The clickGo function uses customersRef.current instead of relying on closure-captured state, so it always sees the latest value—even if clickGo itself doesn't re-render.

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.08 20:47:55