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:
jQuery.ajax()
method or SnapkiteStreamClient.initializeStream()
props
objectWe 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:
TweetStore
by using the getTweet()
method.TweetStore
.TweetStore
changes its tweet, we update the component's state to the latest tweet that we get from TweetStore
by using the getTweet()
method.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 tweetTweetActionCreators
: This creates and dispatches a new action with a new tweetAppDispatcher
: This dispatches all the actions to all storesTweetStore
: This registers with a dispatcher and emits the change event on every new action received from a dispatcherStream
: This listens to changes in TweetStore
, gets a new tweet from TweetStore
, updates the state with a new tweet, and re-rendersCan 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.
18.119.248.149