Search code examples
react-nativereact-native-flatlist

Counter with different state per element in a FlatList React Native


I've been several days stuck with this code. I'm trying to make a restaurant cart where each of the product of the flatlist have a + and a minus button and i cant find how to do it. Iwas thinking of using a list of objects as an state, but i can't find a way to work.

const renderProduct = ({ item }) => {
    // console.log(JSON.stringify(prodUnit))
    return (

      <ImageCard
        imageUri={item.image ? { uri: process.env.API_BASE_URL + '/' + item.image } : defaultProductImage}
        title={item.name}
      >

        <TextRegular numberOfLines={2}>{item.description}</TextRegular>
        <TextSemiBold textStyle={styles.price}>{item.price.toFixed(2)}€</TextSemiBold>
        {!item.availability &&
          <TextRegular textStyle={styles.availability }>Not available</TextRegular>
        }
        <View style={styles.container1}>
              <View style={{
                alignItems: 'flex-start',
                flex: 1,
                textAlign: 'centre',
                marginEnd: 10,
                marginTop: 12,
                padding: 0

              }}>
                <Pressable style={({ pressed }) => ({
                  backgroundColor: pressed
                    ? 'rgb(255, 255, 255)'
                    : 'rgb(255, 173, 38)',
                  borderRadius: 20,
                  height: 40,
                  marginTop: 5,
                  marginBottom: 5,
                  padding: 20,
                  alignSelf: 'center',
                  flexDirection: 'row',
                  alignItems: 'center'

                })} pressed={handleIncrement}>

        <Text style={{ color: 'white', fontFamily: 'monospace', fontSize: 30 }}>+</Text>

        </Pressable>
              </View>
              <View style={{
                alignItems: 'flex-start',
                flex: 1,
                textAlign: 'centre',
                marginEnd: 10,
                marginTop: 12,
                padding: 0

              }}>
                <Pressable style={({ pressed }) => ({
                  backgroundColor: pressed
                    ? 'rgb(255, 255, 255)'
                    : 'rgb(255, 173, 38)',
                  borderRadius: 20,
                  height: 40,
                  marginTop: 5,
                  marginBottom: 5,
                  padding: 20,
                  alignSelf: 'center',
                  flexDirection: 'row',
                  alignItems: 'center',
                  textAlign: 'centre'

                })} pressed= {handleDecrement}>
          {
        <Text style={{ color: 'white', fontFamily: 'monospace', fontSize: 35 }}>-</Text>
          }
        </Pressable>

              </View>

        <View style={{ textAlign: 'centre', marginTop: 25, marginLeft: 15 }}>
            <Text style={{ color: 'grey' }}>{units}
            </Text>
        </View>
            </View>

      </ImageCard>
    // aqui falta hacer que me devuelva la lista de objetos bien :(
    )
  }

  const renderEmptyProductsList = () => {
    return (
      <TextRegular textStyle={styles.emptyList}>
        This restaurant has no products yet.
      </TextRegular>
    )
  }

  const fetchRestaurantDetail = async () => {
    try {
      const fetchedRestaurant = await getDetail(route.params.id)
      setRestaurant(fetchedRestaurant)
    } catch (error) {
      showMessage({
        message: `There was an error while retrieving restaurant details (id ${route.params.id}). ${error}`,
        type: 'error',
        style: GlobalStyles.flashStyle,
        titleStyle: GlobalStyles.flashTextStyle
      })
    }
  }

  return (
    <Formik>

                <FlatList
                  ListHeaderComponent={renderHeader}
                  ListEmptyComponent={renderEmptyProductsList}
                  style={styles.container}
                  data={restaurant.products}
                  extraData={units}
                  renderItem={renderProduct}
                  keyExtractor={item => item.id.toString()}

                />
  </Formik>
  )
}

I've tried several options such as the list of objects or separating into different components. However I wasn't able to perform it. It is expected to increment the quantity of the product when + is pressed and decremented when - is pressed. It receives an array of products which looks like this Array in the console


Solution

  • Here is a working example of what you need. Please adjust your code accordingly.

    import React, { useState } from "react";
    import { FlatList, Pressable, Text, View } from "react-native";
    
    export default function App() {
      const [productQuantities, setProductQuantities] = useState({});
    
      const handleIncrement = (productId) => {
        const updatedQuantities = { ...productQuantities };
        if (updatedQuantities[productId]) {
          updatedQuantities[productId]++;
        } else {
          updatedQuantities[productId] = 1;
        }
        setProductQuantities(updatedQuantities);
      };
    
      const handleDecrement = (productId) => {
        const updatedQuantities = { ...productQuantities };
        if (updatedQuantities[productId]) {
          updatedQuantities[productId]--;
          if (updatedQuantities[productId] === 0) {
            delete updatedQuantities[productId];
          }
          setProductQuantities(updatedQuantities);
        }
      };
    
      const renderProduct = ({ item }) => {
        const productId = item.id.toString();
        const units = productQuantities[productId] || 0;
    
        return (
          <View>
            <Text>{item.name}</Text>
            <View style={{ flexDirection: "row", alignItems: "center" }}>
              <Pressable
                style={({ pressed }) => ({
                  backgroundColor: pressed ? "grey" : "blue",
                  borderRadius: 20,
                  height: 30,
                  width: 30,
                  justifyContent: "center",
                  alignItems: "center",
                  marginRight: 10
                })}
                onPress={() => handleDecrement(productId)}
              >
                <Text style={{ color: "white" }}>-</Text>
              </Pressable>
              <Text>{units}</Text>
              <Pressable
                style={({ pressed }) => ({
                  backgroundColor: pressed ? "grey" : "blue",
                  borderRadius: 20,
                  height: 30,
                  width: 30,
                  justifyContent: "center",
                  alignItems: "center",
                  marginLeft: 10
                })}
                onPress={() => handleIncrement(productId)}
              >
                <Text style={{ color: "white" }}>+</Text>
              </Pressable>
            </View>
          </View>
        );
      };
    
      const renderEmptyProductsList = () => {
        return <Text>This restaurant has no products yet.</Text>;
      };
    
      const products = [
        { id: 1, name: "Pizza", price: 10.0 },
        { id: 2, name: "Pasta", price: 8.0 },
        { id: 3, name: "Salad", price: 6.0 }
      ];
    
      return (
        <FlatList
          data={products}
          renderItem={renderProduct}
          ListEmptyComponent={renderEmptyProductsList}
          keyExtractor={(item) => item.id.toString()}
        />
      );
    }
    

    You may check it here: https://codesandbox.io/s/restaurant-card-rlezk1