javascript - 如果我在屏幕上来回移动,为什么 useState 的状态会恢复为 false?
问题描述
我一直在尝试切换最喜欢的产品并将它们id
和isFav
道具存储在 Firebase 数据库中。然后我用它们在FavoritesScreen
.
如果我去ProductDetailsScreen
(我切换收藏夹的地方),我可以毫无问题地切换它的真/假。
此外,如果我然后使用Bottom Tab Navigation
来检查FavoritesScreen
orOrdersScreen
等然后返回ProductDetailsScreen
,则没有任何改变。
但是如果(从ProductDetailsScreen
)我回到(到ProductsOverviewScreen
)然后再回到快照ProductDetailsScreen
的状态isFav
回到假!不过id
andisFav
保存在 Firebase 上,但isFav
保存为false
.
注意:我使用 useState() 钩子...
当我尝试登录时,又发生了一件我不明白的事情isFav
。我有两根原木,一根在里面,toggleFavoriteHandler
一根在外面。当我第一次运行时toggleFavoriteHandler
,我也setIsFav(prevState => !prevState);
得到了: 输出:
outside: false
inside: false
outside: true
所以我猜前两个 false 来自初始状态,然后 true 来自上述状态切换。但为什么它只在外面得到它是真的?为什么实际上前两个是假的?我在日志之前将状态更改为 true。我希望它立即变为 true 并让它们全部为 true!
然后,如果我回到ProductsOverviewScreen
然后再回到ProductDetailsScreen
我从外面得到两个日志:
输出:
outside: true
outside: false
所以它会恢复到初始状态!?
我真的不明白工作流程是如何进行的。这些日志正常吗?
任何人都可以提供一些提示来回的错误可能在哪里,好吗?
谢谢!
这是代码:
ProductDetailsScreen.js
...
const ProductDetailScreen = (props) => {
const [ isFav, setIsFav ] = useState(false);
const dispatch = useDispatch();
const productId = props.navigation.getParam('productId');
const selectedProduct = useSelector((state) =>
state.products.availableProducts.find((prod) => prod.id === productId)
);
const toggleFavoriteHandler = useCallback(
async () => {
setError(null);
setIsFav((prevState) => !prevState);
console.log('isFav inside:', isFav); // On first click I get: false
try {
await dispatch(
productsActions.toggleFavorite(
productId,
isFav,
)
);
} catch (err) {
setError(err.message);
}
},
[ dispatch, productId, isFav setIsFav ]
);
console.log('isFav outside: ', isFav); // On first click I get: false true
return (
<ScrollView>
<View style={styles.icon}>
<TouchableOpacity style={styles.itemData} onPress={toggleFavoriteHandler}>
<MaterialIcons name={isFav ? 'favorite' : 'favorite-border'} size={23} color="red" />
</TouchableOpacity>
</View>
<Image style={styles.image} source={{ uri: selectedProduct.imageUrl }} />
{Platform.OS === 'android' ? (
<View style={styles.button}>
<CustomButton
title="Add to Cart"
onPress={() => dispatch(cartActions.addToCard(selectedProduct))}
/>
</View>
) : (
<View style={styles.button}>
<Button
color={Colours.gr_brown_light}
title="Add to Cart"
onPress={() => dispatch(cartActions.addToCard(selectedProduct))}
/>
</View>
)}
<Text style={styles.price}>€ {selectedProduct.price.toFixed(2)}</Text>
<Text style={styles.description}>{selectedProduct.description}</Text>
</ScrollView>
);
};
ProductDetailScreen.navigationOptions = ({ navigation }) => {
return {
headerTitle: navigation.getParam('productTitle'),
headerLeft: (
<HeaderButtons HeaderButtonComponent={CustomHeaderButton}>
<Item
title="goBack"
iconName={Platform.OS === 'android' ? 'md-arrow-back' : 'ios-arrow-back'}
onPress={() => navigation.goBack()}
/>
</HeaderButtons>
),
headerRight: (
<HeaderButtons HeaderButtonComponent={CustomHeaderButton}>
<Item
title="cart"
iconName={Platform.OS === 'android' ? 'md-cart' : 'ios-cart'}
onPress={() => navigation.navigate({ routeName: 'Cart' })}
/>
</HeaderButtons>
)
};
};
...styles
products.js/actions
export const toggleFavorite = (id, isFav) => {
return async (dispatch) => {
try {
// If it is a favorite, post it.
// Note it is initially false...
if (!isFav) {
const response = await fetch('https://ekthesi-7767c.firebaseio.com/favorites.json', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
id,
isFav
})
});
if (!response.ok) {
throw new Error(
'Something went wrong.'
);
}
const resData = await response.json();
// Note: No `name` property, that's why we use a `for_in` loop
// console.log('POST', JSON.stringify(resData));
dispatch({ type: TOGGLE_FAVORITE, productId: id });
} else if (isFav) {
// First get the key in order to delete it in second fetch(...).
const response = await fetch(`https://ekthesi-7767c.firebaseio.com/favorites.json`);
if (!response.ok) {
throw new Error(
'Something went wrong.'
);
}
const resData = await response.json();
// Note: No `name` property, that's why we use a `for_in` loop
// console.log('fetch', JSON.stringify(resData));
for (const key in resData) {
console.log('resData[key].id', resData[key].id === id);
if (resData[key].id === id) {
await fetch(`https:app.firebaseio.com/favorites/${key}.json`, {
method: 'DELETE'
});
if (!response.ok) {
throw new Error(
'Something went wrong.'
);
}
// console.log('fetch', JSON.stringify(resData));
dispatch({ type: TOGGLE_FAVORITE, productId: id });
}
}
}
} catch (err) {
// send to custom analytics server
throw err;
}
};
};
ProductsOverviewScreen.js
...
const ProductsOverviewScreen = (props) => {
const [ isLoading, setIsLoading ] = useState(false);
const [ error, setError ] = useState(); // error initially is undefined!
const [ isRefresing, setIsRefresing ] = useState(false);
const dispatch = useDispatch();
const categoryId = props.navigation.getParam('categoryId');
const products = useSelector((state) =>
state.products.availableProducts.filter((prod) => prod.categoryIds.indexOf(categoryId) >= 0)
);
const productId = props.navigation.getParam('productId');
const isFav = useSelector((state) => state.products.favoriteProducts.some((product) => product.id === productId));
const loadProducts = useCallback(
async () => {
setError(null);
setIsRefresing(true);
try {
await dispatch(productsActions.fetchProducts());
} catch (err) {
setError(err.message);
}
setIsRefresing(false);
},
[ dispatch, setIsLoading, setError ]
);
// loadProducts after focusing
useEffect(
() => {
const willFocusEvent = props.navigation.addListener('willFocus', loadProducts);
return () => willFocusEvent.remove();
},
[ loadProducts ]
);
// loadProducts initially...
useEffect(
() => {
setIsLoading(true);
loadProducts();
setIsLoading(false);
},
[ dispatch, loadProducts ]
);
const selectItemHandler = (id, title) => {
props.navigation.navigate('DetailScreen', {
productId: id,
productTitle: title,
isFav: isFav
});
};
if (error) {
return (
<View style={styles.centered}>
<Text>Something went wrong!</Text>
<Button title="Try again" onPress={loadProducts} color={Colours.chocolate} />
</View>
);
}
if (isLoading) {
return (
<View style={styles.centered}>
<ActivityIndicator size="large" color={Colours.chocolate} />
</View>
);
}
if (!isLoading && products.length === 0) {
return (
<View style={styles.centered}>
<Text>No products yet!</Text>
</View>
);
}
return (
<FlatList
onRefresh={loadProducts}
refreshing={isRefresing}
data={products}
keyExtractor={(item) => item.id}
renderItem={(itemData) => (
<ProductItem
title={itemData.item.title}
image={itemData.item.imageUrl}
onSelect={() => selectItemHandler(itemData.item.id, itemData.item.title)}
>
{Platform.OS === 'android' ? (
<View style={styles.actions}>
<View>
<CustomButton
title="Details"
onPress={() => selectItemHandler(itemData.item.id, itemData.item.title)}
/>
</View>
<BoldText style={styles.price}>€ {itemData.item.price.toFixed(2)}</BoldText>
<View>
<CustomButton
title="Add to Cart"
onPress={() => dispatch(cartActions.addToCard(itemData.item))}
/>
</View>
</View>
) : (
<View style={styles.actions}>
<View style={styles.button}>
<Button
color={Colours.gr_brown_light}
title="Details"
onPress={() => selectItemHandler(itemData.item.id, itemData.item.title)}
/>
</View>
<BoldText style={styles.price}>€ {itemData.item.price.toFixed(2)}</BoldText>
<View style={styles.button}>
<Button
color={Colours.gr_brown_light}
title="Add to Cart"
onPress={() => dispatch(cartActions.addToCard(itemData.item))}
/>
</View>
</View>
)}
</ProductItem>
)}
/>
);
};
ProductsOverviewScreen.navigationOptions = (navData) => {
return {
headerTitle: navData.navigation.getParam('categoryTitle'),
headerRight: (
<HeaderButtons HeaderButtonComponent={CustomHeaderButton}>
<Item
title="cart"
iconName={Platform.OS === 'android' ? 'md-cart' : 'ios-cart'}
onPress={() => navData.navigation.navigate({ routeName: 'Cart' })}
/>
</HeaderButtons>
)
};
};
...styles
解决方案
状态更新不是同步的。考虑到以下几点:
const [isFav, setIsFav] = React.useState(true);
setIsFav(false); // state update here
console.log(isFav); // isFav hasn't updated yet and won't be `false` until next render
要获取最新状态,您需要将您的登录信息输入useEffect
/ useLayoutEffect
。
从 React 文档中,
将 setState() 视为更新组件的请求而不是立即命令。为了更好地感知性能,React 可能会延迟它,然后一次更新多个组件。React 不保证立即应用状态更改。
setState() 并不总是立即更新组件。它可能会批量更新或将更新推迟到以后。
推荐阅读
- c# - 是否可以将 winforms 状态条和进度条转换为 wpf 状态栏?
- mysql - Mysql查询以获取第一个位置的特定ID
- ruby - IRB 要求 mongo 失败无法加载此类文件——mongo
- oop - 如何在 Dart 中实现继承?
- xml - 转换文件根据子节点的文本值替换整个父节点 - XML Transform
- vb.net - 如何在 DatagridView Checkbox vb.net 中保持选中复选框
- rollup - 如何使用 Rollup 插件挂钩的返回承诺?
- api - 在 .net 核心 web api 中存储 JWT 令牌的位置?
- ios - React Native iOS 空白白屏和 EXC_BAD_ACCESS (code=EXC_I386_GPFLT)
- typescript - 在响应标头中设置 cookie,但未在浏览器中设置