I got some trouble with the MainPage component, because the BestSellers component always re-renders whenever the ProductList re-renders. I use 2 different states and actions for them.
Here my MainPage:
const LargeProductCard = ({ product }) => {
let truncatedTitle =
product.name.length > 26
? `${product.name.substring(0, 23)}...`
: product.name;
const navigation = useNavigation();
return (
<TouchableOpacity
style={styles.largeCard}
onPress={() => navigation.navigate('Product', { id: product._id })}
>
<Text style={styles.productName}>{truncatedTitle}</Text>
<Image
source={{ uri: product.images[0].url }}
style={styles.productImage}
/>
<Text style={styles.buyNow}>BUY NOW!</Text>
</TouchableOpacity>
);
};
const BestSellers = () => {
const dispatch = useDispatch();
const [currentBestSellerIndex, setCurrentBestSellerIndex] = useState(0);
const { bestSellers, error, loading } = useSelector(
(state) => state.bestSellers
);
useEffect(() => {
if (error) {
ToastAndroid.show(error.message, ToastAndroid.LONG)
}
const fetch = async () => {
if (bestSellers) return;
await dispatch(getBestSellers());
};
fetch();
}, [bestSellers]);
useEffect(() => {
const interval = setInterval(() => {
setCurrentBestSellerIndex((prevIndex) => (prevIndex + 1) % 5);
}, 5000);
return () => clearInterval(interval);
});
if (loading || bestSellers === undefined) {
return <ActivityIndicator size="large"></ActivityIndicator>;
}
return (
<View>
{bestSellers && bestSellers.length > 0 && (
<LargeProductCard product={bestSellers[currentBestSellerIndex]} />
)}
</View>
);
};
const ProductList = () => {
const dispatch = useDispatch();
const [currentPage, setCurrentPage] = useState(1);
const [loadingMore, setLoadingMore] = useState(false);
const [showList, setShowList] = useState([]);
const { products, error, productsFounded } = useSelector(
(state) => state.products
);
useEffect(() => {
if (error) {
ToastAndroid.show(error.message, ToastAndroid.LONG);
dispatch(clearErrors());
}
dispatch(getProducts('', 1, [0, 9999999], '', 0));
}, []);
useEffect(() => {
if (productsFounded || productsFounded > 0) {
const newProducts = products.filter(
(product) => !showList.find((item) => item._id === product._id)
);
setShowList([...showList, ...newProducts]);
}
}, [products]);
const fetchMoreProducts = async () => {
if (loadingMore || currentPage >= Math.ceil(productsFounded / 4)) {
return;
}
setLoadingMore(true);
const nextPage = currentPage + 1;
await dispatch(getProducts('', nextPage, [0, 9999999], '', 0));
setCurrentPage(nextPage);
setLoadingMore(false);
};
return (
<FlatList
data={showList}
renderItem={({ item }) => <SmallProductCard product={item} />}
keyExtractor={(item) => item._id}
onEndReached={fetchMoreProducts}
onEndReachedThreshold={0.1}
ListFooterComponent={loadingMore ? <Text>Loading...</Text> : null}
numColumns={2}
contentContainerStyle={styles.flatListContentContainer}
/>
);
};
const MainPage = () => {
const bestSeller = useMemo(() => <BestSellers />, []);
return (
<View style={styles.content}>
<Text style={styles.bestSeller}>Best Seller</Text>
<BestSellers />
<Text style={styles.Title}>Our Products</Text>
<ProductList />
</View>
);
};
Reducers for them:
export const productReducer = (state = { products: [] }, action) => {
switch (action.type) {
case ADMIN_PRODUCTS_REQUEST:
case ALL_PRODUCTS_REQUEST:
return {
loading: true,
products: [],
};
case ALL_PRODUCTS_SUCCESS:
return {
loading: false,
products: action.payload.products,
totalProduct: action.payload.totalProduct,
resPerPage: action.payload.resPerPage,
productsFounded: action.payload.productsFounded,
bestSellers: action.payload.bestSellers,
};
case ADMIN_PRODUCTS_SUCCESS:
return {
loading: false,
products: action.payload,
};
case ADMIN_PRODUCTS_FAILED:
case ALL_PRODUCTS_FAILED:
return {
loading: false,
error: action.payload,
};
case CLEAR_ERRORS:
return {
...state,
error: null,
};
default:
return state;
}
};
export const bestSellersReducer = (state = { bestSellers: [] }, action) => {
switch (action.type) {
case BEST_SELLERS_REQUEST:
return {
loading: true,
};
case BEST_SELLERS_SUCCESS:
return {
loading: false,
bestSellers: action.payload.bestSellers,
};
case BEST_SELLERS_FAILED:
return {
loading: false,
error: action.payload,
};
case CLEAR_ERRORS:
return {
...state,
error: null,
};
default:
return state;
}
};
Actions:
export const getProducts =
(keywords, currentPage = 1, price, category, ratings) =>
async (dispatch) => {
try {
dispatch({
type: ALL_PRODUCTS_REQUEST,
});
let link = `${apiURL}/products?keywords=${keywords}&page=${currentPage}&price[lte]=${price[1]}&price[gte]=${price[0]}&ratings[gte]=${ratings}`;
if (category) {
link = `${apiURL}/products?keywords=${keywords}&page=${currentPage}&price[lte]=${price[1]}&price[gte]=${price[0]}&category=${category}&ratings[gte]=${ratings}`;
}
const { data } = await axios.get(link);
dispatch({
type: ALL_PRODUCTS_SUCCESS,
payload: data,
});
} catch (err) {
dispatch({
type: ALL_PRODUCTS_FAILED,
payload: err.response.data.errMessage,
});
}
};
export const getBestSellers = () => async (dispatch) => {
try {
dispatch({
type: BEST_SELLERS_REQUEST,
});
const { data } = await axios.get(`${apiURL}/products/bestsellers`);
dispatch({
type: BEST_SELLERS_SUCCESS,
payload: data,
});
} catch (err) {
dispatch({
type: BEST_SELLERS_FAILED,
payload: err.response.data.errMessage,
});
}
};
Please help me to figure out why or give me some suggestions.
Thank for your guys supports.
I expect the component BestSellers does not re-render when the ProductList get update.