繁体   English   中英

如果我在屏幕上来回走动 go,为什么 useState 的 state 会突然恢复为 false?

[英]Why the state of a useState snaps back to false if I go back and forth on the Screen?

我一直在尝试切换最喜欢的产品并将它们的idisFav道具存储在 Firebase 数据库中。 然后我使用它们在FavoritesScreen

如果我 go 到ProductDetailsScreen creen (我在其中切换收藏夹),我将它切换为 true/false 没有问题。

此外,如果我然后使用Bottom Tab Navigation检查FavoritesScreenOrdersScreen等,然后 go 返回ProductDetailsScreen creen ,则没有任何改变。

但是,如果(从ProductDetailsScreen creen )我 go 返回(到ProductsOverviewScreen )然后再次在ProductDetailsScreen creen 上返回,则 isFav 的isFav突然恢复为假! 然而idisFav保存在 Firebase 上,但isFav保存为false

注意:我使用 useState() 钩子...

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

outside: false inside: false outside: true

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

然后,如果我 go 回到ProductsOverviewScreen然后再回到ProductDetailsScreen creen 我从外面得到两个日志:

Output:

outside: true outside: false

所以它会恢复到最初的 state? ?

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

任何人都可以提供一些提示来回的错误可能在哪里,好吗?

谢谢!

这是代码:

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

State 更新不是同步的。 考虑到以下几点:

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

要获得最新的 state,您需要将您的登录信息放入useEffect / useLayoutEffect

从 React 文档中,

将 setState() 视为更新组件的请求而不是立即命令。 为了更好地感知性能,React 可能会延迟它,然后一次更新多个组件。 React 不保证立即应用 state 更改。

setState() 并不总是立即更新组件。 它可能会批量更新或将更新推迟到以后。

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

在@satya 发表评论后,我又试了一次。 现在我从 redux state 得到 isFav 的 state。 即,我检查当前产品是否在 favoriteProducts 数组中。

...imports

const ProductDetailScreen = (props) => {
    const [ error, setError ] = useState(); // error initially is undefined!

    const dispatch = useDispatch();

    const productId = props.navigation.getParam('productId');
    const selectedProduct = useSelector((state) =>
        state.products.availableProducts.find((prod) => prod.id === productId)
    );
// HERE !!! I get to see if current product is favorite!
    const currentProductIsFavorite = useSelector((state) => state.products.favoriteProducts.some((product) => product.id === productId));


    const toggleFavoriteHandler = useCallback(
        async () => {
            setError(null);
            try {
                await dispatch(productsActions.toggleFavorite(productId, currentProductIsFavorite));
            } catch (err) {
                setError(err.message);
            }
        },
        [ dispatch, productId, currentProductIsFavorite, setIsFav ]
    );

    ...

    return (
        <ScrollView>
            <View style={styles.icon}>
                <TouchableOpacity style={styles.itemData} onPress={toggleFavoriteHandler}>
                    <MaterialIcons name={currentProductIsFavorite ? 'favorite' : 'favorite-border'} size={23} color="red" />
                </TouchableOpacity>
            </View>
            <Image style={styles.image} source={{ uri: selectedProduct.imageUrl }} />
    ...

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM