Egghead Newbie
Egghead Newbie

Reputation: 53

Action dispatched and state updated, but no render of the UI component

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

Answers (2)

Egghead Newbie
Egghead Newbie

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

markerikson
markerikson

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

Related Questions