Refactoring the Stream component

Now, with the Flux architecture in place, we will rethink how our React components get the data that they need to render. As you know, there are usually two sources of data for a React component:

  • Calling another library; for example, in our case, calling the jQuery.ajax() method or SnapkiteStreamClient.initializeStream()
  • Receiving data from a parent React component via the props object

We want our React components not to use any external libraries to receive data. Instead, from now on, they will get that same data from stores. Keeping this plan in mind, let's refactor our Stream component.

Here is how it looks now:

var React = require('react'),
var SnapkiteStreamClient = require('snapkite-stream-client'),
var StreamTweet = require('./StreamTweet.react'),
var Header = require('./Header.react'),

var Stream = React.createClass({

  getInitialState: function () {
    return {
      tweet: null
    }
  },

  componentDidMount: function () {
    SnapkiteStreamClient.initializeStream(this.handleNewTweet);
  },

  componentWillUnmount: function () {
    SnapkiteStreamClient.destroyStream();
  },

  handleNewTweet: function (tweet) {
    this.setState({
      tweet: tweet
    });
  },

  render: function () {
    var tweet = this.state.tweet;

    if (tweet) {
      return (
        <StreamTweet
        tweet={tweet}
        onAddTweetToCollection={this.props.onAddTweetToCollection} />
      );
    }

    return (
      <Header text="Waiting for public photos from Twitter..." />
    );
  }
});

module.exports = Stream;

First, let's get rid of the componentDidMount(), componentWillUnmount(), and handleNewTweet() methods, and import the TweetStore store:

var React = require('react'),
var StreamTweet = require('./StreamTweet.react'),
var Header = require('./Header.react'),
var TweetStore = require('../stores/TweetStore'),

var Stream = React.createClass({

  getInitialState: function () {
    return {
      tweet: null
    }
  },

  render: function () {
    var tweet = this.state.tweet;

    if (tweet) {
      return (
        <StreamTweet
        tweet={tweet}
        onAddTweetToCollection={this.props.onAddTweetToCollection} />
      );
    }

    return (
      <Header text="Waiting for public photos from Twitter..." />
    );
  }
});

module.exports = Stream;

There is also no need to "require" the snapkite-stream-client module anymore. Next, we need to change how the Stream component gets its initial tweet. Let's update its getInitialState() method:

getInitialState: function () {
  return {
    tweet: TweetStore.getTweet()
  }
},

Code-wise, this might look like a small change, but it's a significant architectural improvement. We are now using the getTweet() method to get data from the TweetStore store. In the previous chapter, we discussed how stores expose the public methods in Flux in order to allow other parts of our application to get data from them. getTweet() is an example of one of these public methods, which are called getters.

You can get data from a store, but you can't set data on a store directly just like that. Stores have no public setter methods. They are purposely designed with this limitation in mind so that when you write your application with Flux, your data can only flow in one direction. This will benefit you hugely down the road, when you'll need to maintain your Flux application.

Now we know how to get our initial tweet, but how do we get all the other new tweets that will arrive later? We can create a timer and call TweetStore.getTweet() repeatedly; however, this is not the best solution, because it assumes that we don't know when TweetStore updates its tweet with a new one. However, we do know that.

How? Remember that, in the previous chapter, we implemented the following public method on the TweetStore object; that is, the addChangeListener()method:

addChangeListener: function (callback) {
  this.on('change', callback);
}

We implemented the removeChangeListener()method as well:

removeChangeListener: function (callback) {
  this.removeListener('change', callback);
}

That's right. We can ask TweetStore to tell us when it changes its data. For this, we need to call its addChangeListener() method and pass it a callback function that TweetStore will call for each new tweet. The question is this: in our Stream component, where do we call the TweetStore.addChangeListener() method?

Since we need to add the change event listener to TweetStore only once per component lifecycle, it makes componentDidMount() a perfect candidate. Add the following componentDidMount() method to the Stream component:

componentDidMount: function () {
  TweetStore.addChangeListener(this.onTweetChange);
},

We add our own change event listener, this.onTweetChange, to TweetStore. Now when TweetStore changes its data, it will trigger our this.onTweetChange method. We will create this method shortly.

Don't forget that we need to remove any event listeners before we unmount our React component. To do this, add the following componentWillUnmount() method to the Stream component:

componentWillUnmount: function () {
  TweetStore.removeChangeListener(this.onTweetChange);
},

Removing an event listener is very similar to adding it. We call the TweetStore.removeChangeListener() method and pass our this.onTweetChange method as an argument.

It's time to create the onTweetChange method in our Stream component:

onTweetChange: function () {
  this.setState({
    tweet: TweetStore.getTweet()
  });
},

As you can see, it updates the component's state with a new tweet stored in TweetStore by using the TweetStore.getTweet() method.

There is one final change that we need to make to our Stream component. Later in this chapter, you'll learn that our StreamTweet component doesn't need the handleAddTweetToCollection() callback function anymore; therefore, in this component, we're going to change the following code snippet:

return (
  <StreamTweet
  tweet={tweet}
  onAddTweetToCollection={this.props.onAddTweetToCollection} />
);

Replace it with the following code:

return (<StreamTweet tweet={tweet} />);

Now let's take a look at our newly refactored Stream component:

var React = require('react'),
var StreamTweet = require('./StreamTweet.react'),
var Header = require('./Header.react'),
var TweetStore = require('../stores/TweetStore'),

var Stream = React.createClass({

  getInitialState: function () {
    return {
      tweet: TweetStore.getTweet()
    }
  },

  componentDidMount: function () {
    TweetStore.addChangeListener(this.onTweetChange);
  },

  componentWillUnmount: function () {
    TweetStore.removeChangeListener(this.onTweetChange);
  },

  onTweetChange: function () {
    this.setState({
      tweet: TweetStore.getTweet()
    });
  },

  render: function () {
    var tweet = this.state.tweet;

    if (tweet) {
      return (
        <StreamTweet tweet={tweet} />
      );
    }

    return (
      <Header text="Waiting for public photos from Twitter..." />
    );
  }
});

module.exports = Stream;

Let's recap to see how our Stream component always has the latest tweet:

  1. We set the component's initial tweet to the latest tweet that we get from TweetStore by using the getTweet() method.
  2. Then, we listen to changes in TweetStore.
  3. When TweetStore changes its tweet, we update the component's state to the latest tweet that we get from TweetStore by using the getTweet() method.
  4. When the component is about to unmount, we stop listening to the changes in TweetStore.

That's how a React component interacts with a Flux store.

Before we move on to making the rest of our application Flux-strong, let's take a look at our current data flow:

  • app.js: This receives the new tweets and calls TweetActionCreators for each tweet
  • TweetActionCreators: This creates and dispatches a new action with a new tweet
  • AppDispatcher: This dispatches all the actions to all stores
  • TweetStore: This registers with a dispatcher and emits the change event on every new action received from a dispatcher
  • Stream: This listens to changes in TweetStore, gets a new tweet from TweetStore, updates the state with a new tweet, and re-renders

Can you see how we can now scale the number of React components, action creators, and stores, and still be able to maintain Snapterest? With Flux, it will always be a one-way data flow. It will be the same mental model regardless of how many new features we implement. We will benefit hugely in the long run when we need to maintain our app.

Did I mention that we're going to adapt Flux in our application even more? Next, let's do exactly that.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.119.248.149