All the interaction with the user will happen through event handlers in React Native. Depending on the controller, we will have different events which can be triggered. The most common event is onPress, as it will be triggered every time we push a button, a checkbox, or a view in general. Let's add some onPress handlers for all the components which can be pushed in our screen:
/*** ShoppingList.js ***/
...
render() {
return (
<Container>
<Content>
<List>
{this.state.products.map(p => {
return (
<ListItem
key={p.id}
onPress={this._handleProductPress.bind(this, p)}
>
<Body>
<Text style={{ color: p.gotten ? '#bbb' : '#000' }}>
{p.name}
</Text>
</Body>
<Right>
<CheckBox
checked={p.gotten}
onPress={this._handleProductPress.bind(this, p)}
/>
</Right>
</ListItem>
);
})}
</List>
</Content>
<Fab
style={{ backgroundColor: '#5067FF' }}
position="bottomRight"
onPress={this._handleAddProductPress.bind(this)}
>
<Icon name="add" />
</Fab>
<Fab
style={{ backgroundColor: 'red' }}
position="bottomLeft"
onPress={this._handleClearPress.bind(this)}
>
<Icon ios="ios-remove" android="md-remove" />
</Fab>
</Container>
);
}
...
Notice we added three onPress event handlers:
- On <ListItem>, to react when the user taps on one product in the list
- On <CheckBox>, to react when the user taps on the checkbox icon next to every product in the list
- On both the <Fab> buttons
If you know React, you probably understand why we use .bind in all our handler functions, but, in case you have doubts, .bind will make sure we can use this inside the definition of our handlers as a reference to the component itself instead of the global scope. This will allow us to call methods inside our components as this.setState or read our component's attributes, such as this.props and this.state.
For the cases when the user taps on a specific product, we also bind the product itself, so we can use them inside our event handlers.
Now, let's define the functions which will serve as event handlers:
/*** ShoppingList.js ***/
...
_handleProductPress(product) {
this.state.products.forEach(p => {
if (product.id === p.id) {
p.gotten = !p.gotten;
}
return p;
});
this.setState({ products: this.state.products });
}
...
First, let's create a handler for when the user taps on a product from our shopping list or in its checkbox. We want to mark the product as gotten (or unmark it if it was already gotten), so we will update the state with the product marked properly.
Next, we will add a handler for the blue <Fab> button to navigate to the AddProduct screen:
/*** ShoppingList.js ***/
...
_handleAddProductPress() {
this.props.navigation.navigate('AddProduct', {
addProduct: product => {
this.setState({
products: this.state.products.concat(product)
});
},
deleteProduct: product => {
this.setState({
products: this.state.products.filter(p => p.id !== product.id)
});
},
productsInList: this.state.products
});
}
...
This handler uses this.props.navigation, which is a property automatically passed by the Navigator component from react-navigation. This property contains a method named navigate, receiving the name of the screen to which the app should navigate plus an object which can be used as a global state. In the case of this app, we will store three keys:
- addProduct: One function to allow the AddProduct screen to modify the ShoppingList component's state to reflect the action of adding a new product to the shopping list.
- deleteProduct: One function to allow the AddProduct screen to modify the ShoppingList component's state to reflect the action of removing a product from the shopping list.
- productsInList: A variable holding the list of products is already on the shopping list, so the AddProducts screen can know which products were already added to the shopping list and display those as "already added", preventing the addition of duplicate items.
Handling state within the navigation should be seen as a workaround for simple apps containing a limited number of screens. In larger apps (as we will see in later chapters), a state management library, such as Redux or MobX, should be used to keep the separation between pure data and user interface handling.
We will add the last handler for the blue <Fab> button, which enables the user to clear all the items in the shopping list in case you want to start a new list:
/*** ShoppingList.js ***/
...
_handleClearPress() {
Alert.alert('Clear all items?', null, [
{ text: 'Cancel' },
{ text: 'Ok', onPress: () => this.setState({ products: [] }) }
]);
}
...
We are using Alert to prompt the user for confirmation before clearing all the elements in our shopping list. Once the user confirms this action, we will empty the products attribute in our component's state.