Reputation: 53
Who can support on my issue: Dispatching an action does does change the state as anticipated. But the component from which the issue gets dispatched does not re-render. When I simply save the component, it of course re-renders and shows the desiree effekt.
Here my files:
actions.js
export const TOGGLE_PRODUCT = "TOGGLE_PRODUCT";
export const INCREMENT = "INCREMENT";
//ACTION CREATER FUNCTIONS
export const toggleProduct = (id) => {
return {
type: TOGGLE_PRODUCT,
productId: id,
};
};
reducer.js
import { PRODUCTLIST } from "../../data/dummydata";
import { TOGGLE_PRODUCT } from "../actions/products";
const initialState = {
allProducts: PRODUCTLIST,
};
const productReducer = (state = initialState, action) => {
switch (action.type) {
case TOGGLE_PRODUCT:
const Products = state.allProducts;
const toggledProduct = Products.find((el) => el.id === action.productId);
if (toggledProduct.status === false) {
toggledProduct.status = true;
} else {
toggledProduct.status = false;
}
console.log("Neue Products: ", Products);
return {
allProducts: Products,
};
default:
return state;
}
};
export default productReducer;
component.js
import { useSelector, useDispatch } from "react-redux";
import React, { useEffect, useCallback } from "react";
import { Text, View, Button, FlatList, StyleSheet } from "react-native";
import Product from "../components/Product";
import { toggleProduct } from "../store/actions/products";
import { increment } from "../store/actions/products";
const ShoppingListScreen = (props) => {
const dispatch = useDispatch();
const toggleProductHandler = useCallback(
// useCallback verhindert infinite loop
(id) => {
dispatch(toggleProduct(id));
},
[]
);
const Products = useSelector((state) => state.product.allProducts);
return (
<View style={styles.screen}>
<FlatList
data={Products}
renderItem={({ item }) => (
<View
style={
item.status === true ? styles.elementselected : styles.element
}
>
<Product
style={styles.text}
id={item.id}
product={item.product}
department={item.department}
status={item.status}
onClick={() => toggleProductHandler(item.id)}
/>
</View>
)}
/>
<View style={styles.button}>
<Button
title="FERTIG"
onPress={() => {
props.navigation.goBack();
}}
/>
{/* <Button
title='Stand "cartRewe" '
onPress={() => {
props.testFunction1();
}}
/>
<Button
title='Stand "planRewe" '
onPress={() => {
props.testFunction2();
}}
/> */}
</View>
</View>
);
};
const styles = StyleSheet.create({
screen: {
backgroundColor: "#fafafa",
flex: 1,
justifyContent: "flex-start",
},
element: {
backgroundColor: "#ddd",
borderWidth: 2,
borderColor: "#bbb",
borderRadius: 20,
marginVertical: 5,
marginHorizontal: 25,
},
elementselected: {
backgroundColor: "#a0ffa0",
borderWidth: 3,
borderColor: "#64ff64",
borderRadius: 20,
marginVertical: 5,
marginHorizontal: 25,
},
text: {
color: "#333",
// fontSize: 22,
// marginHorizontal: 10
},
button: {
marginVertical: 24,
},
});
export default ShoppingListScreen;
Upvotes: 0
Views: 1140
Reputation: 53
This totally makes sense to not mutate the state object. Since I do now want to implement a further package just for this issue, I revised my source code as advised. For any reason my component still does not rerender without a manual safe. How about this code, what is still wrong?
reducer.js
import { PRODUCTLIST } from "../../data/dummydata";
import { TOGGLE_PRODUCT } from "../actions/products";
const initialState = {
allProducts: PRODUCTLIST,
};
const productReducer = (state = initialState, action) => {
switch (action.type) {
case TOGGLE_PRODUCT:
const newProducts = state.allProducts;
const toggledProduct = newProducts.findIndex(
(el, idx) => el.id === action.productId
);
if (newProducts[toggledProduct].status === false) {
newProducts[toggledProduct].status = true;
} else {
newProducts[toggledProduct].status = false;
}
return {
...state,
newProducts,
};
default:
return state;
}
};
export default productReducer;
Upvotes: 0
Reputation: 67627
These lines are the problem:
const Products = state.allProducts;
const toggledProduct = Products.find((el) => el.id === action.productId);
if (toggledProduct.status === false) {
toggledProduct.status = true;
} else {
toggledProduct.status = false;
}
return {
allProducts: Products,
};
That's mutating the existing state, and you must never mutate the state in a Redux reducer!.
To fix this, you would need to make copies of the Products
array and the toggledProduct
object, update the copies, and return those.
Having said that, the better option is to use our official Redux Toolkit package, which is our recommended approach for writing Redux logic. It lets you write "mutating" logic that is turned into safe and correct immutable updates.
Here's what this would look like with Redux Toolkit's createSlice
API:
const productsSlice = createSlice({
name: 'products',
initialState: {allProducts: []},
reducers: {
productToggled(state, action) {
const toggledProduct = state.allProducts.find(e => e.id === action.payload);
// This "mutating" syntax _only_ works inside Redux Toolkit's createSlice/createReducer!
toggledProduct.status = !toggledProduct.status;
}
}
})
Upvotes: 1