WatermelonDB in React Native (2/2)

After the correct configuration of WatermelonDB and preparation of the model and table schema, we can move on to the next steps.

Application view

At first it is worth to prepare the view. Let’s think about what exactly do we need. Every new task should be an element of the list, and in addition, we should be able to delete, rename, check, as well as uncheck each of them. Firstly, let’s create a new directory called pages in which we will store our view.
Inside this directory create a new TaskPage.js file with a simple class containing our list of tasks (don’t forget to import all the necessary libraries!).

class TasksPage extends Component {
  render = () => {
    return (
      <>
      </>
    );
  };
}

One of the important features of WatermelonDB library is the ability to use components that will react to changes in the database. If elements of the task list inside our database are changed, the list will also be updated in the view. To do that, allow the components to be observed:

import withObservables from '@nozbe/with-observables';
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';

...

export default withDatabase(
  withObservables([], ({database}) => ({
    tasks: database.collections.get('tasks').query().observe(),
  }))(TasksPage),
);

Now using props of the component we can access the tasks property which is an collection of tasks table records in the database. Next add a Flatlist to display the elements in the render() function:

render = () => {
  const {tasks} = this.props;

  return (
    <>
      <FlatList
        data={tasks}
        ListHeaderComponent={this.renderHeader}
        renderItem={this.renderTask}
        keyExtractor={(item) => 'item' + item.id}
      />
    </>
  );
};

To prepare the rendering of an element let’s add a new library that adds the CheckBox component. In the terminal inside the project directory run the command:

yarn add react-native-check-box

Now add the renderTask() method:

renderTask = ({item}) => (
  <View style={[styles.itemContainer, styles.insideContainer]}>
    <View style={styles.insideContainer}>
      <CheckBox
        isChecked={item.completed}
        style={styles.checkbox}
        checkBoxColor="#666666"
        uncheckedCheckBoxColor="#666666"
        checkedCheckBoxColor="#38d13b"
        onClick={() => this.onChangeItemCompleted(item)}
      />
      <TextInput
        style={styles.input}
        value={item.name}
        onChangeText={(value) => this.onChangeItemName(item, value)}
      />
    </View>
    <TouchableOpacity onPress={() => this.removeTask(item)}>
      <Image style={styles.icon} source={require('../assets/remove.png')} />
    </TouchableOpacity>
  </View>
);

All of the used functions will be explained in a moment. Before that, let us move on to creating a filed that allows to add a new task:

renderHeader = () => (
  <View style={[styles.itemContainer, styles.insideContainer]}>
    <View style={styles.insideContainer}>
      <TextInput
        style={[styles.input, styles.headerInput]}
        placeholder="Dodaj nazwę zadania"
        onChangeText={this.onChangeNewTaskName}
        value={this.state.newTaskName}
      />
    </View>
    <TouchableOpacity style={[styles.rightButton, styles.addButton]}>
      <Image style={styles.icon} source={require('../assets/add.png')} />
    </TouchableOpacity>
  </View>
);

Let’s also add a few styles to slightly improve the appearance of our application:

const styles = StyleSheet.create({
  itemContainer: {
    padding: 15,
  },
  insideContainer: {
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'space-between',
    flex: 1,
  },
  checkbox: {
    width: 30,
    marginRight: 15,
    borderColor: '#666666',
  },
  icon: {
    height: 30,
    width: 30,
  },
  input: {
    flex: 1,
  },
  headerInput: {
    marginLeft: 45,
  },
});

After the view is prepared, we can focus on the application logic.

Database operations

Step 1
Adding a new task

When adding a new task, it’s necessary to enter its name. For that, in the class, let’s add a definition of state and a method that allows to change it.

state = {
  newTaskName: '',
};

...

onChangeNewTaskName = (value) => {
  this.setState({newTaskName: value});
};

After clicking on the button for adding a new element, we call the method:

addTask = async () => {
  const {database} = this.props;
  const {newTaskName} = this.state;
  const tasksCollection = database.collections.get('tasks');

  await database.action(async () => {
    await tasksCollection.create((task) => {
      task.name = newTaskName;
      task.completed = false;
    });
    this.setState({newTaskName: ''});
  });
};

First, we have to indicate from which collection we are going to retrieve elements. Because in our case this collection is called tasks, this is the one that we select. Then we perform an insertion of a new element of the collection on it. After inserting a record to the database, it is worth clearing the text field from the task name, which is why the setState() function was used.

Step 2
Editing a task

Task editing can be divided into two actions. The first one is changing the status of the task completion after clicking on the field. The second one is updating the name of the task. For the sake of clarity, we’ll break it down into two methods, each of which will perform a different action.

onChangeItemName = async (item, name) => {
  const {database} = this.props;
  const tasksCollection = database.collections.get('tasks');
  const taskToUpdate = await tasksCollection.find(item.id);

  await database.action(async () => {
    await taskToUpdate.update((task) => {
      task.name = name;
    });

    this.setState({refreshing: !this.state.refreshing});
  });
};

onChangeItemCompleted = async (item) => {
  const {database} = this.props;
  const tasksCollection = database.collections.get('tasks');
  const taskToUpdate = await tasksCollection.find(item.id);

  await database.action(async () => {
    await taskToUpdate.update((task) => {
      task.completed = !item.completed;
    });

    this.setState({refreshing: !this.state.refreshing});
  });
};

It is quite an easy task. After selecting the right collection, we find the element that we want to edit, based on its automatically generated id.

In this case, however, there is a trick that we have to use. By definition, a flatlist will not be informed about a change in the elements of the data array. Therefore, we have to inform it about that change. For this purpose, we create a boolean attribute in the state view, which changes its value when some update in the database is made. We also add the extraData attribute to the flatlist, the value of which will be the above-mentioned state value. Thanks to this, after each action of updating a record in the database, the flatlist will be able to reload the changed elements.

Step 3
Deleting a task

The last functionality will be removing the task from the list. Once again we create a separate method that will take care of that. Inside we find a collection and an element that we want to remove from it and we carry out a permanent deletion action. We can use the markAsDeleted() method instead, which will add a flag informing about deletion to the field, even though it will be possible to restore the record physically.

removeTask = async (item) => {
  const {database} = this.props;
  const tasksCollection = database.collections.get('tasks');
  const taskToRemove = await tasksCollection.find(item.id);

  await database.action(async () => {
    await taskToRemove.destroyPermanently();
  });
};

Testing

Let’s test our application.

Task editing test

It will also be important to check how the application behaves after it is restarted (i.e. whether the database actually works in offline mode).

Offline mode test

Summary

This example shows that creating an application with a local database is not a difficult task. Thanks to
ready-made database libraries, we have access to much faster and more programmer-friendly methods. However, this and the previous article are only an introduction to the topic, which is much more extensive and contains many interesting nuances.

Additional information available in the documentation: https://nozbe.github.io/WatermelonDB/

Adam Gałęcki

Digital Marketing Specialist | I create content and run SEO campaigns on a daily basis.

Leave a comment:

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

Case_studies_baner
Ostatnie komentarze