Skip to content

Latest commit

 

History

History
309 lines (244 loc) · 26.5 KB

README.md

File metadata and controls

309 lines (244 loc) · 26.5 KB

flux logo FOR_FLUX_SAKE flux logo

A simple tutorial for simple people who want to use flux with react. This tutorial follows on from my react tutorial (WIP).

Feel free to raise issues/make pull reqs for any reason, I'm easy.

##Introduction In this tutorial, we'll be converting a purely React app into one built with the Flux design pattern. By doing so, I hope you can see why you might want to use Flux if you're using React, and how you might start going about changing your app accordingly.
This tutorial isn't intended to take you the whole way to competency with Flux, but it is meant to bridge the pretty significant gap between not knowing Flux and being able to understand most tutorials online. It tries to do this by slowly morphing something you hopefully already understand into a thing built according to the principles of the Flux architecture (how grand does that sound).

Pre-requisites

Although this tutorial attempts to be as beginner-friendly as possible, it does make some assumptions about your background. It assumes:

  • Familiarity with Javascript (you understand what .bind() does, and when it might be needed),
  • Familiarity with NodeJS (you understand the module.exports/require pattern, and know of EventEmitters),
  • Some experience with React (you understand the difference between state and props, where state should lie, and how components can set the state of other components),
  • Some experience with Git (we'll be switching branches now and again).

If you don't feel comfortable with any of these, tutorials for these are everywhere. I've linked a few at the bottom of this readme if you're feeling a bit lost though.

We'll be using React and Flux. Other than a number of build tools and object-assign, we won't be using anything else. So no funky random modules required into our code to make learning difficult. Oh yeah, and keyMirror, which is included with React.

"Apart from object-assign and keyMirror", I hear you quip. Well, all object- assign is is a nice utility module filling in for an es6 feature we don't yet have. In a nutshell, it lets you copy the properties of some source objects and stick them in a destination object. But read the readme and the link above for more details if you want.
KeyMirror just takes an object as an argument and returns an object with the values the same as the keys. So keyMirror({"blah": null}) returns {"blah": "blah"}. What a lifesaver.

Getting Started

// -- Terminal window -- \\
git clone https://github.com/MIJOTHY/FOR_FLUX_SAKE.git
cd FOR_FLUX_SAKE
npm install gulp browserify -g
npm install
gulp

// -- Terminal window 2 -- \\
python -m SimpleHTTPServer

// -- browser -- \\
localhost:8000

So now you've installed the shizzle, you're good to get coding, right? Wrong. We've got some preparatory work to do first.
If you're on master, you'll be looking at the fully Flux-ified version. I highly recommend comparing the purely React version, the mid-transition-to-Flux version and the fully Flux version, which you can do with a git checkout between reactversion, clearfruitversion and master. If you're still gulping and server-ing, you'll be able to refresh your browser and see the new version. But it's the code we're interested in here.

##What is Flux? Flux is not a library, or even a module. It's a design pattern more than anything else. If you've done much React, you'll know that a big part of the design philosophy behind it is this unidirectional data flow concept. Having a one-way flow of data through your app is meant to make interactions easier to reason about, as well as leading to a significantly more robust app as complexity grows. Although technically you can npm install flux, all you get is the facebook dispatcher. This is a crucial part of this tutorial, but there are a plethora of other ways to implement Flux design principles without their particular flux module.
Safe, thanks for that m8, but what's Flux?

The flow

  1. Some sort of interaction happens in the view
  2. This creates an action, which the dispatcher dispatches
  3. Stores react to the dispatched action if they're interested, updating their internal state
  4. After updating, the stores emit a change event
  5. Stateful view component(s) hear the change event of stores they're listening to
  6. Stateful view component(s) ask the stores for new data, calling setState with the new data

There's a lot there, but I'll try to break it down.

###Actions:
An action in Flux is what's made when something happens. If you want a user to be able to submit a registration form by clicking on a button, then you want the user's interaction with the form to create an action. So, in our React version, contained within all of those 'onChange/Click' functions are actions, which are then dispatched. "So what you're saying is, if I click on something, that's not an action in itself, but it creates an action?" Yh, don't blame me for the unintuitiveness. Just accept it. Your click is an interaction. And stop talking to your computer.
Anyway, to give these more context, I'll introduce...

###The Dispatcher:
You can think of this as being like a radio station. All it does is broadcast actions when they happen, and let things tune in to hear those broadcasts. Instead of your 'onClick' function using a callback passed down to it through props to set the state of your application, you'll instead have it use the dispatcher to dispatch a specific action. Where does it get dispatched to? Nowhere, everywhere, whatever. It just gets dispatched. The dispatcher isn't a postperson. The dispatcher just dispatches. It releases actions into the air for anyone to hear if they're tuned in.
So how specific are these actions meant to be? Well, you'll probably want to give them two things: a type and a payload. Why? Well, when a user submits a form, you want to take care of that interaction appropriately. You need to know that what just happened was a form submission, so you can treat it as such. Also, you need the information contained within the form. Sometimes you can get away with just a type. Sometimes you might want some other identifying bit of data. But for now, let's stick with type and payload.

In order to react to dispatched actions, stores (more on these below) need to register a callback with the dispatcher. What this practically means is that you to write some code that explicitly tells your store to actually listen to actions dispatched by the dispatcher, and what to do when it hears them. In this way, stores can do stuff when the dispatcher dispatches something they like. If you want to listen to Kurupt FM to hear your mum get a shout-out so you can tease your brother about it, you need to tune in your radio to whatever frequency those badmen broadcast on, and start listening for some keywords. That's what happens when a store registers a callback with the dispatcher and provides some code to run in it if shoutOut.type === "your mum".

Also, there's only one dispatcher. It's basically a single, global communication point between your components and your stores. Much nicer than those bloody callbacks passed down through props. Am I right? Yes I am.

###Stores:
Store's are really important. You know how you've been holding all the logic and data concerning the state of your application in your top-level React component? That really shouldn't be there. It looks pretty messy too. React is first and foremost a UI library. Your data and logic concerns should be taken care of elsewhere. What does that mean? It means your React components shouldn't be pushing objects to arrays of data that you then use to set state. React components should simply ask stores for data, and update themselves accordingly. The only thing allowed to update data in your application is the store itself.

Your store should represent the ideal state of your application at any given time. Let's clarify this with an example. In the React version of the application, if a user types a fruit name into the header and hits enter, that causes the top-level component to push that new fruit to the array of fruities, and then set its state to the new, updated data, causing a re-render and making that new fruit appear on the list.

Using the Flux pattern, A user would do the same, but rather than this directly causing the top-level component to mess with the data, it would instead dispatch an action with a type of NEW_FRUIT and a payload of whatever the user typed in. Let's say our Fruit Store is listening for this particular action (it does this by registering a callback with the dispatcher, which sounds complicated but should get clearer on seeing the code). Upon hearing this dispatched action, it does what the top-level component used to do. It pushes the new fruit to the array of fruities it holds. So, now a user has taken an action, and the store has updated its internal state. The view is now out of sync with the data we want it to represent.
So, what the store needs to do is to say that it has changed. As long as the view is listening for that particular store's change event, when the store says it has changed, the view should ask the store for the new data to bring it back into sync, call setState, re-render and so the new list item appears on the screen.

So, a store holds a stock of data, and responds to dispatched actions that it's interested in by updating that data accordingly, before broadcasting an event saying that "I've changed". Any time a store changes its data, it has updated its internal state. Whenever a store updates its internal state, it needs to emit a change event. But it can't use the dispatcher for this since this updating isn't an action! The dispatcher is only used for actions, and only stores listen to the dispatcher. So the store will simply be an EventEmitter that emits "change" after every update. If you look in FruitStore.js, you'll see that this all takes place in FruitDispatcher.register, which is where it describes what it wants to do once it hears an action from the Dispatcher.

Things are never sent directly to places. All that happens is that events get broadcasted, things listen for events, and if they hear an event that they're listening for, they react accordingly. Stores listen for specific dispatched actions, and once they hear one that they're interested in, they update their internal state. Views listen for update events from stores that they're interested in, and once they hear them, ask for the fresh data so they can re-render. Note the slight difference. Actions are dispatched with their data contained within them, so anyone listening can immediately get to work with them. Stores emit change events that only say "I've changed", and don't contain any data, so their subscribers have to go to the store to ask for the updated data.

I bet that at this point, that rogue apostrophe at the start of this section has been slyly tempting you into thinking of raising an issue to proudly point out how heinous a mistake I've made. Well, the joke's on you. Stores are actually so important that nobody can reach in and change their data. The only things that can change their data are themselves when they hear a dispatched action that they care about. In more technical terms, there are no public setters provided by stores. The only public store methods are getters (for getting crap from the store) and ways to subscribe or unsubscribe from the store (for letting your components hear it when that store emits a change event, so you can go to get the updated crap from the store). So if you've got a problem with that apostrophe, tough. The only way you can get rid of it is to dispatch an action that it's listening for. But you don't have a dispatcher, sucker. I can put apostrophes in any st'o're I like and you can't do shit about it.

##The App The app we'll be building with the Flux design pattern is a 5-a-day tracker. But I use it for counting how many pints I've had before lunchtime.
First you'll see the old, React version built decades ago. Next to it is the new, Flux version we'll be building. How far we've come. Isn't technology great.

###Impressed? I thought so. Let's break down the functionality of this app. In the header, we can:

  • Type text into the header to filter the fruit list
  • Type text into the header and click the leaf button/hit enter to add a new fruit item to the list

In each list item, we can:

  • Increment the quantity we've eaten of a given piece of fruit by clicking the associated plus button.
  • Decrement the quantity we've eaten by clicking the minus button, and if we click minus on a 0-quantity fruit item, it removes it from the list.

In the footer, we can:

  • Clear the entire list by clicking that crappy little restart button.

Wow, amazing. I feel healthier already.

Let's begin

Let's start small. What's the simplest action we have? I'm gonna say it's clearFruities(), which we use when someone clicks that reset button in the footer. It's also ridiculously named. Let's ditch clearFruities() and move this logic to the FruitStore! We're going to want to:

  1. Replace our use of clearFruities() in the footer's clickHandler() function. Instead, we'll create an action with the type CLEAR_ALL_FRUITS that we'll dispatch with the FruitDispatcher. We don't need no payload this time!

  2. Create a store to hold the data we use for fruits, and take care of the updating logic for us. We'll also need to get the store listening for dispatched events. In particular, we're gonna want to set it up to listen for actions with a type of CLEAR_ALL_FRUITS, and on hearing that event, have it clear all of the fruit data it's holding. Then it will need to emit a change event, so its subscribers can ask it for the new shiznit.

  3. Once we've dealt with the sto're, we'll need to change our top-level component to get its state from that store, rather than taking care of the data and logic on its own!

Exciting. And hey, there's that rogue apostrophe again! I forgot why that's there (HINT HINT).

Step 1

Ite, you'd better be on the react branch, so you can play along. git checkout -b reactversion and git pull if you haven't already. You should now have only react stuff. First things first, we need to make a dispatcher so we can use it to dispatch actions. How do we do that? Go to your src folder, and create a folder called /dispatcher. In there, let's make a file called FruitDispatcher.js, and that'll contain these two lines...

var Dispatcher = require("flux").Dispatcher;
module.exports = new Dispatcher();

And that's it. We've made our dispatcher. Isn't flux cool? The equals operators even line up without us trying. god thats fluxy.

Step 1.1

To the FruitFooter!

Presently, your clickHandler looks a bit like this:

e.preventDefault();
this.props.clearFruities()

Since we're gonna get fluxy, we don't need (or want) to use this state-changing callback passed down from 'pon high. Instead, we're gonna want to have our click create and dispatch an action for anything that's interested to hear and act upon. For now, we'll do that directly in this file as we're only dealing with this one action, but later on, we'll create a file specifically for creating actions and dispatching them. So, go ahead and require the FruitDispatcher you just made, and let's create an action with the type CLEAR_ALL_FRUITS. Remember, we won't need a payload this time as we're only sending out a deletion action:

e.preventDefault();
var action = {
	type: "CLEAR_ALL_FRUITS"
}
FruitDispatcher.dispatch(action);

If you're feeling fluxy, go ahead and create the action directly within the call to dispatch. I assigned it to the variable action here to make it obvious that we're creating an action and then dispatching that action. Refresh your browser page, and start clicking reset like a madperson. One of three things should happen:
a) An ERROR cos you forgot to npm install
b) An ERROR cos you done screwed up
c) An ANYTHING cos you done screwed up
d) Nothing cos you did good

Step 2

This a big one - got to make a store. In the /src folder, let's make a folder called /stores, and in there a file called FruitStore.js. This store is gonna need a bunch of boilerplate:

var FruitDispatcher = require("../dispatcher/FruitDispatcher");
var EventEmitter    = require("events").EventEmitter;
var assign          = require("object-assign");

// We'll assign this to a variable with a longer name than the string so that 
// we can practice our touch-typing (and so an error is thrown if we make a typo)
var CHANGE_EVENT = "change";

// We create an empty object (a 'singleton', since we're not using a constructor), 
// and assign to it the methods of EventEmitter.prototype and these custom methods
// we're sticking in the object passed as the 3rd argument
var FruitStore = assign({}, EventEmitter.prototype, {

// This is a method so that the store can broadcast to anyone listening that it has changed. 
// We'll want to call this whenever the store updates the data it holds (changes its internal state).
	emitChange: function() {
		this.emit(CHANGE_EVENT);
	},
// These listener functions are what our views use to make sure they're listening for 
// change events emitted by the store, and to stop listening accordingly.
	addChangeListener: function(callback) {
		this.on(CHANGE_EVENT, callback);
	},

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

});

module.exports = FruitStore;

Let's move our state into to FruitStore so that we can listen for the CLEAR_ALL_FRUITS action if it gets dispatched and clear our stock of fruits accordingly if we hear it. Let's insert the following just below var CHANGE_EVENT = "change";

var _headerText = "";
var _fruities   = [
			{ id: "123456", fruit: "Chicken", quantity:6 },
			{ id: "123467", fruit: "Apples" , quantity:2 },
			{ id: "123478", fruit: "Oranges", quantity:4 },
			{ id: "123489", fruit: "Peaches", quantity:1 }
		];

Mad. So we've done our setup - our state is now held in the store, and we've got a bunch of public un/subscribe methods available. Now for the slightly more interesting part - let's register a callback with the FruitDispatcher so that the store can do something when it hears an action of a certain type:

// Start listening to actions dispatched by the dispatcher, 
// And upon hearing one
FruitDispatcher.register(function(action) {
	// Check for its type...
	switch(action.type) {
		// If it's one we want
		case "CLEAR_ALL_FRUITS":
			// Empty our stock of fruities
			_fruities = [];
			// And emit a change event to let anyone listening know that 
			// we've just updated our internal state
			FruitStore.emitChange();
			break;
	}
});

Step 3

FruitStore

So, now we've got our state held in the FruitStore rather than in the top-level component of our app, and we have a way to mutate that state through the medium of the dispatcher. What we're missing, however, are ways to get state from the stores. From other modules, we _can't access _fruities or headerText because they're local to this module. That leading underscore is meant to let us know that they're private. What we'll want to do is give our FruitStore some public getter methods. Let's stick these below the public change listener functions:

var FruitStore = assign(
	...
	getFruities: function() {
		return _fruities;
	},

	getText: function() {
		return _headerText;
	}

});

Why are these public methods? Because they're methods of an object that we're exporting, so anyone importing the module can call them. Why are these public methods? Because we want them to be accessible to the general public, and these only let them un/subscribe to the store and ask the store for its current state.

Let's go to FruitApp and fruit some shiz up (...)

FruitApp

We'll need to do some cleaning. Let's change getStateFromData to getStateFromStore. If you haven't guessed already, we're gonna want to import FruitStore.js into this file, and make use of the two public getter methods we defined above. So, instead of hardcoding headerText and fruities there, we can instead say:

return {
	headerText: FruitStore.getText(),
	fruities: FruitStore.getFruities()
};

Gd 1. We now have a way to get state from stores, but we're not using it. In order to use it, we'll need to change getInitialState so that it calls getStateFromStore() rather than getStateFromData(). Do that now, refresh your browser, and you should see our default fruits there. Click on the clear fruits button, and if nothing happens, you haven't screwed up yet. Give yourself a pat on the back if you like congratulating yourself for non-achievements.

You wanna fix it yh?

So let's think this through. When our app runs, initially it asks the store for its stock of fruities. When the app gets that stock, it sets its state with it. When we click that clear fruits button, we dispatch an action with type CLEAR_ALL_FRUITS. Our store hears that action, and empties its array of _fruities. It then emits a CHANGE_EVENT and... There's our problem. Firstly, although our FruitApp is getting its initial state from the FruitStore, it's not subscribed to it so it doesn't get any update notifications. Secondly, in virtue of it not getting any update notifications, it doesn't realise that it should ask the store for its updated state when it changes. Let's fix that. There are 3 methods we'll want to add to var FruitApp just under getInitialState:

componentDidMount: function() {
	FruitStore.addChangeListener(this._onChange);
},

componentWillUnmount: function() {
	FruitStore.removeChangeListener(this._onChange);
},

_onChange: function() {
	this.setState(getStateFromStore());
},

The first one tells our component to, once it mounts, subscribe to FruitStore so it can hear any change events that it emits. Take a peek at addChangeListener inside FruitStore and you'll see that it takes a callback that it passes to on to be executed when CHANGE_EVENT occurs. In FruitApp, we'll want to call getStateFromStore when we hear that the store has changed its state. We'll always want to get the entire state from the store as the store is the caretaker of state for our application. If there's any logic going on, the store takes care of it, so all our view has to do is to ask for that new data and it arrives ready to be rendered. So _onChange does that for us - it asks for the updated state and sets the component state with what arrives.
We'll also want a way for our component to unsubscribe from store notifications if it ever gets removed from the page. We do that in the same way as we subscribe to the store. We pass in the callback so that the component has the freshest state before its untimely demise.

Nice, now our app should fully run. Of course, there's still a considerable amount of legacy code flying about - we're only getting our initial state from the store and only clearing fruities is done properly, but we've laid the groundwork and there's really not much work left to get the app fully fluxified!

Add and commit, then git checkout clearfruitversion to compare your version to mine.

K, let's do the rest

I'll let you do the rest on your own. But here are some hints:

1. Folder Structure

Try to keep your code modular. Move your action creator functions into a seperate file, in which you create and dispatch the actions. Define a set of constants for your app that your action creators will use as types, and that your stores will listen for. You should be able to look at your blahBlahAppConstants.js file and, at a glance, know every state-mutating action that can occur in your app. For this app, your final product should have a structure that looks a bit like this:

/src
	/actions
		FruitActionCreators.js
	/components
		...
	/constants
		FruitConstants.js
	/dispatcher
		FruitDispatcher.js
	/stores
		FruitStore.js
main.js

If you start wanting to make api calls or your logic gets a bit bloated, I'd advise making a util folder and to stick those capabilities in there. The fundamental thing is to be sensible with splitting up your app though. If a function does more than one thing, it should be split up. If you have a bunch of functions that do similar stuff (such as making API calls or creating actions) but they're in a file not dedicated to that, perhaps it's bloody well time you move them there.

2. Migrating state-mutating functions to the store

Literally all you have to do now is add the relevant cases to your switch statement within your callback that's registered with the dispatcher, and then take the logic from your components and put them there. This may also involve creating that FruitConstants.js file as seen above and exporting an ActionTypes object with a bunch of constants from there.

3. Tidying up

Now we can delete all those state-mutating functions in FruitApp, those callbacks that we were passing down through props, and change our components so that they use the dispatcher (or, even better, make use of FruitActionCreators' methods).

If you're feeling really stuck, you can git checkout master to see the final version.

gz bud

u shud now be ok to read other flux tuts that r a bit more advanced :)

Where do I go now?

Away to a place that will teach you to code really well for free.

Good Flux Resources

####Videos:
A basic flux app. Skims over a lot, has some unexplained requires, but a good video nonetheless

Easy to understand app. Funky code and madness modularization, but really good explanation of some of the core Fluxian concepts, and nice code to read on the repo

####Posts: A really nice and short breakdown of the core concepts of Flux. Highly recommended

Good Prerequisite Resources

A solid intro to git
My react tutorial (WIP) (am I allowed to put this here?)
A great, brief react overview