Chat app with React Native (part 3) - Create Firestore collections to store chat rooms

Published on Apr 21, 2020

12 min read



In part 2 of this series, we made progress with the chat app by adding email authentication using the real-time auth service from Firebase. This ensures that we have a system in place to authenticate users.

In part 3, let's extend our progress by creating and storing chat rooms in real-time using Firestore data storage, provided by the Firebase. We'll continue to explore different tips and best practices for using react-navigation. For example, we'll create a modal screen and expand the home stack created in the previous post.

How to share common header options styles using screenOptions


Let us start with a simple yet a very common technique to modify header bar options across various screens in a stack navigator. This technique is a common practice that you will find using yourself with react-navigation.

Start by modifying the header in the home stack such that any route that is wrapped by HomeStack navigator is going to have a similar background color, header tint color, and font size.

This is a common practice to configure the header bar and share style properties among different routes in the same stack navigator.

Open src/navigation/HomeStack.js file and add a screenOptions prop to Stack.Navigator.

1export default function HomeStack() {
2 return (
3 <Stack.Navigator
4 screenOptions={{
5 headerStyle: {
6 backgroundColor: '#6646ee'
7 },
8 headerTintColor: '#ffffff',
9 headerTitleStyle: {
10 fontSize: 22
11 }
12 }}
13 >
14 <Stack.Screen name="Home" component={HomeScreen} />
15 </Stack.Navigator>
16 );

Go back to the simulator and you are going to get the following result.

Add a separate stack navigator for modal screen


In this section, you are going to create a modal screen that will allow the user in the app to create a new chat room. Later in this tutorial, the name of the chat room entered from this screen is going to be stored in the Firestore collection.

A modal screen displays the content that temporarily blocks interactions with the main view. It is like a popup and usually has a different transition in terms of opening and closing of the screen. This mode of the screen is generally used to display one specific piece of information.

Here's a flowchart to help visualize the navigation flow we're trying to achieve by the end of this section.

Start by creating a new screen file called AddRoomScreen.js inside src/screens directory with the following content.

1import React from 'react';
2import { View, Text } from 'react-native';
3import FormButton from '../components/FormButton';
5export default function AddRoomScreen({ navigation }) {
6 return (
7 <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
8 <Text>Create a new chat room</Text>
9 <FormButton
10 mode="contained"
11 title="Close Modal"
12 onPress={() => navigation.goBack()}
13 />
14 </View>
15 );

Right now, focus adding this modal screen to the Home stack navigator rather than its contents.

Also, add a temporary button to open the modal screen in the HomeScreen.js file.

1import React, { useContext } from 'react';
2import { View, StyleSheet } from 'react-native';
3import { Title } from 'react-native-paper';
4import { AuthContext } from '../navigation/AuthProvider';
5import FormButton from '../components/FormButton';
7export default function HomeScreen({ navigation }) {
8 const { user, logout } = useContext(AuthContext);
10 return (
11 <View style={styles.container}>
12 <Title>Home Screen</Title>
13 <Title>All chat rooms will be listed here</Title>
14 <Title>{user.uid}</Title>
15 <FormButton
16 modeValue="contained"
17 title="Logout"
18 onPress={() => logout()}
19 />
20 <FormButton
21 modeValue="contained"
22 title="Add Room"
23 onPress={() => navigation.navigate('AddRoom')}
24 />
25 </View>
26 );

Now open src/navigation/HomeStack.js file. In order to keep the modal as a separate route from other home stack routes (such as HomeScreen), let us create two new stack navigators in this file.

Start by importing the modal screen with the rest of the routes and create two new stack navigator instances. You can give a custom name to each instance.

1// ... rest of the import statements
2import AddRoomScreen from '../screens/AddRoomScreen';
4// create two new instances
5const ChatAppStack = createStackNavigator();
6const ModalStack = createStackNavigator();

From the snippet, the ChatAppStack is going to contain those screens routes that are do not require the use of a modal screen and focus only on the chat app features.

1function ChatApp() {
2 return (
3 <ChatAppStack.Navigator
4 screenOptions={{
5 headerStyle: {
6 backgroundColor: '#6646ee'
7 },
8 headerTintColor: '#ffffff',
9 headerTitleStyle: {
10 fontSize: 22
11 }
12 }}
13 >
14 <ChatAppStack.Screen name="Home" component={HomeScreen} />
15 </ChatAppStack.Navigator>
16 );

The Modal stack is going to wrap both the ChatAppStack and the modal screen as routes. Modify the exported HomeStack as below. Make sure to set the mode of ModalStack.Navigator to modal and headerMode to none.

1export default function HomeStack() {
2 return (
3 <ModalStack.Navigator mode="modal" headerMode="none">
4 <ModalStack.Screen name="ChatApp" component={ChatApp} />
5 <ModalStack.Screen name="AddRoom" component={AddRoomScreen} />
6 </ModalStack.Navigator>
7 );

Go to the simulator. You are going to find the Add room button on the home screen as shown below.

Click on the button and notice the transition when the modal screen pops up.

How to add an icon in the header bar


The modal stack is working as per the requirement. But the way the user would navigate from the home screen to modal is not by clicking a button in the center of the home screen. This action is going to be done by clicking an icon button from the header.

Luckily, the react-navigation library provides props for us to implement this action without any hassle. Import IconButton from react-native-paper UI library inside the file src/navigation/HomeStack.js.

1// rest of the imports
2import { IconButton } from 'react-native-paper';

Then add an options prop with a function such that you are able to pass navigation prop reference. Add the following code to the HomeScreen route.

2 name="Home"
3 component={HomeScreen}
4 options={({ navigation }) => ({
5 headerRight: () => (
6 <IconButton
7 icon="message-plus"
8 size={28}
9 color="#ffffff"
10 onPress={() => navigation.navigate('AddRoom')}
11 />
12 )
13 })}

Also, remove FormButton in HomeScreen.js you create in the previous section.

Here is how the home screen in the simulator looks like after this step.

Complete the modal screen


Right now the modal screen just displays a line of text and a close button but the real functionality this screen has to provide is to allow the user to enter the name of the chat room using an input field. Then, using a form button, add the chat room name in a Firestore collection.

Open AddRoomScreen.js and start by modifying the import statements.

1import React, { useState } from 'react';
2import { View, StyleSheet } from 'react-native';
3import { IconButton, Title } from 'react-native-paper';
4import FormInput from '../components/FormInput';
5import FormButton from '../components/FormButton';

Then, to add a chat room, define a state variable called roomName inside a functional component AddRoomScreen.

To modify the JSX returned from this component. Make sure to add a close button at the right corner of the screen and using custom components you can add the input field as well as the submit button.

1export default function AddRoomScreen({ navigation }) {
2 const [roomName, setRoomName] = useState('');
3 // ... Firestore query will come here later
5 return (
6 <View style={styles.rootContainer}>
7 <View style={styles.closeButtonContainer}>
8 <IconButton
9 icon="close-circle"
10 size={36}
11 color="#6646ee"
12 onPress={() => navigation.goBack()}
13 />
14 </View>
15 <View style={styles.innerContainer}>
16 <Title style={styles.title}>Create a new chat room</Title>
17 <FormInput
18 labelName="Room Name"
19 value={roomName}
20 onChangeText={text => setRoomName(text)}
21 clearButtonMode="while-editing"
22 />
23 <FormButton
24 title="Create"
25 modeValue="contained"
26 labelStyle={styles.buttonLabel}
27 onPress={() => handleButtonPress()}
28 disabled={roomName.length === 0}
29 />
30 </View>
31 </View>
32 );

Do not worry about the handleButtonPress method on onPress prop for FormButton. This is going to execute the Firestore query and that is what you are going to do from the next section.

The corresponding styles of the above component are defined as below.

1const styles = StyleSheet.create({
2 rootContainer: {
3 flex: 1
4 },
5 closeButtonContainer: {
6 position: 'absolute',
7 top: 30,
8 right: 0,
9 zIndex: 1
10 },
11 innerContainer: {
12 flex: 1,
13 justifyContent: 'center',
14 alignItems: 'center'
15 },
16 title: {
17 fontSize: 24,
18 marginBottom: 10
19 },
20 buttonLabel: {
21 fontSize: 22
22 }

If you go to the modal screen, you are going to get the following result.

Here is the complete flow of the HomeStack navigator so far.

The Create button will remain disabled unless the user starts typing.

Add Firestore to the Chat app


To store messages as well as user information, let us use the Firestore data storage service from Firebase. Firestore has similarities to a NoSQL database (if you are familiar with NoSQL types).

To use the Firestore database, all you have to do is install the @react-native-firebase/firestore package and run the command to build the app again. Open up a terminal window and execute the following command.

yarn add @react-native-firebase/firestore
# do not forget to install pods for ios
cd ios / && pod install
# after pods have been installed
cd ..

Do note that, the Firestore package from react-native-firebase depends on two other packages:

  • @react-native-firebase/app
  • @react-native-firebase/auth

This means that these two packages are required to install to use Firestore. For the current app, you have already installed these packages so you do not have to install them again.

The last step in this section is to rebuild the app for each OS.

# for iOS
npx react-native run-ios
# for Android
npx react-native run-android

That's it to install Firestore.

Create a collection in firestore to store chat rooms


Each chat room is going to contain x number of messages between different users. To store a chat room in the Firestore, let's create a collection called THREADS.

Start by importing firestore in the AddRoomScreen.js file.

1// after other import statements
2import firestore from '@react-native-firebase/firestore';

Inside the functional component AddHomeScreen add a handler method called handleButtonPress.

This method is going to have the business logic to store the name of the chat room under the collection THREADS. The unique id of each chat room is going to be created by the Firestore itself.

1function handleButtonPress() {
2 if (roomName.length > 0) {
3 firestore()
4 .collection('THREADS')
5 .add({
6 name: roomName
7 }
8 })
9 .then(() => {
10 navigation.navigate('Home');
11 });
12 }

Go back to the simulator and try to create a new chat room.

After that, go to the Firebase database console and verify if the THREADS collection has a room called Room 1 or not.

Display a list of chat rooms on the home screen


To display chat rooms from Firestore you are going to make use of FlatList form React Native. Start by adding the following the import statements inside the src/screens/HomeScreen.js file.

1import React, { useState, useEffect } from 'react';
2import { View, StyleSheet, FlatList } from 'react-native';
3import { List, Divider } from 'react-native-paper';
4import firestore from '@react-native-firebase/firestore';
6import Loading from '../components/Loading';

Inside the functional component HomeScreen, define two state variables:

  • threads that is going to be used as the source of data for the FlatList component after the data has been fetched from the Firestore.
  • loading variable is going to keep track of whether the data is being fetched or not.
1export default function HomeScreen() {
2 const [threads, setThreads] = useState([]);
3 const [loading, setLoading] = useState(true);
5 useEffect(() => {
6 const unsubscribe = firestore()
7 .collection('THREADS')
8 .onSnapshot(querySnapshot => {
9 const threads = => {
10 return {
11 _id:,
12 // give defaults
13 name: '',
15 };
16 });
18 setThreads(threads);
20 if (loading) {
21 setLoading(false);
22 }
23 });
25 /**
26 * unsubscribe listener
27 */
28 return () => unsubscribe();
29 }, []);
31 if (loading) {
32 return <Loading />;
33 }
35 // of the component

Using the hook useEffect in the above snippet you can query the Firestore to fetch the name of chat rooms from the collection THREADS.

When the component loads, to fetch the existing chat rooms or in other words, to read the data from the Firestore, start by declaring a unsubscribe listener to the query. This listener is going to subscribe to any updates. These updates can be new or existing chat rooms. Declaring a listener here is important because when the screen unmounts, it is important to unsubscribe from this listener.

Using the querySnapShot, you are going fetch every document or the chat thread is going to be the part of the the state variable threads. At this point, data is returned from the query, as well as a default object that contains the _id(required as unique if for each item in the FlatList component), and the name of the chat room.

Here is the complete JSX rendered by this component.

1<View style={styles.container}>
2 <FlatList
3 data={threads}
4 keyExtractor={item => item._id}
5 ItemSeparatorComponent={() => <Divider />}
6 renderItem={({ item }) => (
7 <List.Item
8 title={}
9 description="Item description"
10 titleNumberOfLines={1}
11 titleStyle={styles.listTitle}
12 descriptionStyle={styles.listDescription}
13 descriptionNumberOfLines={1}
14 />
15 )}
16 />

The Divider component is a lightweight separator provided by UI library react-native-paper. Here are the styles associated with the above JSX.

1const styles = StyleSheet.create({
2 container: {
3 backgroundColor: '#f5f5f5',
4 flex: 1
5 },
6 listTitle: {
7 fontSize: 22
8 },
9 listDescription: {
10 fontSize: 16
11 }

Go back to the simulator device and you are going to get the following result.



The main objective of this tutorial is to create and store chat room names in a Firestore cloud database collection as well as integrate the configure the Firestore in our current app. This objective has been completed among other tips and techniques to create a modal screen and share header bar modifications among different route screens.

What's Next?


In the next part of this series, we are going to explore how to integrate and use react-native-gifted-chat which is one of the most important, open source, and actively maintained library to use when building a chat app using React Native. The "out of the box" features it provides in terms of mere props are so helpful and saves a ton of development time.

You can find the complete source code for this project at this Github repo.

👉 Here is a list of resources used in this tutorial:

More Posts

Browse all posts

Aman Mittal author

I'm a software developer and a technical writer. On this blog, I write about my learnings in software development and technical writing.

Currently, working maintaining docs at 𝝠 Expo. Read more about me on the About page.

Copyright ©  2019-2024 Aman Mittal · All Rights Reserved.