• No results found

Actions and action creators

In Flux, an action is a JavaScript object that has both a type and, optionally, a payload of data. An action object is created as a result of user interaction (or some other event), and the type of the action is most often a constant string that reflects the interaction's intent. In our application, the action's type is enough information to deduce the effect on the application state, so our action objects will be very minimal:

{

type: 'INCREMENT' }

Now that we know what the medium of communication looks like, we need to construct the channel on which to send it. The first thing we need is a way for our view to create these actions, and we do that by making reusable functions, cleverly called action creators. In order to keep our app modular and organized, we'll put our action creators in a new file: src/actions.js:

export const increment = () => { const action = {

type: 'INCREMENT' };

};

export const decrement = () => { const action = {

type: 'DECREMENT' };

};

const action = { type: 'ZERO' };

};

Note that these action creators are not yet complete; they create the action object, but do nothing with it. We'll revisit this in the next section. For now, we need to wire these new action creators into our view. We'll do this by first importing them into our index.ios.js file:

import React, { Component } from 'react'; import { AppRegistry, StyleSheet, Text, View, TouchableOpacity } from 'react-native';

import { increment, decrement, zero } from './src/actions';

Next, we need to call the action creator functions when the appropriate button is pressed by the user. We can easily do this by passing them as the onPress property for their respective TouchableOpacity:

<TouchableOpacity onPress={increment} style={styles.button}> <Text style={styles.buttonText}>

+ </Text>

</TouchableOpacity>

<TouchableOpacity onPress={decrement} style={styles.button}> <Text style={styles.buttonText}>

- </Text>

</TouchableOpacity>

<TouchableOpacity onPress={zero} style={styles.button}> <Text style={styles.buttonText}>

0 </Text>

</TouchableOpacity>

Now that we've connected the view to the action creators, we need to complete them by building the next part of our data pipeline–the dispatcher.

Dispatcher

The dispatcher's role in a Flux application is to accept actions, one at a time, and hand them off to the stores. Though Facebook does not provide specific implementation details for much of Flux, they do have an open source dispatcher that developers tend to use when constructing their own Flux code.

However, in our application, we'll build a simple dispatcher ourselves in order to see how it works. The version found in Facebook's open source Flux repository is slightly more robust, and if we were creating this application for a production deployment, it would probably be a better choice.

The first thing we'll need to do is create a src/Dispatcher.js file and create the Dispatcher class in it:

class Dispatcher { dispatch(action) {

// TODO: Pass to Stores }

}

For now, our Dispatcher has one method, dispatch(), which accepts an action object and will eventually pass that action along to stores. We can improve upon this a bit by enforcing the requirement that only one action be processed at a time. To do this, we'll define a property, isDispatching, that can be set at the beginning and end of our dispatch() function. If, however, the Dispatcher is already in the middle of a dispatch when the dispatch() method is called again, we'll throw an error:

class Dispatcher { constructor() { this.isDispatching = false; } dispatch(action) { if (this.isDispatching) {

throw new Error('Cannot dispatch in the middle of a dispatch'); }

this.isDispatching = true; // TODO: Pass to Stores this.isDispatching = false; }

The one at a time rule of the dispatcher may seem odd at first glance. You may be asking yourself, won't this slow down our application? Well, there is actually a very good reason for this invariant to be in place. If an action is dispatched as the result of another action, we are in danger of, at the very least, having cascading effects throughout our application that are difficult to predict and follow. In the worst case, these may lead to circular cascading effects that cause an infinite dispatching loop and break our application. It's far better for us to disallow this behavior altogether.

Now that we have the skeleton for our dispatcher, we need to export it. Instead of exporting the class, though, we will actually export an instance of the class. We do this in order to create a singleton dispatcher that manages all the actions for our entire application:

export default new Dispatcher();

Finally, we need to draw a metaphorical line between the action creators and the

dispatcher. To do this, we'll first import the new dispatcher instance into our actions.js file:

import Dispatcher from './Dispatcher';

Then, in each of the action creator functions, we'll send the action into the dispatcher's dispatch() method:

export const increment = () => { const action = {

type: 'INCREMENT' };

Dispatcher.dispatch(action); };

export const decrement = () => { const action = {

type: 'DECREMENT' };

Dispatcher.dispatch(action); };

export const zero = () => { const action = {

type: 'ZERO' };

Dispatcher.dispatch(action); };

Stores

Stores have two responsibilities in a Flux application. First, as their name suggests, they are responsible for storing the application's state. In a traditional Flux application, stores are broken up by logical domain, each one responsible for a small part of the greater

application. For example, in a social media application, one store might be responsible for the user's profile and another responsible for the user's posts.

Stores are also responsible for updating the application state as a result of actions received from the dispatcher. As we discussed earlier, this encapsulation of state and mutation logic helps reduce complexity by removing shared mutable state from all other parts of the application.

For our Countly application, we only need one store for keeping track of the tally. We'll call it TallyStore. In a new src/TallyStore.js file, the first thing we'll do is create a variable to store the application state in and give it an initial value:

let tally = 0;

This tally variable is considered a private variable that is within the closure of the store, but not exported for public consumption. Shielding this, and other variables and methods, from the rest of the application is one way that we can reduce the opportunity to mutate state outside this file.

Since we've deemed that the tally variable will not be accessible from the outside, we need to create a public getter function that allows other components to get the application's current state: class TallyStore { getTally() { return tally; } }

const instance = new TallyStore(); export default instance;

Note that, since the value of the tally variable is a primitive number and is, therefore, immutable, we can return it directly. If, however, our tally variable stored a mutable JavaScript object, we would need to return a copy of the object rather than the object itself. Doing so prevents developers of other components from accidentally mutating the

application state: const tally = { count: 0

Related documents