React Native Hook状态在函数内不更新,列表长度判断导航异常
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:
- Closure Capture: When you assign
clickGoinside auseLayoutEffectthat depends oncustomers, each newcustomersupdate creates a newclickGofunction that captures the latestcustomersvalue. But theonPressfunction (and the ActionSheet callback inside it) isn't being updated whencustomerschanges. - Stale Header Right: The
useLayoutEffectthat sets yourheaderRightonly runs whenprops.navigationchanges. So even whencustomersupdates, the header'sonPressremains the old version—still referencing the initialclickGofunction that holds the stalecustomersvalue.
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
customersto theuseLayoutEffectdependency array so the header'sonPressgets updated with the latest state whenevercustomerschanges. - Removed the
clickGovariable and moved its logic directly into the ActionSheet's button callback, where it uses the latestcustomersvalue from the component's render scope. - Fixed the
onRefreshcallback's missing dependencies (userIdanddispatch) 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
customersRefis updated every timecustomerschanges, so.currentalways has the latest state. - The
clickGofunction usescustomersRef.currentinstead of relying on closure-captured state, so it always sees the latest value—even ifclickGoitself doesn't re-render.
内容的提问来源于stack exchange,提问作者Matteo Bolognesi

