Creating a model of a self-service mall powered by nfc tags with React Native. (Part 3).

Adeogo Oladipo
5 min readMay 9, 2021

Cart UI (Part 2).

Today, we will work on refactoring our app codebase and we will also implement the functionalities of the cart item buttons.

Refactoring the code

Refactoring is the process of restructuring existing code. It improves the maintainability and code quality.

To avoid cluttering the App.js file, we’ll move the CartItem component into it’s own file. To do that, we will create a file called CartItem.js and move the lines of code that concern the CartItem component there.

The CartItem.js file should look similar to this after you also move the styles.

import React from 'react';
import {Image, StyleSheet, Text, TouchableOpacity, View} from 'react-native';

const CartItem = ({product}): Node => {
return (
<View style={styles.cartItemContainer}>
<Image style={styles.productImage} source={{uri: product.url}}/>
<View style={styles.cartItemInnerContainer}>
<View style={styles.cartItemTitleDescCancelIconContainer}>
<View style={styles.cartItemTitleDescContainer}>
<Text style={styles.titleText}>
{product.title}
</Text>
<Text style={styles.descText}>
{product.description}
</Text>
</View>
<TouchableOpacity activeOpacity={0.4}>
<Image source={require('./assets/icon_delete.png')} style={styles.icon}/>
</TouchableOpacity>
</View>
<View style={styles.priceQuantityContainer}>
<Text style={styles.priceText}>
${product.priceInDollars}
</Text>
<View style={styles.utility}/>
<View style={styles.quantityContainer}>
<TouchableOpacity activeOpacity={0.5}>
<Image source={require('./assets/icon_minus.png')} style={styles.icon}/>
</TouchableOpacity>
<Text style={styles.quantityText}>3</Text>
<TouchableOpacity activeOpacity={0.5}>
<Image source={require('./assets/icon_plus.png')} style={styles.icon}/>
</TouchableOpacity>
</View>
</View>
</View>
</View>
);
};

const styles = StyleSheet.create({
icon: {
height: 30,
width: 30,
},
productImage: {
height: 80,
width: 80,
borderRadius: 8,
},
cartItemContainer: {
height: 80,
padding: 8,
display: 'flex',
flexDirection: 'row',
marginVertical: 12,
},
cartItemInnerContainer: {
flex: 1,
display: 'flex',
height: 80,
paddingBottom: 4,
},
cartItemTitleDescCancelIconContainer: {
display: 'flex',
flex: 4,
flexDirection: 'row',
},
cartItemTitleDescContainer: {
alignContent: 'center',
justifyContent: 'space-evenly',
flex: 1,
},
titleText: {
marginLeft: 8,
fontSize: 18,
},
descText: {
marginLeft: 8,
fontSize: 14,
},
priceQuantityContainer: {
flex: 3,
alignItems: 'center',
display: 'flex',
flexDirection: 'row',
},
priceText: {
marginLeft: 8,
fontSize: 18,
color: 'slategray',
},
utility: {
flex: 1,
},
quantityContainer: {
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
},
quantityText: {
fontSize: 16,
marginHorizontal: 8,
}
});

export default CartItem;

You should also make sure to import the CartItem component from it’s new path in the App.js file.

Changing the data structure

Currently, our data is structured in list, to make implementing our features much easier, we will change from a list to a JavaScript object.

We will rename our list from productsInCart to allProductsInStore, then we edit it from a list into an Object. So we go from

const productsInCart = [{
id: '0',
'title': 'Shoulder rolls tee',
'description': 'Shoulder rolls tee description',
'url': 'https://storage.googleapis.com/material-vignettes.appspot.com/image/34-0.jpg',
'priceInDollars': 27,
}, {
id: '1',
'title': 'Cerise scallop tee',
'description': 'Cerise scallop tee description',
'url': 'https://storage.googleapis.com/material-vignettes.appspot.com/image/33-0.jpg',
'priceInDollars': 42,
}, {
id: '2',
'title': 'Grey slouch tank',
'description': 'Grey slouch tank description',
'url': 'https://storage.googleapis.com/material-vignettes.appspot.com/image/35-0.jpg',
'priceInDollars': 24,
}, {
id: '3',
'title': 'Sunshirt dress',
'description': 'Sunshirt dress description',
'url': 'https://storage.googleapis.com/material-vignettes.appspot.com/image/36-0.jpg',
'priceInDollars': 58,
}, {
id: '4',
'title': 'Fine lines tee',
'description': 'Fine lines tee description',
'url': 'https://storage.googleapis.com/material-vignettes.appspot.com/image/37-0.jpg',
'priceInDollars': 58,
};

to

const allProductsInStore = {
'0': {
id: '0',
title: 'Shoulder rolls tee',
description: 'Shoulder rolls tee description',
url: 'https://storage.googleapis.com/material-vignettes.appspot.com/image/34-0.jpg',
priceInDollars: 27,
stock: 8,
quantity: 1,
}, '1': {
id: '1',
title: 'Cerise scallop tee',
description: 'Cerise scallop tee description',
url: 'https://storage.googleapis.com/material-vignettes.appspot.com/image/33-0.jpg',
priceInDollars: 42,
stock: 2,
quantity: 1,
}, '2': {
id: '2',
title: 'Grey slouch tank',
description: 'Grey slouch tank description',
url: 'https://storage.googleapis.com/material-vignettes.appspot.com/image/35-0.jpg',
priceInDollars: 24,
stock: 1,
quantity: 1,
},
'3': {
id: '3',
title: 'Sunshirt dress',
description: 'Sunshirt dress description',
url: 'https://storage.googleapis.com/material-vignettes.appspot.com/image/36-0.jpg',
priceInDollars: 58,
stock: 12,
quantity: 1,
}, '4': {
id: '4',
title: 'Fine lines tee',
description: 'Fine lines tee description',
url: 'https://storage.googleapis.com/material-vignettes.appspot.com/image/37-0.jpg',
priceInDollars: 58,
stock: 3,
quantity: 1,
},
};

As you can see, I also added the properties stock and quantity to keep track of the quantity of the item left in stock and the quantity of the item in the user’s cart.

Changing from static to state controlled data source

Finally, we will add a state controlled variable called productsInCart using the useState react hook. This variable will be updated by calling the updateProductsInCart method and for now, will be initially assign the initial value of allProductsInStore, the variable holding all the products we have currently in our store.

const [productsInCart, updateProductsInCart] = useState(allProductsInStore);

Since our data structure has changed, we also need to reflect that change where the data is being passed. Since our FlatList requires a list of items, we will pass a list of the values of the productsInCart variable.

So we will go from

<FlatList
contentInsetAdjustmentBehavior="automatic" data={productsInCart}

to

<FlatList
contentInsetAdjustmentBehavior="automatic" data={Object.values(productsInCart)}

Implementing cart item button functionalities

We have 3 buttons on the cart item. Two buttons to control the quantity of items and the third to remove the item from the cart.

For the first two buttons, we will create a function called updateItemQuantity and for the third, we will create a function called removeItem.

updateItemQuantity

const updateItemQuantity = (id, quantity) => {
productsInCart[id].quantity = quantity;
updateProductsInCart({...productsInCart});
};

This takes the id of the the product and the new quantity and updates the state accordingly.

removeItem

const removeItem = (id) => {
delete productsInCart[id];
updateProductsInCart({...productsInCart});
};

This deletes the item from the state using the id and updates the state accordingly.

Passing the function to the CartItem component

In the renderItem prop of the FlatList, we will pass the functions we have just created to the CartItem child component as shown below.

<FlatList
contentInsetAdjustmentBehavior="automatic" data={Object.values(productsInCart)}
renderItem={({item}) => <CartItem key={item.id} product={item}
updateItemQuantity={updateItemQuantity} removeItem={removeItem}/>}/>

And in the CartItem component, we will add the functions as part of the props expected from the parent component. So we will go from

const CartItem = ({product}): Node => {

to

const CartItem = ({product, updateItemQuantity, removeItem}): Node => {

Connecting new functions to our buttons

First, we will create 3 new functions in the CartItem component to represent each of our buttons,

const onDecreaseQuantity = () => {
updateItemQuantity(product.id, --product.quantity);
};

const onIncreaseQuantity = () => {
updateItemQuantity(product.id, ++product.quantity);
};

const onRemoveItem = () => {
removeItem(product.id);
};

These 3 functions will be triggered by the decrement, increment and removal buttons respectively.

Finally we will pass these functions as onPress props for their respective TouchableOpacity elements.

<TouchableOpacity activeOpacity={0.4} onPress={onRemoveItem}>
<Image source={require('./assets/icon_delete.png')} style={styles.icon}/>
</TouchableOpacity>
<TouchableOpacity activeOpacity={0.5} } onPress={onDecreaseQuantity}>
<Image source={require('./assets/icon_minus.png')} style={styles.icon}/>
</TouchableOpacity>
<TouchableOpacity activeOpacity={0.5}
onPress={onIncreaseQuantity}>
<Image source={require('./assets/icon_plus.png')} style={styles.icon}/>
</TouchableOpacity>

Finishing Touches

Firstly, we want to disable the decrement button if the quantity in cart is 1.

disabled={product.quantity === 1

So the decrement element becomes:

<TouchableOpacity activeOpacity={0.5} disabled={product.quantity === 1}
onPress={onDecreaseQuantity}>
<Image source={require('./assets/icon_minus.png')} style={styles.icon}/>
</TouchableOpacity>

Then we want to disable the increment button if the quantity is same as the quantity in the stock.

disabled={product.quantity === product.stock}

So the increment element becomes:

<TouchableOpacity activeOpacity={0.5} disabled={product.quantity === product.stock}
onPress={onIncreaseQuantity}>
<Image source={require('./assets/icon_plus.png')} style={styles.icon}/>
</TouchableOpacity>

And finally, we want to show some textual instruction if there are no items in the cart. So in App.js, we will add a text that does just that.

{!Object.keys(productsInCart).length && <Text style={styles.noItemsText}>No items in cart. Tap on the plus button  "+"  to add items to your cart</Text>}

Next Steps

We will finally add the listener for the NFC tags and using the listener, we will populate our cart. The state of the code after this article can be found here https://github.com/OlaAde/NFCStore/tree/ui-part-2.

--

--

Adeogo Oladipo

Co-Founder and CTO @DokitariUG. A Strong believer in the Potential in Each Human.