Search code examples
reactjsreact-nativereduxreact-native-ui-kitten

React Native redux state array update causes undefined error


I'm building a RN app and I just recently learned REDUX and applied it into my app. I have a shopping cart feature in my mobile app. On one screen the user can add items to the cart. Then when they are done they can click the cart icon to view the full cart (new screen).

Shown below is the code for my cart screen.

import React, { useState, useEffect } from "react";
import { StyleSheet, SafeAreaView } from "react-native";
import {
  Icon,
  Layout,
  Text,
  TopNavigation,
  TopNavigationAction,
  Divider,
  List,
  ListItem,
  Button,
  useTheme,
} from "@ui-kitten/components";
import * as AppConstants from "../constants/constants";
import { useDispatch, useSelector } from "react-redux";
import { cartItemRemoved } from "../store/shoppingCart";

const CartScreen = ({ navigation }) => {
  const theme = useTheme();
  const dispatch = useDispatch();
  const cartData = useSelector((state) => state.shoppingCart.cart);

  const BackIcon = (props) => <Icon {...props} name="arrow-back" />;
  const TrashIcon = (props) => <Icon {...props} name="trash-2" />;

  const BackAction = () => (
    <TopNavigationAction icon={BackIcon} onPress={() => navigation.goBack()} />
  );

  useEffect(() => {}, []);

  const renderItemAccessory = (item) => {
    return (
      <Button
        status="danger"
        accessoryLeft={TrashIcon}
        size="tiny"
        onPress={() => {
          alert("You selected: " + item.serviceName + item.serviceId);
          dispatch(cartItemRemoved(item.serviceId));
        }}
      >
        Delete
      </Button>
    );
  };

  const renderItem = ({ item }) => (
    <ListItem
      title={`${item.serviceName}`}
      description={`${item.price}`}
      accessoryRight={() => renderItemAccessory(item)}
    />
  );

  return (
    <SafeAreaView style={{ flex: 1 }}>
      <TopNavigation
        title={AppConstants.TITLE_Cart}
        subtitle={AppConstants.APP_SLOGAN}
        alignment="center"
        accessoryLeft={() => BackAction()}
      />
      <Divider />
      <Layout style={styles.container}>
        {cartData.length > 0 ? (
          <>
            <List
              data={cartData}
              ItemSeparatorComponent={Divider}
              renderItem={renderItem}
            />
            <Button onPress={() => {}}>Send</Button>
          </>
        ) : (
          <Text style={styles.text} category="h1">
            Cart is empty.
          </Text>
        )}
      </Layout>
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  container: {
    maxHeight: 300,
  },
  text: {
    textAlign: "center",
    padding: 10,
  },
});

export default CartScreen;

When this screen is first opened the items in the cart are displayed without any errors or warnings. Soon as I click the delete button, I can see that my REDUX state array was updated, the item was removed from it. But then my app crashes with the error message below.

TypeError: undefined is not an object (evaluating 'cartData.length')

This error is located at:
    in CartScreen (at SceneView.tsx:122)
    in StaticContainer
    in StaticContainer (at SceneView.tsx:115)
    in EnsureSingleNavigator (at SceneView.tsx:114)
    in SceneView (at useDescriptors.tsx:153)
    in RCTView (at View.js:34)
    in View (at ResourceSavingScene.tsx:68)
    in RCTView (at View.js:34)
    in View (at ResourceSavingScene.tsx:63)
    in ResourceSavingScene (at DrawerView.tsx:183)
    in RCTView (at View.js:34)
    in View (at src/index.native.js:134)
    in ScreenContainer (at DrawerView.tsx:162)
    in RCTView (at View.js:34)
    in View (at Drawer.tsx:645)
    in RCTView (at View.js:34)
    in View (at createAnimatedComponent.js:240)
    in AnimatedComponent(View) (at Drawer.tsx:638)
    in RCTView (at View.js:34)
    in View (at createAnimatedComponent.js:240)
    in AnimatedComponent(View) (at Drawer.tsx:628)
    in PanGestureHandler (at GestureHandlerNative.tsx:13)
    in PanGestureHandler (at Drawer.tsx:619)
    in DrawerView (at DrawerView.tsx:215)
    in SafeAreaProviderCompat (at DrawerView.tsx:213)
    in RCTView (at View.js:34)
    in View (at DrawerView.tsx:212)
    in DrawerView (at createDrawerNavigator.tsx:47)
    in DrawerNavigator (at HomeStack.js:13)
    in HomeStack (at SceneView.tsx:122)
    in StaticContainer
    in StaticContainer (at SceneView.tsx:115)
    in EnsureSingleNavigator (at SceneView.tsx:114)
    in SceneView (at useDescriptors.tsx:153)
    in RCTView (at View.js:34)
    in View (at CardContainer.tsx:245)
    in RCTView (at View.js:34)
    in View (at CardContainer.tsx:244)
    in RCTView (at View.js:34)
    in View (at CardSheet.tsx:33)
    in ForwardRef(CardSheet) (at Card.tsx:573)
    in RCTView (at View.js:34)
    in View (at createAnimatedComponent.js:165)
    in AnimatedComponent (at createAnimatedComponent.js:215)
    in ForwardRef(AnimatedComponentWrapper) (at Card.tsx:555)
    in PanGestureHandler (at GestureHandlerNative.tsx:13)
    in PanGestureHandler (at Card.tsx:549)
    in RCTView (at View.js:34)
    in View (at createAnimatedComponent.js:165)
    in AnimatedComponent (at createAnimatedComponent.js:215)
    in ForwardRef(AnimatedComponentWrapper) (at Card.tsx:544)
    in RCTView (at View.js:34)
    in View (at Card.tsx:538)
    in Card (at CardContainer.tsx:206)
    in CardContainer (at CardStack.tsx:620)
    in RCTView (at View.js:34)
    in View (at Screens.tsx:84)
    in MaybeScreen (at CardStack.tsx:613)
    in RCTView (at View.js:34)
    in View (at Screens.tsx:54)
    in MaybeScreenContainer (at CardStack.tsx:495)
    in CardStack (at StackView.tsx:462)
    in KeyboardManager (at StackView.tsx:458)
    in RNCSafeAreaProvider (at SafeAreaContext.tsx:74)
    in SafeAreaProvider (at SafeAreaProviderCompat.tsx:42)
    in SafeAreaProviderCompat (at StackView.tsx:455)
    in RCTView (at View.js:34)
    in View (at StackView.tsx:454)
    in StackView (at createStackNavigator.tsx:87)
    in StackNavigator (at navigation/index.js:21)
    in EnsureSingleNavigator (at BaseNavigationContainer.tsx:409)
    in ForwardRef(BaseNavigationContainer) (at NavigationContainer.tsx:91)
    in ThemeProvider (at NavigationContainer.tsx:90)
    in ForwardRef(NavigationContainer) (at navigation/index.js:20)
    in ServiceNavigation (created by Connect(ServiceNavigation))
    in Connect(ServiceNavigation) (at App.js:46)
    in RCTView (at View.js:34)
    in View (created by ModalPanel)
    in ModalPanel (created by ApplicationProvider)
    in ThemeProvider (created by StyleProvider)
    in MappingProvider (created by StyleProvider)
    in StyleProvider (created by ApplicationProvider)
    in ApplicationProvider (at App.js:45)
    in Provider (at App.js:43)
    in App (created by ExpoRoot)
    in RootErrorBoundary (created by ExpoRoot)
    in ExpoRoot (at renderApplication.js:45)
    in RCTView (at View.js:34)
    in View (at AppContainer.js:106)
    in RCTView (at View.js:34)
    in View (at AppContainer.js:132)
    in AppContainer (at renderApplication.js:39)

Further down I also get this error message

Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
at node_modules\react-native\Libraries\LogBox\LogBox.js:173:8 in registerError

Here is the code in my REDUX reducer

//================================================
// Action types
//================================================
const CART_ITEM_ADDED = "cartItemAdded";
const CART_ITEM_REMOVED = "cartItemRemoved";

//================================================
// Actions - This is also known as action creators
//================================================
export const cartItemAdded = (
  serviceId,
  quantity,
  description,
  serviceName,
  price
) => ({
  type: CART_ITEM_ADDED,
  payload: {
    serviceId,
    quantity,
    description,
    serviceName,
    price,
  },
});

export const cartItemRemoved = (serviceId) => ({
  type: CART_ITEM_REMOVED,
  payload: {
    serviceId,
  },
});

//================================================
// Reducer
//================================================

const initialState = {
  cart: [],
};

export default function shoppingCartReducer(state = initialState, action) {
  switch (action.type) {
    case CART_ITEM_ADDED:
      return {
        ...state,
        cart: [...state.cart, action.payload],
      };

    case CART_ITEM_REMOVED:
      return state.cart.filter(
        (service) => service.serviceId !== action.payload.serviceId
      );

    default:
      return state;
  }
}

If anyone can please help me understand what I am missing here?


Solution

  • the problem come from your reducers

    case CART_ITEM_REMOVED:
          return state.cart.filter(
            (service) => service.serviceId !== action.payload.serviceId
          );
    

    you have changed all state, it must be

    case CART_ITEM_REMOVED:
          return {
           ...state,
           cart: state.cart.filter((service) => service.serviceId !== action.payload.serviceId)
           };