Posts

, ,

Using Mobx + Firebase to build a Twitter Clone

In this tutorial I’ll be showing you how to create a very Twitter Clone using React and Firebase. MobX will be used as the data store for Firebase data. Here’s what the final output is going to look like:

MobX-Firebase Twitter Clone

Firebase Setup

In order to not bog us down in Firebase setup, this tutorial will assume that you already have an existing Firebase project created in the Firebase Console. If not, go ahead and create one. Once you have a project, go to the database section and under the rules tab add the following:

{
  "rules": {
    ".read": true,
    ".write": true
  }
}

This sets read and write permissions of your database to public. This means you don’t need to authenticate later on in order to gain access the database. This is good for quickly getting started with something, but not for production apps. Be sure to change this later!

Project Setup

Clone the create-react-app-mobx project. This allows you to easily create a React Project which has already the MobX toolset built into it (the original create-react-app doesn’t support all MobX features yet).

git clone https://github.com/mobxjs/create-react-app-mobx.git react-firebase-mobx-twitter-clone

Next, open your package.json file and add the following:

"mobx-firebase-store": "^1.0.3",
"react-timeago": "^3.2.0",
"chance": "^1.0.6",
"slugify": "^1.1.0",
"twitter-text": "^1.14.3"

Here’s a breakdown of what each package does:

  • mobx-firebase-store – used for storing Firebase data in MobX maps. This uses Firebase-nest as a peer dependency to subscribe to changes in a Firebase store.
  • react-timeago – used for generating a human-friendly text based on a timestamp.
  • chance – used for generating a random name to be assigned to the current user.
  • slugify – used for making the random name URL-friendly.
  • twitter-text – contains utility functions for working with twitter text. Things like counting the remaining text and converting URL’s into links.

Execute npm install to install all the packages. Once that’s done, you can run npm start to serve the project for development. You can access https://localhost:3000 on your browser to view the project.

Creating the Main Component

Before moving on, delete everything inside the src folder of the project. By default it contains a demo project that uses MobX to implement a counter app. We don’t really need those so you can go ahead and delete them.

Create an index.js file. This is where you supply your Firebase database URL and render the main app component which you’ll be creating later:

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

const config = {
    databaseURL: 'https://{YOUR_FIREBASE_PROJECT_NAME}.firebaseio.com',
};

ReactDOM.render(
  ,
  document.getElementById('root')
);

Next, create an App.js file. This is where you import the TweetList component we’ll build that serves as the component that the user will interact with, and the TweetStore store which contains the code for connecting, and saving/reading data from Firebase.

import React, { Component } from 'react';
import TweetList from './TweetList';
import TweetStore from './TweetStore';
import './index.css';

class App extends Component {

  componentWillMount() {
    this.store = new TweetStore(this.props.config); //create a new instance of the store by passingin the Firebase config
  }

  render() {
    return (
      

MobX-Firebase Twitter Clone

); } } export default App;

Creating the TweetList Component

Now we’ll create the TweetList.js file. At the very top, import all the libraries and components that you need. You’ll see how each of these will be used later so we won’t cover what each one does here.

import React, { Component } from 'react';

import { observer } from 'mobx-react';
import { createAutoSubscriber } from 'firebase-nest';

import Chance from 'chance';
import slugify from 'slugify';
import TimeAgo from 'react-timeago'
import twitter_text from 'twitter-text';

const tweet_limit = 140;
var ch = new Chance();

Next, create the Tweetlist component. Initialize the state as well as the functions inside the constructor.

class TweetList extends Component {

  constructor(props) {
    super(props);
    this.state = {
      tweet: '', //the user's tweet text
      username: slugify(ch.name()),
      loading: false, //whether the Firebase data is currently loading or not
      remaining: tweet_limit //number of characters allowed for each tweet
    };
    this.updateText = this.updateText.bind(this); 
    this.submitTweet = this.submitTweet.bind(this); 
  }
  ...
}

The render() method uses the getTweets() method from the store. You’ll be creating this later, for now know that everytime someone makes a change to your Firebase database, the component gets re-rendered. If the Firebase data becomes available, the textarea for entering the tweet and the list of tweets gets rendered.

render() {
  const tweets = this.props.store.getTweets();
  if (!tweets) {
    return 
Loading tweets...
} return (
{this.state.username}
{this.state.remaining}
{tweets.keys().reverse().map(messageKey => this.renderTweet(messageKey, tweets.get(messageKey)))}
); }

Here’s the function for rendering each tweet:

renderTweet(key, tweet) {
  return (
    
@{tweet.username} -
); }

The function for updating the state of the current tweet text and remaining count:

updateText(evt) {
  let tweet = evt.target.value
  let remaining = tweet_limit - twitter_text.getTweetLength(tweet);
  this.setState({
    tweet,
    remaining
  });
}

The function for saving the tweet in Firebase:

submitTweet() {
  this.props.store.createTweet({
    username: this.state.username,
    timestamp: new Date().getTime(),
    text: this.state.tweet 
  });

  this.setState({
    tweet: '',
    remaining: tweet_limit
  });
}

Next, you need to define the getSubs() and subscribeSubs() function. The naming is relevant because the Firebase-nest library rely on these two functions. getSubs() returns the array of subscriptions while subscribeSubs() performs the subscription and returns the function for unsubscribing from the Firebase data.

getSubs(props, state) {
  return props.store.allTweetsSubs();
}

subscribeSubs (subs, props, state) {
  return props.store.subscribeSubs(subs);
}

Finally, you use the createAutoSubscriber() function to subscribe and observe to Firebase data. This function uses the getSubs() and subscribeSubs() functions that you defined earlier in order to subscribe to Firebase data and propagate the changes in the store. It will then return the function that requires a Reactive component to be passed in. We can turn our component into a Reactive one by wrapping it in MobX’s observer() function. This ensures that any data that is used for rendering the component forces a re-rendering when it’s updated. If you don’t set your component as an observer, it won’t re-render when the current user or someone else posts a new tweet.

export default createAutoSubscriber()(observer(TweetList));

Creating the Store

We shall now create out Mobx store. Create a TweetStore.js file. This serves as the store for the TweetList component. It contains the methods for saving and fetching data from Firebase.

Start by importing firebase and mobx-firebase-store. firebase is the official Firebase JavaScript library for working with Firebase. This allows you to connect to Firebase. The mobx-firebase-store library provides a bridge between the Firebase library and the MobX library. It allows you to subscribe to Firebase data through the use of firebase-nest subscriptions. This makes the data flow into MobX’s observable maps.

import Firebase from 'firebase';
import MobxFirebaseStore from 'mobx-firebase-store';

Define the property in which the array of tweets are stored:

const tweets_subkey = 'tweets';

Here’s what it looks like in the Firebase console:

Firebase database subkey

Create the store. Inside the constructor, connect to Firebase and create the store. MobXFirebaseStore accepts a reference to a Firebase database as its argument. Right below that, call super(store.fb) to make the store available inside the class via this.

export default class TweetStore extends MobxFirebaseStore {

  constructor(config) {
    const fbApp = Firebase.initializeApp(config);
    const store = new MobxFirebaseStore(Firebase.database(fbApp).ref());
    super(store.fb);
  }
  ...
}

allTweetsSubs() as you’ve known earlier, returns an array of subscriptions. In this case you only want to subscribe to a single subkey (tweets) and you want its data to be returned as a list (asList: true). Basically, it’s used to specify the subkey in your Firebase database that you want to subscribe to.

  
allTweetsSubs() {
  return [{
      subKey: tweets_subkey,
      asList: true
  }];
}

createTweet() uses Firebase’s push method to push the tweet object into the array. This creates a new item in the tweets array in Firebase which then triggers the subscriber to update the list of tweets for all the connected clients.

createTweet(tweet) {
  this.fb.child(tweets_subkey).push(tweet);
}

getTweets() returns the collection of tweets:

    
getTweets() {
  return this.getData(tweets_subkey);
}

resolveFirebaseQuery() is used for customizing the query used for fetching Firebase data. Here, the results are ordered according to their timestamp and only the 10 most recent tweets are being fetched. That said, Firebase still returns the records in ascending order that’s why you had to call reverse() on the TweetList component earlier to reverse the ordering.

  
resolveFirebaseQuery(sub) { 
  return this.fb.child(tweets_subkey).orderByChild('timestamp').limitToLast(10);
}

Add the Styles

Open the public/index.html file and add the following inside the <head>:


This uses picnic.css to add some default styling to make the project look good.

Next, create a src/index.css file and add the following:

body {
  margin: 0;
  padding: 0;
  font-family: sans-serif;
}

.tweets {
    margin-top: 30px;
}

.tweet {
    padding: 20px;
}

Conclusion

That’s it! In this tutorial you’ve learned how to use MobX to act as a data store for your Firebase data. As you have seen, using Firebase with MobX doesn’t completely change the way you interact with Firebase so you can still use your existing knowledge of the Firebase API. You can check out the code used in this tutorial in this Github repo.

, ,

Introduction to Data Binding with MobX

Once you get past very simple applications, programming on the web can become a nightmare of event handling and notifications, where the state of an application is spread out among dozens of different text boxes, select boxes and similar components. This is one reason why Model/View/Controller architectures (MVC) have become pretty much de rigour (along with multiple variations on this theme) in web programming.

However, MVC also requires that when you change the model, the view will change in response. This is pretty easy to accomplish when there is a direct one-to-one relationship between a component and its associated value. Where things begin to fall apart is when multiple components are dependent upon the same set of values, or when changes in values change other values that have component dependencies. This is a behavior you see quite often with spreadsheets (and one reason why translating spreadsheets into web applications can be quite a headache).

Mobx and MVC

The Mobx library makes use of ES2015 and (with later version) ES2017 decorators in order to identify specific variables or objects and make them “observable”. What this means in practice is that the framework “keeps track” of these variables, and any time the variables change, then the a notifier is activated for handling the impacts of these changes.

An example of this can be seen in the following CodePen.

See the Pen MobX+React test by Kurt Cagle (@kurt_cagle) on CodePen.

This example is deliberately kept simple to illustrate the process. Here, you can change a person’s first or last name, or change their age by using a numeric input control. This data is kept within a particular data structure, when either part of the name is changed, a new variable called flippedName is calculated, which holds an alternative medium state (in this case the name given as “lastName, firstName”). Finally, if the age of the person is 18 or higher, the flipped name appears green – the person involved can participate in whatever activity is being supported. If the age is under 17, however, the flipped name turns red, and a warning message is posted.

This example is especially useful because the data is entangled – the dispay of the output text is dependent upon both the names and the age. Moreover, the input fields make it possible to update the model, showing a “round trip” type activity that is common with MVC type applications.

Dipping into the code, the first few lines take advantage of the Babel transpiler to implement the ES2017 decorator capabilities. Decorators are pre-process directives that perform “meta-operations” on code, and are in effect the equivalent of “aspect” programming used in languages like Java. These can be used to automate logging, to set up services from functions and, in the case of mobx, to identify and manage variables and objects to be watched.

Explore React Courses

The @observable decorator is syntactic sugar for the older ES2015 implementation mobx.observable(...). This is not a function in the traditional sense. Instead, observers act on functions and expressions of code prior to them actually being evaluated, deferring the actual evaluation until some context structures can be set up.

class Person {

@observable firstName;

@observable lastName;

@observable age:number;

constructor(_firstName,_lastName,_age){

this.firstName = _firstName;

this.lastName = _lastName;

this.age = _age;

this.bind("firstName");

this.bind("lastName");

this.bind("age");

}

@computed get flippedName() {return `${this.lastName}, ${this.firstName}` }

In this case, three properties of a given class are identified as being observable – firstName, lastName and age. This means that, when an instance of this is created, then the instance members will be observable.

The bind() function in this class is not directly mobx related. Instead, it establishes a simple binding between an input element with an id that has the same name as the variable in question. That way, if you change an input field, you also change the model, and by extension change any functionality that depends upon the variable being changed.

bind(prop,defaultValue){

document.querySelector(`#${prop}`).value = defaultValue!=null?defaultValue:this[prop];

document.querySelector(`#${prop}`).addEventListener("input", (evt)=>{this[prop] = evt.target.value});

}

An area where mobx really comes in handy is for those scenarios where you want to make computed properties. A computed property is one that has a dependency upon observable properties – when you change the observable, you also change the computed value. Here, the first and last names are inverted to create a “lastname, firstname” type string in this read-only getter. By using the @computed decorator, should the independent variables (firstName or lastName here) change, then the @computed getter is re-evaluated.

This is by itself actually a huge benefit that mobx provides. While for something like inverting name takes comparatively few cycles to perform, when you have complex structures, regular expressions, template strings, asynchronous calls or similar processes, not having to rerun a getter when nothing has changed can result in huge cycle and memory savings. The @computed decorator feature right here is well worth the price of admission to Mobx.

The mobx.autorun() decorator is one of the few that doesn’t have an associated @ property. It is one of the big workhorses of Mobx, however. Whenever an observable or computed property is changed mobx.autorun is called IF the enclosed expressions have dependencies upon those variables.

In the sample codepen, I define three “render” methods. The first displays the flipped label. The second changes the color of the inverted title from green to red if the age given is below 18, while the final method adds a warning beneath the input fields indicating when a person is too young to participate in the program.

There are three such mobx.autorun() statements, one that calls each of these functions respectively. When a user changes the first name using the input, the showLabel() and warn() functions are invoked. If they change the age, the redLabel() and warn() functions are invoked. Finally, if the last name is changed, then only showLabel() gets invoked. Why? The showLabel() function has a dependency on the first and last name, while warn() uses only the first name, and redLabel() has no dependency at all upon the last name.

The advantage of this should be obvious, especially with complex forms or applications. If a component is unchanged, then it should not be necessary to re-render it, especially when there are a lot of interdepencies acting upon that component.

In that respect, the relationship between MobX and React should be obvious. MobX creates a way to indicate when the broader (global) data model changes, notifying downstream functions when independent variables and automatically computing @computed values that can then reduce the overall computational cost. React maintains a data model at the component level, so that when state variables or attributes change, only those portions of the component dependent upon those changes gets updated.

This article focused on the basic functionality of mobx. A subsequent article will focus will look at how you can use mobx and react together to keep the total amount of unnecessary work for your application to a minimum.

Explore React Courses