React Native修改state未立即更新,切换国家时城市下拉框数据滞后如何解决?
问题根因分析
- 城市下拉列表延迟更新:React 中
setState为异步更新,你在onChange回调中先执行updateCityPicker,此时state.country还未被更新为最新选中的国家,过滤城市时使用的还是上一次的旧值,因此每次更新的都是上一次选中国家对应的城市列表,需要两次切换才能看到正确结果。 - 跨组件setState告警:异步函数
getUserAddressDetails、updateUser的then回调执行时,可能组件已经被卸载,此时仍然调用setState就会触发该告警;同时空依赖useEffect中引用的state是初始闭包值,也会导致状态更新异常。
修复代码
[imports all in place] const countries2 = require('../../common/constants/countries2.json'); const cities = require('../../common/constants/cities.json'); const validator = { city: { required: true, string: true, }, country: { required: true, string: true, }, }; const RegistrationAddress = ({ route }) => { const navigation = useNavigation(); const { personalDetails, socialRegistration } = route.params || {}; const [updateUser] = useMutation(UPDATE_PROFILE, { fetchPolicy: 'no-cache', }); const [state, setState] = useState({ city: '', country: '', personalDetails: personalDetails, }); const [errors, setErrors] = useState({}); const [filteredCities, setCities] = useState([]); // 监听country变化自动更新城市列表 useEffect(() => { const currentCountry = state.country; if (!currentCountry) { setCities([]); return; } const suggestions = cities.filter((el) => el[currentCountry]); const list = []; suggestions.forEach((element) => { element[currentCountry].forEach((cl) => { list.push({ label: cl, value: cl }); }); }); setCities(list); // 切换国家后清空已选城市,避免状态不一致 setState(prev => ({...prev, city: ''})); }, [state.country]); // 新增挂载状态标记,避免组件卸载后更新state触发告警 useEffect(() => { let isMounted = true; const err = validate({}, validator); if (isMounted) setErrors(err); getUserAddressDetails(isMounted); return () => { isMounted = false; } }, []); const onChange = (field, val) => { const newState = { ...state, [field]: val }; setState(newState); const err = validate(newState, validator); if (err) { setErrors(err); } else { setErrors({}); } }; const getUserAddressDetails = async (isMounted) => { const userAddressDetail = await AsyncStorage.getItem('userAddressDetails'); const userAddressDetails = JSON.parse(userAddressDetail) || {}; // 改用函数式更新,避免闭包读取旧state的问题 setState(prev => ({ ...prev, city: userAddressDetails.city || '', country: userAddressDetails.country || '', })); if (!isMounted) return; const err = validate({ city: userAddressDetails.city || '', country: userAddressDetails.country || '', }, validator); if (err) { setErrors(err); } else { setErrors({}); } }; const { city, country } = state; const navigateToRegistrationConfirmDetails = () => { if (socialRegistration) { updateUser({ variables: { data: { city, country, }, }, }).then(async () => { await AsyncStorage.removeItem('userBasicDetails'); await AsyncStorage.removeItem('userAddressDetails'); navigation.navigate('QuestionnaireIntroduction'); }).catch(err => console.log({err})); } else { navigation.navigate('RegistrationConfirmDetails', { userDetails: state }); } }; return ( <ImageBackground style={styles.imageBackground}> <View style={styles.regStepView}> <Text style={styles.regStep}> Address <Text style={styles.oneOutOfThree}> - 3/3</Text> </Text> </View> <ScrollView showsVerticalScrollIndicator={false}> <KeyboardAvoidingView style={styles.inputsView}> <Dropdown mainContainerStyle={{ width: '100%', backgroundColor: 'white', marginTop: 5, }} textStyle={{ fontSize: verticalScale(14) }} value={country} onValueChange={(val) => onChange('country', val)} testID="countryID" placeholder="eg. United Kingdom" items={countries2} checkDropdownErrors={false} error={errors.accountCurrency && errors.accountCurrency[0]} showDropdownError={''} title="Which country do you currently live in?" /> <Dropdown mainContainerStyle={{ width: '100%', backgroundColor: 'white', marginTop: 5, }} textStyle={{ fontSize: verticalScale(14) }} value={city} onValueChange={(val) => onChange('city', val)} testID="cityID" placeholder="eg. London" items={filteredCities} checkDropdownErrors={false} error={errors.city && errors.city[0]} showDropdownError={''} title="Which city do you currently live in?" /> </KeyboardAvoidingView> </ScrollView> <View> <Button disabled={Object.keys(errors).length} styleContainer={{ marginBottom: scale(24) }} title="Next" onPressFunc={async () => { await AsyncStorage.setItem( 'userAddressDetails', JSON.stringify(state), ) .then(() => navigateToRegistrationConfirmDetails()) .catch((err) => console.log({ err })); }} /> </View> </ImageBackground> ); }; export default RegistrationAddress;
内容的提问来源于stack exchange,提问作者jonlastthelast
相关产品推荐
相关产品推荐

