React Native Firebase Tutorial: Build CRUD Firestore App

by Didin J. on Oct 09, 2018 React Native Firebase Tutorial: Build CRUD Firestore App

The comprehensive step by step React Native and Firebase tutorial: Build CRUD (Create, Read, Update, Delete) Firestore Android and iOS App

The comprehensive step by step React Native Firebase tutorial: Build CRUD (Create, Read, Update, Delete) Firestore Android and iOS App. Previously, we have introduced a getting started tutorial of React Native that build the Android and iOS app for accessing RESTful API. Now, we have to create a tutorial for accessing Firebase Firestore Database using React Native and Firebase module. We will use Firebase Javascript library or node module for this tutorial.


Shortcut to the steps:


The scenario for this React Native Firebase tutorial is very simple, just a create, read, update, delete (CRUD) operation of Firebase Database. To handle this connection and CRUD operation we use the React Native Firebase Library. There's no difference in the Firebase Console configuration, they all still the same way as the previous configuration for another framework tutorial.

React Native Firebase - Library or Module

React Native Firebase module use the Firebase Native SDKs - this allows us to provide APIs to a vast majority of Firebase products and services.

The following tools, frameworks, and modules are required for this tutorial:

  1. React Native
  2. Node.js (NPM or Yarn)
  3. Firebase
  4. React Native Firebase library
  5. Android Studio or SDK for Android
  6. XCode for iOS
  7. Terminal (OSX/Linux) or Command Line (Windows)
  8. Text Editor or IDE (We are using Atom)

Before start to the main steps, make sure that you have installed Node.js and can run `npm` in the terminal or command line. To check the existing or installed Node.js environment open the terminal/command line then type this command.

node -v
v8.12.0
npm -v
6.4.1
yarn -v
1.10.1


Setup Google Firebase

Setup Google Firebase is very simple. Open your browser then go to Google Firebase Console and log in using your Google account.

React Native Firebase Tutorial: Build CRUD Firestore App - Firebase Console

Click the `Add Project` button, name it as you like (ours: Djamware) and checks all checkboxes. Finally, click `Create Project` button then wait a few seconds and click the `Continue` button. You will be redirected to this page.

React Native Firebase Tutorial: Build CRUD Firestore App - Firebase Project

Next, go to Project Settings by click Gear Icon. Scroll down then click `Add Firebase to your iOS App`. Now, follow the wizard to add new iOS App, download the configuration file then click next or skip until finished. Do the same way for Android Firebase App.

React Native Firebase Tutorial: Build CRUD Firestore App - Android Apps

Go to Develop menu on the left menu then click `Create Database` on Cloud Firestore page.

React Native Firebase Tutorial: Build CRUD Firestore App - Cloud Firestore Setup

Choose `Start in test mode` then click `Enabled` button.

React Native Firebase Tutorial: Build CRUD Firestore App - Database

Click `Add Collection` button to add the new collection for this tutorial. Fill collection ID (ours: 'boards') then click next. Add the required fields then click finish the wizard.

React Native Firebase Tutorial: Build CRUD Firestore App - New Collection

Now, the Firebase Firestore Database is ready to access. Make sure that your iOS and Android app match with the configuration files (json/plist).


Install React App Creator and Create App

The Create React Native App (create-react-native-app) is a tool for creating a React Native App. To install it, type this command in your App projects folder.

sudo npm install -g create-react-native-app

Then create a React Native App using this command.

create-react-native-app reactFirebaseCrud

If there question to install Expo, just type Y and choose a blank template because we will add the navigation later. That command will create a React Native app then install all required modules. The app or project folder will contain these folders and files.

|-- App.js
|-- app.json
|-- assets
|   |-- icon.png
|   `-- splash.png
|-- node_modules
`-- package.json

Next, go to the newly created React App folder.

cd reactFirebaseCrud

This React Native App is running via Expo app, before running on your Android or iOS device, make sure you have to install the Expo App to Android or Expo Client to iOS. Of course, that app is available in the App Store. So, we assume that you have installed the Expo App in your device then type this command to run the app.

npm start

or

yarn start

You will see this barcode and instruction in the terminal or command line.

React Native Firebase Tutorial: Build CRUD Firestore App - Expo

To open the app in the Android device, open the Expo App first then tap on Scan QR Code button. Scan the barcode in the terminal or command line then you will see the React Native Android App like this after waiting for minutes the build process.

React Native Firebase Tutorial: Build CRUD Firestore App - React Native in Expo

For iOS Device, press `s` from the keyboard to send React Native App URL to your Email or SMS. Enter your phone number or Email address (We use an email address) then press Enter. You will get this email to your mailbox.

React Native Firebase Tutorial: Build CRUD Firestore App - Expo Email

Choose open in Expo URL then open in your browser, that will be redirected to Expo App. In Expo App welcome screen, shake your phone to open the React Native App. Now, you will see this screen in your iOS device.

React Native Firebase Tutorial: Build CRUD Firestore App - Initial React Native App

This step for development purpose, we will show you how to build for production at the end of this article.


Add React Navigation Header and required Screen

Above generated React Native App just show blank app with plain text. Now, we will show you how to add the Navigation Header and Home Screen for your app. So, it will look like the Native App. In the terminal or command line type this command to install React Navigation (react-navigation) module.

npm install react-navigation --save

Next, create a folder for components and components files in the root of the app folder.

mkdir components
touch components/BoardScreen.js
touch components/BoardDetailScreen.js
touch components/AddBoardScreen.js
touch components/EditBoardScreen.js

Open and edit `components/BoardScreen.js` then add this React codes.

import React, { Component } from 'react';
import { Button, View, Text } from 'react-native';

class BoardScreen extends Component {
  static navigationOptions = {
    title: 'Board List',
  };
  render() {
    return (
      <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
        <Text>Board List</Text>
        <Button
          title="Go to Details"
          onPress={() => this.props.navigation.navigate('BoardDetails')}
        />
        <Button
          title="Go to Add Board"
          onPress={() => this.props.navigation.navigate('AddBoard')}
        />
        <Button
          title="Go to Edit Board"
          onPress={() => this.props.navigation.navigate('EditBoard')}
        />
      </View>
    );
  }
}

export default BoardScreen;

Open and edit `components/BoardDetailScreen.js` then add this React codes.

import React, { Component } from 'react';
import { Button, View, Text } from 'react-native';

class BoardDetailScreen extends Component {
  static navigationOptions = {
    title: 'Board Details',
  };
  render() {
    return (
      <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
        <Text>Board Details</Text>
        <Button
          title="Go to Details... again"
          onPress={() => this.props.navigation.push('BoardDetails')}
        />
        <Button
          title="Go to Home"
          onPress={() => this.props.navigation.navigate('Board')}
        />
        <Button
          title="Go back"
          onPress={() => this.props.navigation.goBack()}
        />
      </View>
    );
  }
}

export default BoardDetailScreen;

Open and edit `components/AddBoardScreen.js` then add this React codes.

import React, { Component } from 'react';
import { Button, View, Text } from 'react-native';

class AddBoardScreen extends Component {
  static navigationOptions = {
    title: 'Add Board',
  };
  render() {
    return (
      <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
        <Text>Add Board</Text>
        <Button
          title="Go to Add Board... again"
          onPress={() => this.props.navigation.push('AddBoard')}
        />
        <Button
          title="Go to Home"
          onPress={() => this.props.navigation.navigate('Board')}
        />
        <Button
          title="Go back"
          onPress={() => this.props.navigation.goBack()}
        />
      </View>
    );
  }
}

export default AddBoardScreen;

Open and edit `components/EditBoardScreen.js` then add this React codes.

import React, { Component } from 'react';
import { Button, View, Text } from 'react-native';

class EditBoardScreen extends Component {
  static navigationOptions = {
    title: 'Edit Board',
  };
  render() {
    return (
      <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
        <Text>Add Board</Text>
        <Button
          title="Go to Edit Board... again"
          onPress={() => this.props.navigation.push('EditBoard')}
        />
        <Button
          title="Go to Home"
          onPress={() => this.props.navigation.navigate('Board')}
        />
        <Button
          title="Go back"
          onPress={() => this.props.navigation.goBack()}
        />
      </View>
    );
  }
}

export default EditBoardScreen;

Next, open and edit `App.js` then add replace all codes with this.

import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { createStackNavigator } from 'react-navigation';
import BoardScreen from './components/BoardScreen';
import BoardDetailScreen from './components/BoardDetailScreen';
import AddBoardScreen from './components/AddBoardScreen';
import EditBoardScreen from './components/EditBoardScreen';

const RootStack = createStackNavigator(
  {
    Board: BoardScreen,
    BoardDetails: BoardDetailScreen,
    AddBoard: AddBoardScreen,
    EditBoard: EditBoardScreen,
  },
  {
    initialRouteName: 'Board',
    navigationOptions: {
      headerStyle: {
        backgroundColor: '#777777',
      },
      headerTintColor: '#fff',
      headerTitleStyle: {
        fontWeight: 'bold',
      },
    },
  },
);

export default class App extends React.Component {
  render() {
    return <RootStack />;
  }
}

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

When the files are saved, the Expo app will refresh the React Native App automatically. Now, the app will look like this.

React Native Firebase Tutorial: Build CRUD Firestore App - Sample App


Install and Configure React Native Firebase

We use Firebase module to access the Firebase Firestore Database. For that, type this command to install the module.

npm install --save firebase

Next, create a new file `Firebase.js` in the root of the project folder for Firebase configuration.

touch Firebase.js

Open and edit `Firebase.js` then replace all codes with this Firebase configuration that contains apiKey, authDomain, databaseURL, projectId, storageBucket, and messagingSenderId.

import * as firebase from 'firebase';
import firestore from 'firebase/firestore'

const settings = {timestampsInSnapshots: true};

const config = {
  apiKey: "YOUR_API_KEY",
  authDomain: "YOUR_AUTH_DOMAIN",
  databaseURL: "YOUR_DATABASE_URL",
  projectId: "YOUR_PROJECT_ID",
  storageBucket: "YOUR_STORAGE_BUCKET",
  messagingSenderId: "YOUR_MESSAGING_ID"
};
firebase.initializeApp(config);

firebase.firestore().settings(settings);

export default firebase;

You can find or get those configuration parameters by click on Web Setup button in the page authentication of Firebase Console.


Show List of Board using React Native Elements

To show the list of the board in the Board components, open and edit `components/BoardScreen.js` then add/replace these imports of StyleSheet, ScrollView, ActivityIndicator, View, Text (react-native), List, ListItem, Button, Icon (react-native-elements), and Firebase.

import React, { Component } from 'react';
import { StyleSheet, ScrollView, ActivityIndicator, View, Text } from 'react-native';
import { List, ListItem, Button, Icon } from 'react-native-elements';
import firebase from '../Firebase';

Replace `navigationOptions` with this.

static navigationOptions = ({ navigation }) => {
  return {
    title: 'Board List',
    headerRight: (
      <Button
        buttonStyle={{ padding: 0, backgroundColor: 'transparent' }}
        icon={{ name: 'add-circle', style: { marginRight: 0, fontSize: 28 } }}
        onPress={() => { navigation.push('AddBoard') }}
      />
    ),
  };
};

Declare all required variable for Firebase Firestore data inside the constructor.

constructor() {
  super();
  this.ref = firebase.firestore().collection('boards');
  this.unsubscribe = null;
  this.state = {
    isLoading: true,
    boards: []
  };
}

Add the function for extract Firebase response to the state.

onCollectionUpdate = (querySnapshot) => {
  const boards = [];
  querySnapshot.forEach((doc) => {
    const { title, description, author } = doc.data();
    boards.push({
      key: doc.id,
      doc, // DocumentSnapshot
      title,
      description,
      author,
    });
  });
  this.setState({
    boards,
    isLoading: false,
 });
}

Call that function inside `componentDidMount` function.

componentDidMount() {
  this.unsubscribe = this.ref.onSnapshot(this.onCollectionUpdate);
}

Replace all view render with this.

render() {
  if(this.state.isLoading){
    return(
      <View style={styles.activity}>
        <ActivityIndicator size="large" color="#0000ff"/>
      </View>
    )
  }
  return (
    <ScrollView style={styles.container}>
      <List>
        {
          this.state.boards.map((item, i) => (
            <ListItem
              key={i}
              title={item.title}
              leftIcon={{name: 'book', type: 'font-awesome'}}
              onPress={() => {
                this.props.navigation.navigate('BoardDetails', {
                  boardkey: `${JSON.stringify(item.key)}`,
                });
              }}
            />
          ))
        }
      </List>
    </ScrollView>
  );
}

Add styles variables before the export code.

const styles = StyleSheet.create({
  container: {
   flex: 1,
   paddingBottom: 22
  },
  item: {
    padding: 10,
    fontSize: 18,
    height: 44,
  },
  activity: {
    position: 'absolute',
    left: 0,
    right: 0,
    top: 0,
    bottom: 0,
    alignItems: 'center',
    justifyContent: 'center'
  }
})


Show Board Details using React Native Elements

To show the details of the board in the BoardDetails components, open and edit `components/BoardDetailScreen.js` then add/replace these imports of StyleSheet, ScrollView, ActivityIndicator, View (react-native), List, ListItem, Text, Card, Button (react-native-elements), and Firebase.

import React, { Component } from 'react';
import { StyleSheet, ScrollView, ActivityIndicator, View } from 'react-native';
import { List, ListItem, Text, Card, Button } from 'react-native-elements';
import firebase from '../Firebase';

Add or replace all of this required functions for show detail of board from Firebase Firestore Database.

static navigationOptions = {
  title: 'Board Details',
};

constructor() {
  super();
  this.state = {
    isLoading: true,
    board: {},
    key: ''
  };
}

componentDidMount() {
  const { navigation } = this.props;
  const ref = firebase.firestore().collection('boards').doc(JSON.parse(navigation.getParam('boardkey')));
  ref.get().then((doc) => {
    if (doc.exists) {
      this.setState({
        board: doc.data(),
        key: doc.id,
        isLoading: false
      });
    } else {
      console.log("No such document!");
    }
  });
}

deleteBoard(key) {
  const { navigation } = this.props;
  this.setState({
    isLoading: true
  });
  firebase.firestore().collection('boards').doc(key).delete().then(() => {
    console.log("Document successfully deleted!");
    this.setState({
      isLoading: false
    });
    navigation.navigate('Board');
  }).catch((error) => {
    console.error("Error removing document: ", error);
    this.setState({
      isLoading: false
    });
  });
}

Replace all rendered view with this.

render() {
  if(this.state.isLoading){
    return(
      <View style={styles.activity}>
        <ActivityIndicator size="large" color="#0000ff" />
      </View>
    )
  }
  return (
    <ScrollView>
      <Card style={styles.container}>
        <View style={styles.subContainer}>
          <View>
            <Text h3>{this.state.board.title}</Text>
          </View>
          <View>
            <Text h5>{this.state.board.description}</Text>
          </View>
          <View>
            <Text h4>{this.state.board.author}</Text>
          </View>
        </View>
        <View style={styles.detailButton}>
          <Button
            large
            backgroundColor={'#CCCCCC'}
            leftIcon={{name: 'edit'}}
            title='Edit'
            onPress={() => {
              this.props.navigation.navigate('EditBoard', {
                boardkey: `${JSON.stringify(this.state.key)}`,
              });
            }} />
        </View>
        <View style={styles.detailButton}>
          <Button
            large
            backgroundColor={'#999999'}
            color={'#FFFFFF'}
            leftIcon={{name: 'delete'}}
            title='Delete'
            onPress={() => this.deleteBoard(this.state.key)} />
        </View>
      </Card>
    </ScrollView>
  );
}

Add the styles before the export code.

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20
  },
  subContainer: {
    flex: 1,
    paddingBottom: 20,
    borderBottomWidth: 2,
    borderBottomColor: '#CCCCCC',
  },
  activity: {
    position: 'absolute',
    left: 0,
    right: 0,
    top: 0,
    bottom: 0,
    alignItems: 'center',
    justifyContent: 'center'
  },
  detailButton: {
    marginTop: 10
  }
})


Add Board using React Native Elements

To add a new board, open and edit `components/AddBoardScreen.js` then add/replace these imports of StyleSheet, ScrollView, ActivityIndicator, View, TextInput (react-native), Button (react-native-elements), and Firebase.

import React, { Component } from 'react';
import { StyleSheet, ScrollView, ActivityIndicator, View, TextInput } from 'react-native';
import { Button } from 'react-native-elements';
import firebase from '../Firebase';

Declare or add the required functions and variables.

static navigationOptions = {
  title: 'Add Board',
};
constructor() {
  super();
  this.ref = firebase.firestore().collection('boards');
  this.state = {
    title: '',
    description: '',
    author: '',
    isLoading: false,
  };
}
updateTextInput = (text, field) => {
  const state = this.state
  state[field] = text;
  this.setState(state);
}

saveBoard() {
  this.setState({
    isLoading: true,
  });
  this.ref.add({
    title: this.state.title,
    description: this.state.description,
    author: this.state.author,
  }).then((docRef) => {
    this.setState({
      title: '',
      description: '',
      author: '',
      isLoading: false,
    });
    this.props.navigation.goBack();
  })
  .catch((error) => {
    console.error("Error adding document: ", error);
    this.setState({
      isLoading: false,
    });
  });
}

Replace all rendered view with this.

render() {
  if(this.state.isLoading){
    return(
      <View style={styles.activity}>
        <ActivityIndicator size="large" color="#0000ff"/>
      </View>
    )
  }
  return (
    <ScrollView style={styles.container}>
      <View style={styles.subContainer}>
        <TextInput
            placeholder={'Title'}
            value={this.state.title}
            onChangeText={(text) => this.updateTextInput(text, 'title')}
        />
      </View>
      <View style={styles.subContainer}>
        <TextInput
            multiline={true}
            numberOfLines={4}
            placeholder={'Description'}
            value={this.state.description}
            onChangeText={(text) => this.updateTextInput(text, 'description')}
        />
      </View>
      <View style={styles.subContainer}>
        <TextInput
            placeholder={'Author'}
            value={this.state.author}
            onChangeText={(text) => this.updateTextInput(text, 'author')}
        />
      </View>
      <View style={styles.button}>
        <Button
          large
          leftIcon={{name: 'save'}}
          title='Save'
          onPress={() => this.saveBoard()} />
      </View>
    </ScrollView>
  );
}

Add the styles codes before the export code.

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20
  },
  subContainer: {
    flex: 1,
    marginBottom: 20,
    padding: 5,
    borderBottomWidth: 2,
    borderBottomColor: '#CCCCCC',
  },
  activity: {
    position: 'absolute',
    left: 0,
    right: 0,
    top: 0,
    bottom: 0,
    alignItems: 'center',
    justifyContent: 'center'
  }
})


Edit and Delete Board using React Native Elements

To edit the existing board, open and edit `components/EditBoardScreen` then replace all imports with these StyleSheet, ScrollView, ActivityIndicator, View, TextInput (react-native), Button (react-native-elements), and Firebase .

import React, { Component } from 'react';
import { StyleSheet, ScrollView, ActivityIndicator, View, TextInput } from 'react-native';
import { Button } from 'react-native-elements';
import firebase from '../Firebase';

Declare all required functions and variables.

static navigationOptions = {
  title: 'Edit Board',
};
constructor() {
  super();
  this.state = {
    key: '',
    isLoading: true,
    title: '',
    description: '',
    author: ''
  };
}
componentDidMount() {
  const { navigation } = this.props;
  const ref = firebase.firestore().collection('boards').doc(JSON.parse(navigation.getParam('boardkey')));
  ref.get().then((doc) => {
    if (doc.exists) {
      const board = doc.data();
      this.setState({
        key: doc.id,
        title: board.title,
        description: board.description,
        author: board.author,
        isLoading: false
      });
    } else {
      console.log("No such document!");
    }
  });
}

updateTextInput = (text, field) => {
  const state = this.state
  state[field] = text;
  this.setState(state);
}

updateBoard() {
  this.setState({
    isLoading: true,
  });
  const { navigation } = this.props;
  const updateRef = firebase.firestore().collection('boards').doc(this.state.key);
  updateRef.set({
    title: this.state.title,
    description: this.state.description,
    author: this.state.author,
  }).then((docRef) => {
    this.setState({
      key: '',
      title: '',
      description: '',
      author: '',
      isLoading: false,
    });
    this.props.navigation.navigate('Board');
  })
  .catch((error) => {
    console.error("Error adding document: ", error);
    this.setState({
      isLoading: false,
    });
  });
}

Replace all rendered view with these codes.

render() {
  if(this.state.isLoading){
    return(
      <View style={styles.activity}>
        <ActivityIndicator size="large" color="#0000ff"/>
      </View>
    )
  }
  return (
    <ScrollView style={styles.container}>
      <View style={styles.subContainer}>
        <TextInput
            placeholder={'Title'}
            value={this.state.title}
            onChangeText={(text) => this.updateTextInput(text, 'title')}
        />
      </View>
      <View style={styles.subContainer}>
        <TextInput
            multiline={true}
            numberOfLines={4}
            placeholder={'Description'}
            value={this.state.description}
            onChangeText={(text) => this.updateTextInput(text, 'description')}
        />
      </View>
      <View style={styles.subContainer}>
        <TextInput
            placeholder={'Author'}
            value={this.state.author}
            onChangeText={(text) => this.updateTextInput(text, 'author')}
        />
      </View>
      <View style={styles.button}>
        <Button
          large
          leftIcon={{name: 'update'}}
          title='Update'
          onPress={() => this.updateBoard()} />
      </View>
    </ScrollView>
  );
}

Add the styles codes before the extract codes.

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20
  },
  subContainer: {
    flex: 1,
    marginBottom: 20,
    padding: 5,
    borderBottomWidth: 2,
    borderBottomColor: '#CCCCCC',
  },
  activity: {
    position: 'absolute',
    left: 0,
    right: 0,
    top: 0,
    bottom: 0,
    alignItems: 'center',
    justifyContent: 'center'
  }
})


Run and Test React Native Firebase App

To run the React Native Application using Expo app. Type this command.

npm start

Follow the previous instruction for running the React Native app in Expo app. Finally, you will see the whole React Native app like this.

React Native Firebase Tutorial: Build CRUD Firestore App - Board List
React Native Firebase Tutorial: Build CRUD Firestore App - Board Details
React Native Firebase Tutorial: Build CRUD Firestore App - Add Board
React Native Firebase Tutorial: Build CRUD Firestore App - Edit Board

Notes: If the Firebase module not working in the Native iOS or Android Build using Expo, you can use React Native Firebase (RNFirebase) module for it. Of course, you have to follow the guide for setup the Firebase in the Firebase console. For the React Native app just change the firebase module to react-native-firebase module and also change the import inside React Native components.

That it's, the React Native Firebase Tutorial: Build CRUD Firestore App. You can find the full source code from our GitHub.

That just the basic. If you need more deep learning about MERN Stack, React.js or React Native you can take the following cheap course:

Thanks!