javascript - 如果我在屏幕上来回移动,为什么 useState 的状态会突然恢复为 false?

标签 javascript reactjs react-native react-navigation

我一直在尝试切换最喜欢的产品并将其 idisFav 属性存储在 Firebase 数据库中。然后我使用它们在 FavoritesScreen 上显示收藏夹。

如果我转到ProductDetailsS​​creen(我在其中切换收藏夹),我可以毫无问题地切换真/假。

此外,如果我使用底部选项卡导航检查FavoritesScreenOrdersScreen等,然后返回ProductDetailsS​​creen ,没有任何改变。

但是如果(从ProductDetailsS​​creen)我返回(到ProductsOverviewScreen),然后再次返回ProductDetailsS​​creen code> isFav 的状态 弹回到假!尽管如此,idisFav 仍保存在 Firebase 上,但 isFav 保存为 false

注意:我使用 useState() Hook ...

当我尝试记录 isFav 时,又发生了一件我不明白的事情。 我有两个日志,一个在 toggleFavoriteHandler 内部,一个在外部。当我第一次运行toggleFavouriteHandler时,我也有setIsFav(prevState => !prevState);,我得到: 输出:

外部:假 内部:假 外部:true

所以我猜前两个 false 来自初始状态,然后 true 来自上面的状态切换。但为什么它只能在真实之外得到呢?为什么前两个实际上是假的?我在日志之前将状态更改为true。我希望它立即变为 true 并且让它们全部变为 true!

然后,如果我返回到 ProductsOverviewScreen,然后再次返回到 ProductDetailsS​​creen,我会从外部获得两个日志:

输出:

外部:true 外部:假

所以它会恢复到初始状态! ?

我真的不明白工作流程是如何进行的。这些日志正常吗?

任何人都可以给出一些提示来回错误可能在哪里吗?

谢谢!

这是代码:

ProductDetailsS​​creen.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 文档,

Think of setState() as a request rather than an immediate command to update the component. For better perceived performance, React may delay it, and then update several components in a single pass. React does not guarantee that the state changes are applied immediately.

setState() does not always immediately update the component. It may batch or defer the update until later.

https://reactjs.org/docs/react-component.html#setstate

关于javascript - 如果我在屏幕上来回移动,为什么 useState 的状态会突然恢复为 false?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58403990/

相关文章:

javascript - 您可以在 Internet Explorer 上捕获这些单选按钮事件吗?

javascript,无论您在哪个时区,如何获得相同的日期?

reactjs - 无法在react中获取material-ui SelectField的属性

react-native - 本地镜像未在发布和 TestFlight 中呈现

javascript - 如果两个数组具有相同的ownerId,如何比较它们

javascript - Flash - 将音频数据 ByteArray 传递给 javascript

javascript - 我正在尝试删除重复项并返回结果。我缺少什么?

java - ReactJS 客户端无法与 Java Web 服务通信

javascript - RefreshControl实现iOS和Android平台