DEV Community

Cover image for Storing and retrieving data for React Native apps with Firebase
Brian Neville-O'Neill
Brian Neville-O'Neill

Posted on • Originally published at blog.logrocket.com on

Storing and retrieving data for React Native apps with Firebase

Written by Yusuff Faruq✏️

Storing and retrieving user-generated data is pretty common in mobile and web apps nowadays, and as such, there are different services that give mobile and web developers the ability to store data.

Among these services is Google’s Firebase. Firebase is a BaaS (backend-as-a-service), which means it allows both web and mobile developers to perform common backend tasks like user authentication and creating databases with no need for maintenance or upkeep.

React Native, on the other hand, is a platform that allows developers to use their knowledge of React, a popular frontend framework, to build native apps that work across both Android and iOS devices. React Native improves productivity as developers do not have to manage two different codebases for both the iOS and Android versions of a particular app.

In this article, you will learn how to store, retrieve, and update user-generated data with Firebase. At the end of this article, we will have built a to-do Android app that saves and retrieves the different to-do items in a database, using a database service provided by Firebase.

LogRocket Free Trial Banner

Prerequisites

To follow along perfectly with this article, you should have a basic understanding of React and React Native.

Setting up

Firstly, let’s create a new React Native project. You can visit the official React Native Documentation for guides on setting up the basics.

Once that is done, you can now create a new React Native project:

npx react-native init ToDoApp
Enter fullscreen mode Exit fullscreen mode

We also need to install the dependencies we will use in the project. These dependencies are:

Run these commands in your terminal to install these dependencies:

npm install --save firebase
npm install react-native-check-box --save
Enter fullscreen mode Exit fullscreen mode

Now that the setup is done, we can start building our app!

Setting up Firebase

Before we start writing our code, we need to do some setup for Firebase to work perfectly with our app.

Create a src folder in the root directory and create a file called config.js in it.

Now, go to the Firebase website.

The Firebase Homepage

Click on the Get Started button and you will be taken to a page where you can create a new project. Once you are done creating a new project, you should be taken to a dashboard page similar to the image below.

The Firebase Get Started Page

Select the Database option from the side menu. After that is done, you should now be able to select from the two database options that Firebase provides: Realtime Database and Firestore , both of which have similar functionality.

If you are unsure of which database service to use, you can check out this guide by Firebase on choosing a database. In this article, we will be making use of the Realtime Database.

Once you select the Realtime Database option, a modal for setting the security rules for your database should pop up. Select Start in test mode. Now you should be at a page that shows the data in your database (which is empty for now).

Page Reflecting Empty Databse

Now that the database has been created, we need to get some information (Firebase configuration) from our Firebase project. These pieces of information or data will help link our code to our Firebase project.

To get our Firebase configuration details, go back to the project overview page and add a web app to the Firebase project. After registering the app, you should get the configuration details in the form of a JavaScript object.

const firebaseConfig = {
  apiKey: 'AIzaXXXXXXXXXXXXXXXXXXXXXXX',
  authDomain: 'test-XXXX.firebaseapp.com',
  databaseURL: 'https://test-XXXXXX.firebaseio.com',
  projectId: 'test-XXXX',
  storageBucket: 'test-XXXX.appspot.com',
  messagingSenderId: 'XXXXXXX',
  appId: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
};
Enter fullscreen mode Exit fullscreen mode

Now open your config.js file and import Firebase. After importing Firebase, you need to copy and paste the Firebase configuration details in the file.

import Firebase from "firebase";
const firebaseConfig = {
    apiKey: 'AIzaXXXXXXXXXXXXXXXXXXXXXXX',
    authDomain: 'test-XXXX.firebaseapp.com',
    databaseURL: 'https://test-XXXXXX.firebaseio.com',
    projectId: 'test-XXXX',
    storageBucket: 'test-XXXX.appspot.com',
    messagingSenderId: 'XXXXXXX',
    appId: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
    measurementId: "X-XXXXXXXXXX"
  };
Enter fullscreen mode Exit fullscreen mode

Now we just need to initialize a Firebase app using the configuration details we got from Firebase. Once that is done, we will get a reference to the database service provided by Firebase for our app and export it.

const app = Firebase.initializeApp(firebaseConfig);
export const db = app.database();
Enter fullscreen mode Exit fullscreen mode

We can now read and write to our real-time database!

Creating our components

Let’s begin to write code!

Note : I will be writing all my code in my app.js file.

First of all, we need a component to display a to-do item and manage the state of that to-do item. This component will be responsible for indicating whether or not a certain to-do task has been done. We will call this component ToDoItem.

import React from 'react';
import {
  StyleSheet,
  Alert,
  View,
  Text,
  Button,
  ScrollView,
  TextInput,
} from 'react-native';
import CheckBox from 'react-native-check-box';

const ToDoItem = () => {
  return (
    <View>
      <CheckBox
        checkBoxColor="skyblue"
      />
      <Text>
        A random To-Do item
      </Text>
    </View>
  );
};
Enter fullscreen mode Exit fullscreen mode

As you can see, we imported the CheckBox component from react-native-check-box. This checkbox will be used to indicate whether or not a particular to-do task has been accomplished.

Now let us style our component! React Native comes with a StyleSheet that we can use to define styles for our different elements.

const styles = StyleSheet.create({
  todoItem: {
    flexDirection: 'row',
    marginVertical: 10,
    alignItems: 'center',

  },
  todoText: {
    borderColor: '#afafaf',
    paddingHorizontal: 5,
    paddingVertical: 7,
    borderWidth: 1,
    borderRadius: 5,
    marginRight: 10,
    minWidth: "50%",
    textAlign: "center"
  },
});
Enter fullscreen mode Exit fullscreen mode

We can now apply these styles in the style attribute of each element like so:

<View style = {styles.todoItem}></View>
Enter fullscreen mode Exit fullscreen mode

We want to style our ToDoItem component differently once the checkbox has been checked. To do this, we will create a state called doneState to manage the state of the to-do task — whether the task has been accomplished or not — using the useState Hook from React.

Firstly, import the useState Hook alongside React like this:

import React, {useState} from 'react';
Enter fullscreen mode Exit fullscreen mode

Now, with the useState Hook imported, we can create our state like this:

const [doneState, setDone] = useState(false);
Enter fullscreen mode Exit fullscreen mode

If you are not familiar with Hooks, you should check out the React documentation.

Basically, we created a state called doneState, which has an initial value of false. If, at some point in the future, we want to update the value of doneState and set it to true, we can always use setDone:

setDone(true);
Enter fullscreen mode Exit fullscreen mode

The CheckBox comes with a prop called isChecked, which enables us to control when the checkbox should be checked. In this case, we want to check our checkbox when the to-do task has been done (when the value of doneState is true).

Since the isChecked prop of our checkbox component is dependent on the value of doneState, we want to change the value of doneState to the opposite of the current value whenever the checkbox is clicked.

This can be done with the onClick prop that comes with the CheckBox component. We will create an event handler called onCheck for the onClick event of the checkbox.

const onCheck = () => {
    setDone(!doneState);
};
Enter fullscreen mode Exit fullscreen mode

Now, as I pointed out before, we want to style our ToDoItem component differently once it has been checked. Once it has been checked, the opacity of the ToDoItem will reduce and the checkbox will be disabled. Let us implement that:

return (
    <View style={styles.todoItem}>
      <CheckBox
        checkBoxColor="skyblue"
        onClick={onCheck}
        isChecked={doneState}
        disabled={doneState}
      />
      <Text style={[styles.todoText, {opacity: doneState ? 0.2 : 1}]}>
        A random To-Do item
      </Text>
    </View>
  );
};
Enter fullscreen mode Exit fullscreen mode

Now that we have the basic parts of our ToDoItem component ready, let us create our App component, which will serve as the home screen of our app. Our App component will be a class-based component, but you can make yours into a functional component if you wish.

We need two pieces of state for our App component: todos, which contains all the to-do items, and presentToDo, which will be the current to-do item being added. We also need to add a View component to contain the list of to-do items; a TextInput component in which the user will type out a new to-do item; a button to add a to-do item; and another button to clear our database, thereby getting rid of all the to-do items.

With some styling, our App component should look like this:

class App extends React.Component {
  constructor() {
    super();
    this.state = {
      todos: {},
      presentToDo: '',
    };
  }
  componentDidMount() {
  }
  addNewTodo() {
  }
  clearTodos() {
  }
  render() {
    return (
      <ScrollView
        style={styles.container}
        contentContainerStyle={styles.contentContainerStyle}>
        <View>
          {/*Empty view: will contain to-do items soon*/}
        </View>
        <TextInput
          placeholder="Add new Todo"
          value={this.state.presentToDo}
          style={styles.textInput}
          onChangeText={e => {
            this.setState({
              presentToDo: e,
            });
          }}
          onSubmitEditing = {this.addNewTodo}
        />
        <Button
          title="Add new To do item"
          onPress={this.addNewTodo}
          color="lightgreen"
        />
        <View style={{marginTop: 20}}>
          <Button title="Clear todos" onPress={this.clearTodos} color="red" />
        </View>
      </ScrollView>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: 'white',
  },
  contentContainerStyle: {
    alignItems: 'center',
  },
  textInput: {
    borderWidth: 1,
    borderColor: '#afafaf',
    width: '80%',
    borderRadius: 5,
    paddingHorizontal: 10,
    marginVertical: 20,
    fontSize: 20,
  },
  todoItem: {
    flexDirection: 'row',
    marginVertical: 10,
    alignItems: 'center',
  },
  todoText: {
    borderColor: '#afafaf',
    paddingHorizontal: 5,
    paddingVertical: 7,
    borderWidth: 1,
    borderRadius: 5,
    marginRight: 10,
    minWidth: '50%',
    textAlign: 'center',
  },
});
Enter fullscreen mode Exit fullscreen mode

Working with Firebase

Now, we are ready to start working with some Firebase code.

First, we need to import the database from the config.js file:

import {db} from './src/config';
Enter fullscreen mode Exit fullscreen mode

Before we move on, we need to understand how data is structured in the Realtime Database that Firebase provides. The data is literally structured as a JSON tree.

According to the documentation:

“All Firebase Realtime Database data is stored as JSON objects. You can think of the database as a cloud-hosted JSON tree. Unlike a SQL database, there are no tables or records. When you add data to the JSON tree, it becomes a node in the existing JSON structure with an associated key.”

This makes working with the Realtime Database easy and familiar.

When our App component mounts, the first thing we want to do is get all the current to-do items and add them to our todos state object. To do that, we need to get a reference to a particular location on our database. The path to this location will be called todos. Once we have done that, we need to listen for changes to the contents of that path or location. This is very similar to listening for events in vanilla JavaScript. In this case, we will be listening for an event called value.

Again, according to the Firebase documentation:

“[This event allows us] to read a static snapshot of the contents at a given path, as they existed at the time of the event. This method is triggered once when the listener is attached and again every time the data, including children, changes.”

The event callback function is passed a snapshot that contains all the data at that location, including child data. We can now grab the data from the snapshot using the val() method. This data is usually in the form of a JavaScript object.

componentDidMount() {
    db.ref('/todos').on('value', querySnapShot => {
      let data = querySnapShot.val() ? querySnapShot.val() : {};
      let todoItems = {...data};
      this.setState({
        todos: todoItems,
      });
    });
  }
Enter fullscreen mode Exit fullscreen mode

We used the ternary operator to assign an empty object to the data variable if we have no data in the path we are reading from.

We also need to add some code to our addNewTodo() method. We want each to-do item to have a name or title and a value to indicate whether or not the relevant to-do task has been accomplished.

Therefore, we will use an object to represent each to-do item in our database. To add data to a particular location, we need to reference the path to the location to which we want to add the data and use the push() method. We also want the user to receive an alert once they have added a new to-do item.

addNewTodo() {
    db.ref('/todos').push({
      done: false,
      todoItem: this.state.presentToDo,
    });
    Alert.alert('Action!', 'A new To-do item was created');
    this.setState({
      presentToDo: '',
    });
  }
Enter fullscreen mode Exit fullscreen mode

To clear the data in our todos path, we just need to get a reference to that location and use the remove() method.

clearTodos() {
    db.ref('/todos').remove();
  }
Enter fullscreen mode Exit fullscreen mode

Now, try adding a to-do item using the TextInput component. You should notice a change to your database in your database dashboard or console.

To-do Item Added To The Database

If we logged the list of to-do items to the console, we should have something like this:

{
  "-LzTe5MyZ7aUL7y-iFku": {
    "done": false,
    "todoItem": "HEY"
  }
}
Enter fullscreen mode Exit fullscreen mode

If you observe this result, you will notice that the list of to-do items is an object that has key names equal to a unique ID assigned to all to-do items by Firebase. The value of each to-do item is an object, which has a similar format to what we specified in our addNewTodo() method.

With this information in hand, we can now extract the keys from our todos state object and pass in the necessary props to each ToDoItem component. Let’s extract the keys and assign them to a variable called todosKeys:

render() {
    let todosKeys = Object.keys(this.state.todos);
    return (
      <ScrollView ...>
        ...
      </ScrollView>
    );
}
Enter fullscreen mode Exit fullscreen mode

Let’s add this to the empty View component in the App component:

<View>
  {todosKeys.length > 0 ? (
    todosKeys.map(key => (
      <ToDoItem
        key={key}
        id={key}
        todoItem={this.state.todos[key]}
      />
    ))
  ) : (
        <Text>No todo item</Text>
  )}
</View>
Enter fullscreen mode Exit fullscreen mode

Now, we need to make some changes to our ToDoItem component. Since our to-do item components now have access to their appropriate names and state, we can get rid of the placeholder values. Our ToDoItem component should look like this now:

const ToDoItem = ({todoItem: {todoItem: name, done}, id}) => {
  const [doneState, setDone] = useState(done);
  const onCheck = () => {
    setDone(!doneState);
  };
  return (
    <View style={styles.todoItem}>
      <CheckBox
        checkBoxColor="skyblue"
        onClick={onCheck}
        isChecked={doneState}
        disabled={doneState}
      />
      <Text style={[styles.todoText, {opacity: doneState ? 0.2 : 1}]}>
        {name}
      </Text>
    </View>
  );
};
Enter fullscreen mode Exit fullscreen mode

Right now, our simple to-do app is mostly done. There is still a slight problem, however: if you check a to-do item as done and you reload the app, that to-do item will be back to its original unchecked state. What’s the purpose of having a database we can’t update?

We will, therefore, need to update the value of done on the database whenever the checkbox has been checked. We can do this with the update() method. To update a particular field, you simply have to pass in an object containing the updated fields:

const onCheck = () => {
    setDone(!doneState);
    db.ref('/todos').update({
      [id]: {
        todoItem: name,
        done: !doneState,
      },
    });
  };
  return (
    <View style={styles.todoItem}>
      <CheckBox
        checkBoxColor="skyblue"
        onClick={onCheck}
        isChecked={doneState}
        disabled={doneState}
      />
        ...
      </View>
    )
  }
Enter fullscreen mode Exit fullscreen mode

Now whenever you check a particular to-do item as done, the to-do item’s value of done should be updated in the database.

Conclusion

That’s it! We have created our to-do Android app with a backend powered by Firebase!

Normally, you would add some authentication to this app so that users will be able to log in on any supported device in the world and still have access to their data. This can also be done with Firebase, and you can learn more about Firebase in the documentation.


Plug: LogRocket, a DVR for web apps

 
LogRocket Dashboard Free Trial Banner
 
LogRocket is a frontend logging tool that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.
 
In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.
 
Try it for free.


The post Storing and retrieving data for React Native apps with Firebase appeared first on LogRocket Blog.

Top comments (0)