Building Private Chat Rooms with React Native for iOS and Android (Part IV)

Stop! We recommend instead using…

ChatEngine is our open and extensible chat API and framework for drastically accelerating chat development for any device and platform.

Learn more here →

This post was written by the team at You can leverage their expertise with custom React and JavaScript training for you or your team. Learn more about creating React Native applications with some further reading on Rangle’s blog, which covers topics like common tooling and workflow, writing your own libraries, and more.

Welcome back to the final tutorial of our four-part series on building a React Native application for Android and iOS, in this case, a realtime mobile chat app with a ton of cool features.

In our prior three posts, we covered:

  1. Creating a skeleton application that runs on both Android and iOS platforms
  2. Realtime chat and presence functionality in our skeleton application
  3. OAuth authentication support using GitHub as a provider

In this post, we cover the final elements of our React Native chat application:

  • Retrieving the friends list from GitHub
  • Private chats with friends

Let’s get started!

React Native Chat Repository

Since this is the final post about our chat application, we can use the branch containing the entire application: master. If you already have a copy of the repository checked out, run git checkout master.

As always, it’s a good idea to get an understanding of where we intend to go with this post. Therefore we recommend building and running the finished application so that you understand the target we are working toward. Fire up the iOS simulator:

npm run serve ios

react-native run-ios

You should eventually see the application pop up with a big green button that says Login with GitHub. Press it! You’ll be redirected to a GitHub authentication page. After you enter your credentials, you will drop back into the application. Now if you click the menu button in the top-right area of the application, you will be presented with the slide-out panel in the image to the bottom of this paragraph.

React Native App

Note: If you happen to be an antisocial GitHub user and you do not have any people following you, and you are not following anyone else, you are going to see an empty friends list. Do not despair: just open up GitHub in a browser and follow someone, then restart the chat application. Voila!

You’ll see that when you click on one of your friends’ names, it will open up a private conversation between you and that person. Instead of saying ReactChat (the default channel) in the top header bar, you’ll see Private conversation with John Doe (or whatever your friend’s name is). You can then swap back to the default channel by opening up the menu again and clicking ReactChat in the Channels list.


Now that we have seen the finished product, let’s see how we got there. Don’t worry, this isn’t going to be nearly as long as the prior posts, since it contains a much smaller subset of functionality.

Backend implementation for /friends

The first thing we need to do is implement a request handler for /friends in our NodeJS server. This URL is what we are going to hit from the React Native application in order to fetch our list of friends from GitHub.

First, we validate that the request is coming from a valid user who we already know about (because they should have passed through the authentication system). We do this using findUser():

const user = findUser(u => u.accessToken === req.query.accessToken);
if (user == null) {

The value we get back is going to contain a JSON document that GitHub provided to us. That document is going to contain a URL that, when hit, will return a list of followers, and another URL that, when hit, will return a list of people we are following. The JSON document describing our user will look a bit like this:

"user": {
   "login": "octocat",
   "id": 1,
   "avatar_url": "",
   "gravatar_id": "",
   "url": "",
   "html_url": "",
   "followers_url": "",
   "following_url": "{/other_user}",
   "gists_url": "{/gist_id}",
   "starred_url": "{/owner}{/repo}",
   "subscriptions_url": "",
   "organizations_url": "",
   "repos_url": "",
   "events_url": "{/privacy}",
   "received_events_url": "",
   "type": "User",
   "site_admin": false

There are two elements we care about here: followers_url and following_url. We are going to hit those URLs and concatenate the results in order to get our friends list. That’s this bit:

const promises = [
 fetch(user._json.followers_url).then(r => r.json()),
 fetch(user._json.following_url.match(/^.+(?={)/)[0]).then(r => r.json())

 .then(([followers, following]) => {

Now we take the results — the GitHub user IDs from both responses — and create some PubNub channels, one for each user. Each channel will be called conversation_userid2_userid2.

When constructing these channel names, the lower ID always comes first. That way we ensure that other instances of the application construct the same private channel names that we do.

   const friends = followers.concat(following);

   // create direct conversation channels
   const friendChannels = createChannels(, =>;

     channels: friendChannels,
     channelGroups: [],
     authKeys: [user.accessToken],
     ttl: 0,
     read: true,
     write: true,
     manage: false,

   // snip the success and failure handlers

Frontend Service Implementation

Now that we have our backend working, let’s take a look at how we are going to call our new /friends endpoint. Open up services/friends.js:

 getFriends(accessToken) {
   return fetch(`${}/friends?accessToken=${accessToken}`)
     .then(res => res.json());

This code couldn’t be much simpler. getFriends retrieves a list of our followers and people we are following. We use fetch to retrieve this data since it provides the simplest standards-conformant way of fetching HTTP resources. Now let’s look at the code that actually calls this service and renders this data inside the application.

UI components

New ChatMenu component

The most important UI element here is going to be the component that actually renders the list of channels and friends and accepts clicks from the user to change the current channel (or private conversation). Pop open components/ChatMenu.js and take a peek.

First we have a function that is capable of rendering an item in the channel list, renderChannel. We pass it a handler called selectChannel which is capable of changing the current channel when the user clicks a channel name. It is ultimately passed to us from Conversation through ChatMenu.

const renderChannel = (selectChannel) => (channel) => {
 return (
   <TouchableOpacity style={[styles.p2]} activeOpacity={0.6}
       onPress={() => selectChannel(channel)}>
     <View style={[styles.flxRow]}>
       <Icon name="message" size={30} color="white" />
       <Text style={[styles.silver, styles.ml2, {marginTop: 6}]}>{channel}</Text>

This is pretty straightforward. We use TouchableOpacity, which is the “architecture astronaut” name for a button in React Native. Inside of that, we render an icon and a text label containing the actual channel name. Then we have a very similar function, renderFriend, which does almost the same thing but with slightly different styling (because we are rendering a friend’s name using his GitHub icon).

Then in our ChatMenu constructor, we are creating two ListView data sources (one for channels and one for friends):

constructor(props) {

 this.channelsDataSource = new ListView.DataSource({
   rowHasChanged: (lhs, rhs) => lhs !== rhs

 this.friendsDataSource = new ListView.DataSource({
   rowHasChanged: (lhs, rhs) => lhs !== rhs

Then in our render method, we take those two data sources and feed them channels and friends (which we got in our props from Conversation) and then render both lists inside of a ScrollView so that if there are more channels and friends than can fit on the screen, the user can scroll up and down to reveal the rest of them. Our click handlers were attached in renderChannel and renderFriend, so all we have to do is make sure that we call those functions to render each individual item in the list. We do this by passing renderRow props to each ListView instance:

const channelsSource = this.channelsDataSource.cloneWithRows(channels);

const friendsSource = this.friendsDataSource.cloneWithRows(friends);

return (
 <View style={[styles.flx1, styles.flxCol, styles.selfStretch, styles.pt3, styles.ph4, styles.ml2, styles.bgNavy]}>
   // sign out menu item snipped
     <ListView style={[styles.mb3, styles.flx1]}
       renderHeader={() => (<Text style={[styles.silver, styles.pl2, styles.f3]}>Channels</Text>)}
     <ListView style={[styles.flx2]}
       renderHeader={() => (<Text style={[styles.silver, styles.pl2, styles.f3]}>Friends</Text>)}

Changes to Conversation

The next thing we need to look at is the Conversation component. Recall from our previous posts that Conversation is the primary container object for our chat application. It’s a good place to put our new channel / friends menu because it will be easy for us to connect it to Redux and to our new FriendsService (from above).

So we import ChatMenu and then add this bit to the render method:

 selectChannel={(id) => selectChannel('open', id)}
 selectFriend={(id) => selectChannel('direct', id)} />

We also have to add channels and friends to the props list so that we will get them from Redux:

(at the bottom of the file, after the BareConversation declaration:)

BareConversation.propTypes = {channels: PropTypes.array, friends: PropTypes.array};
// (additional props snipped for clarity)

With this assignment to propTypes, we are essentially telling React what arguments Conversation expects when it is instantiated. Then in the call to connect(), we supply a mapStateToProps function which extracts channel and friends and provides them as Conversation props:

const mapStateToProps = state =>
   state.conversation.toJS(), {
     friends: state.conversation.get('friends').toArray(), // <k,v> -> [v]
     channels: [channel],
     // additional properties snipped

export const Conversation = connect(mapStateToProps, actions)(BareConversation);

Redux State

The final bit of implementation work we have to do involves managing the Redux state. First, let’s take a look at actions/connection.js. We have to update our connect method to make loading the friends list part of application startup:

connect(authenticationToken) {
 return dispatch => {
   dispatch({type: CONNECTING});

   let user;

     .then(res => {
       user = res;

       return connect(authenticationToken,;
     .then(() => {
       dispatch({type: CONNECTED, payload: user});

       return friendsService.getFriends(authenticationToken);
     .then(friends => {
       dispatch({type: STORE_FRIENDS, payload: friends})
     // snip error handler

Then in reducers/conversation.js, we need to implement a handler for STORE_FRIENDS:

     return state.set('friends', Map(payload));


That’s about it! At this point, we have a working realtime chat application, built atop the PubNub service API, which supports OAuth authentication and automatically imports your friends from GitHub!

The complete application is available in the master branch at — be sure to check it out and submit any feedback through the GitHub Issues page. We hope this has been useful for you and that you’ll be creating lots of cool React Native apps in the days ahead



Try PubNub Today

Connect up to 100 devices for Free