Making a simple React Native app

So I decided to learn React Native while making an app that would come in handy for me.

See, I like to keep a little checklist on my phone of all the different items that I need to take with me to work, so I don’t have to think every morning. I use Google Keep for this, and while it works fine, I want it to reset as soon as I’ve checked all the items, so all the items are automatically unchecked for the next day. So I decided to make an app!

I’m using React Native for this because I’ve been meaning to learn React Native for a while now, and what better way to learn than to solve your own little problems?

Let’s begin!

Hello, world!

Let’s start by installing expo-cli as it’s the easiest way to get started.

npm install -g expo-cli

Now we’ll initialize our app itself.

$ expo init DailyChecklist
? Choose a template: 
  ----- Managed workflow -----
❯ blank         minimal dependencies to run and an empty root component  
  tabs          several example screens and tabs using react-navigation 
  ----- Bare workflow -----
  bare-minimum  minimal setup for using unimodules 

Let’s choose blank template, and name our app. I’ll call it “Daily checklist”.

To keep things simple, I’ll try to stick to core React Native and avoid using Redux or anything like that for now.

Let’s start the app.

npm start

So now we have a couple of options as to how to run and “hotload” the app from the computer to the device. In decreasing order of speed: if you have a USB cable available with an Android device, you can choose the “Local” option. If your phone and computer are both connected to the same WiFi or LAN, choose LAN. Otherwise, choose Tunnel. I chose the “Local” option since it’s the fastest. You should see something like below:

Let’s make a few changes in our App.js file.

...
  render() {
    return (
      <View style={styles.container}>
        <Text>Hello React Native!</Text>
      </View>
    );
  }
...

If everything went well, the app on your phone should update automatically. This is called “hot reloading”, and it’s one of the neat things I like about React Native.

It works! Not every interesting, though.

It works! Not very interesting, though. Let’s get ourselves some checkboxes.

Adding checkboxes

Now the CheckBox component offered with React Native works only on Android, but that’s fine in my case because I’m only working on the Android app for now. It shouldn’t be too hard to swap it out with a cross-platform component if needs be.

import React from "react";
import { StyleSheet, CheckBox, Text, View } from "react-native";

export default class App extends React.Component {
  render() {
    return (
      <View style={styles.container}>
        <View style={{ flexDirection: "row" }}>
          <CheckBox />
          <Text style={{ marginTop: "auto", marginBottom: "auto" }}>Hello</Text>
        </View>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#fff",
    alignItems: "center",
    justifyContent: "center",
  },
});

So we have a check box! Now let’s make that into a reusable component, and then render a bunch of these.

import React from "react";
import { StyleSheet, CheckBox, Text, View } from "react-native";

const Item = ({ checked, value }) => (
  <View style={{ flexDirection: "row" }}>
    <CheckBox value={checked} />
    <Text style={{ marginTop: "auto", marginBottom: "auto" }}>{value}</Text>
  </View>
);

export default class App extends React.Component {
  render() {
    return (
      <View style={styles.container}>
        <Item checked={true} value="Item 1" />
        <Item checked={false} value="Item 2" />
        <Item checked={true} value="Item 3" />
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#fff",
    alignItems: "center",
    justifyContent: "center",
  },
});

That’s great, but right now you can’t uncheck a box once checked, and vice versa. This is because we need to update CheckBox‘s value ourselves, using the callback we provide to onValueChange.

import React from "react";
import { StyleSheet, CheckBox, Text, View } from "react-native";

const Item = ({ checked, onValueChange, value }) => (
  <View style={{ flexDirection: "row" }}>
    <CheckBox
      value={checked}
      onValueChange={onValueChange}
    />
    <Text style={{ marginTop: "auto", marginBottom: "auto" }}>{value}</Text>
  </View>
);

export default class App extends React.Component {
  state = {
    items: [
      { value: "Phone charger", checked: true },
      { value: "Notebook", checked: false },
      { value: "Pen", checked: false },
    ],
  };

  checkItem = index => {
    this.setState(({ items }) => ({
      items: items.map((item, currentIndex) => {
        if (index === currentIndex) {
          return { ...item, checked: !item.checked };
        }
        return item;
      }),
    }));
  };

  render() {
    return (
      <View style={styles.container}>
        {this.state.items.map(({ checked, value }, index) => (
          <Item
            key={index}
            checked={checked}
            value={value}
            onValueChange={() => this.checkItem(index)}
          />
        ))}
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#fff",
    alignItems: "center",
    justifyContent: "center",
  },
});

I added a new function called checkItem that checks or unchecks an item based on its index. To do this, I use React’s setState with callback. Then I passed it as a callback function to Item‘s onValueChange prop.

So our checkboxes work, that’s great! But we can’t add any items of our own yet. Let’s fix that.

Adding new items

Let’s add a TextInput component for, well, text input. And let’s store whatever the user types in this field in a state variable. We’ll also add a button. Pressing this button will add the new item to our list.

First, let’s add the state variable to store user input:

...
export default class App extends React.Component {
  state = {
    items = [],
    newItemText: '',
  }
...

Now we’ll add the functions to add items. handleNewItemTextChange is to store the input of TextInput in state. handleAddButtonPress gets called when user clicks the “Add” button, which we also defined below.

  handleNewItemTextChange = newItemText => {
    this.setState({ newItemText });
  };

  handleAddButtonPress = e => {
    const newItemText = this.state.newItemText.trim();
    if (newItemText) {
      this.addItem(newItemText);
      this.setState({ newItemText: '' });
    }
  }
...
  <TextInput
    style={styles.inputText}
    placeholder="Add new item..."
    value={this.state.newItemText}
    onChangeText={this.handleNewItemTextChange}
    onSubmitEditing={this.handleAddButtonPress}
    blurOnSubmit={false}
  />
  <Button
    style={styles.addButton}
    onPress={this.handleAddButtonPress}
    title="Add"
  />

We use onSubmitEditing to make sure that the soft keyboard’s “Go” action also works for adding the new item. It’s the button you usually see at the bottom right corner of soft keyboards on Android.

Great! We can check items, uncheck them and even add new ones. Now let’s add the feature to delete items.

Deleting items

To keep things simple, we’ll ask user for confirmation on long press of the item text. User can then either confirm deletion, or cancel.

First, let’s define the functions to delete the item, and to confirm the deletion with an alert dialog.

  deleteItem = index => {
    console.log('Deleting item of index ', index);
    this.setState(({ items }) => ({
      items: items.filter((item, _index) => index !== _index),
    }));
  };

  confirmDeleteItem = index => {
    console.log('confirming deletion of item of index', index);
    const itemText = this.state.items[index].value;
    Alert.alert(
      null,
      `Are you sure you want to delete item '${itemText}'?`,
      [
        { text: 'Cancel' },
        { text: 'Delete', onPress: () => this.deleteItem(index) },
      ],
      { cancelable: true },
    );
  };

Next, we’ll modify our Item component to accept the prop onLongPress.

const Item = ({ checked, onValueChange, value, onLongPress }) => (
  <View style={{ flexDirection: 'row' }}>
    <CheckBox value={checked} onValueChange={onValueChange} />
    <Text style={styles.itemText} onLongPress={onLongPress}>
      {value}
    </Text>
  </View>
);

Now we’ll define our long press handler function and pass it to our Item component.

  handleItemLongPress = index => {
    const itemText = this.state.items[index].value;
    Alert.alert(
      itemText,
      'Please select an action.',
      [
        { text: 'Cancel' },
        { text: 'Delete', onPress: () => this.confirmDeleteItem(index) },
      ],
      { cancelable: true },
    );
  };
...
<Item
  key={index}
  checked={checked}
  value={value}
  onValueChange={() => this.checkItem(index)}
  onLongPress={() => this.handleItemLongPress(index)}
/>
...

Neat! Now let’s add the feature to edit items.

Editing items

As before, let’s add the function to actually modify the state so that items can get edited.

  editItem = (index, value) => {
    this.setState(
      ({ items }) => ({
        items: items.map((item, currentIndex) => {
          if (index === currentIndex) {
            return { ...item, value };
          }
          return item;
        }),
      }),
      () => this.persistItems(),
    );
  };

As before, let’s add the function to actually modify the state so that items can get edited.

  editItem = (index, value) => {
    this.setState(
      ({ items }) => ({
        items: items.map((item, currentIndex) => {
          if (index === currentIndex) {
            return { ...item, value };
          }
          return item;
        }),
      }),
      () => this.persistItems(),
    );
  };

As before, let’s add the function to actually modify the state so that items can get edited.

  editItem = (index, value) => {
    this.setState(
      ({ items }) => ({
        items: items.map((item, currentIndex) => {
          if (index === currentIndex) {
            return { ...item, value };
          }
          return item;
        }),
      }),
      () => this.persistItems(),
    );
  };
const Item = ({ checked, onValueChange, value, onLongPress }) => (
  <View style={{ flexDirection: 'row' }}>
    <CheckBox value={checked} onValueChange={onValueChange} />
    <Text style={styles.itemText} onLongPress={onLongPress}>
      {value}
    </Text>
  </View>
);

Now we’ll define our long press handler function and pass it to our Item component.

  handleItemLongPress = index => {
    const itemText = this.state.items[index].value;
    Alert.alert(
      itemText,
      'Please select an action.',
      [
        { text: 'Cancel' },
        { text: 'Delete', onPress: () => this.confirmDeleteItem(index) },
      ],
      { cancelable: true },
    );
  };
...
<Item
  key={index}
  checked={checked}
  value={value}
  onValueChange={() => this.checkItem(index)}
  onLongPress={() => this.handleItemLongPress(index)}
/>
...

Neat! Now let’s add the feature to edit items.

Editing items

As before, let’s add the function to actually modify the state so that items can get edited.

  editItem = (index, value) => {
    this.setState(
      ({ items }) => ({
        items: items.map((item, currentIndex) => {
          if (index === currentIndex) {
            return { ...item, value };
          }
          return item;
        }),
      }),
      () => this.persistItems(),
    );
  }; 

It’s fairly simple. When called, it’ll edit the item specified by index to value.

Now let’s add a wrapper function around this that gets called when user clicks on the “Edit” button.

handleEditButtonPress = () => {
  this.editItem(this.state.editItemIndex, this.state.editItemText);
  this.setState({ confirmEditModalVisible: false });
};

Now to edit items, we need another input field, where we can type in the new value. To accommodate this input field, we’ll use a Modal component:

import { Modal } from 'react-native';
...
      <View style={styles.container}>
        <Modal
          animationType="slide"
          transparent={false}
          visible={this.state.confirmEditModalVisible}
          onRequestClose={() =>
            this.setState({ confirmEditModalVisible: false })
          }
        >
          <View>
            <TextInput
              style={styles.inputText}
              placeholder="Edit item..."
              value={this.state.editItemText}
              onChangeText={this.handleEditItemTextChange}
              onSubmitEditing={this.handleEditButtonPress}
              blurOnSubmit={false}
            />
            <Button
              style={styles.addButton}
              onPress={this.handleEditButtonPress}
              title="Save"
              disabled={!this.state.editItemText.trim()}
            />
          </View>
        </Modal>
        {this.state.items.map(({ checked, value }, index) => (
...

Here, we’ve added a new Modal component, with a View component inside of it. This View component contains the main UI of what the modal will contain. It has a TextInput and a Button.

We’ve added a bunch of new state variables and functions here. Let’s go ahead and define those.

...
  state = {
    ...
    editItemText: '',
    editItemIndex: null,
    confirmEditModalVisible: false,
  }

...

  handleEditItemTextChange = editItemText => {
    this.setState({ editItemText });
  };

...

editItemText contains the text entered in the TextInput field above. editItemIndex holds the index of the item being edited. confirmEditModalVisible indicates whether or not the modal is visible.

Let’s add another function actually show the modal. We’ll reuse our handleItemLongPress function for this.

  confirmEditItem = index => {
    const editItemText = this.state.items[index].value;
    this.setState({
      confirmEditModalVisible: true,
      editItemText,
      editItemIndex: index,
    });
  };

  handleItemLongPress = index => {
    const itemText = this.state.items[index].value;
    Alert.alert(
      itemText,
      'Please select an action.',
      [
        { text: 'Cancel' },
        { text: 'Delete', onPress: () => this.confirmDeleteItem(index) },
        { text: 'Edit', onPress: () => this.confirmEditItem(index) },
      ],
      { cancelable: true },
    );
  };

Now you should be able to edit items by long pressing an item and choosing “Edit”.

Source code

Hope this article was helpful. For reference, the completed source code of the app is available here.

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.