Home

Awesome

bloody-flux

a server & client-side friendly implementation of flux

Build Status

bloody-flux is a server & client-friendly implementation of the flux architecture.

install

$ npm install bloody-flux --save

require

// ES6 module syntax
import { Dispatcher, Store, StoreReceiver, InitialData } from "bloody-flux"
// commonjs + ES6
var { Dispatcher, Store, StoreReceiver, InitialData } = require("bloody-flux")
// commonjs + ES5
var Flux = require("bloody-flux")
var Dispatcher = Flux.Dispatcher
// …

design choices

in order to be able to render components on the server, the singleton pattern for the stores & the dispatcher simply makes concurrency impossible. therefore in this flux implementation, stores & dispatchers are only used as singletons for the client-side.

using instances for the dispatcher also means you can't just require them to access them. to make their use possible, the dispatcher keeps its stores in a map, and components only need to get this dispatcher. in this implementation this is done through the react components' context.

here, actions creators are just simple functions without the dispatcher as dependency, they just return the action object, and you need to explicitely use dispatcher.dispatch(actionCreator(params)). this enables us to easily test them, and to avoid an extra dependency that makes it difficult to work with both the server and the client.

as mixins aren't available in ES6 classes, and the react team chose not to keep that feature before the language itself brings a good solution to compose classes, the way that is used to attach a store to a component is a higher- order component getting the stores data in its state, and passes it as props to the component needing it. to do that, a factory is used to "wrap" the user's components. a good consequence of this is that the user's component doesn't have to be stateful anymore, as its data comes in its props.

API

Dispatcher

const dispatcher = new Dispatcher()

creates a new dispatcher instance. Dispatcher is a subclass of facebook's flux dispatcher. you have its features in bloody-flux's dispatcher too.

dispatcher.registerStore(store)

registers a store, and makes it listen to dispatched actions.

the store must have a registerDispatcher method and a static displayName string property

dispatcher.getStore(displayName)

returns the registered store matching displayName

dispatcher.dispatch(action)

dispatches an action to the stores.

this action must be an object, and must contain a non-null type property in order to identify this action.

Dispatcher.getContextType()

shorthand to get a valid react PropType for a dispatcher instance.

import React, {Component} from "react"
import {Dispatcher} from "bloody-flux"

class MyComponent extends Component {
  static contextTypes = {
    ...Dispatcher.getContextType()
  }
}

Store

class MyStore extends Store { … }

to create a store, you first need to extend it.

static displayName

you must define a unique displayName string as a static property in order to identify your store.

the dispatcher will throw an error if you attempt to register two objects with the same static displayName

state

you can define an original state by setting a state object in your class spec.

// with ES7 property initializer
class MyStore extends Store {
  state = {
    property1 : value1
    // …
  }
}

// with ES6
class MyStore extends Store {
  constructor() {
    this.state = {
      property1 : value1
      // …
    }
    super()
  }
}

setState(nextState)

like react's setState, this method merges nextState with this.state in a new object, and emits a change event.

replaceState(nextState)

replaces the state by nextState, and emits a change event.

shouldEmitChange(prevState, nextState)

a method you can optionally define for a better performance if you send too much unnecessary change events. the change event is only sent if shouldEmitChange returns a truthy value.

by default, shouldEmitChange always returns true

class MyStore extends Store {
  // …
  shouldEmitChange(prevState, nextState) {
    if(prevState.id === nextState.id) {
      // don't send a change event if id didn't change
      return false
    }
    return true
  }
}

getActionHandlers()

a method where you return what method(s) should be executed for a given action. you must return an object with functions as values and action types as keys.

for a nicer syntax, you can take advantage of ES6's computed property names

methods are ran with the store as thisValue

import {ActionTypes} from "../constants"

class MyStore extends Store {
  updatePost(post) {
    this.setState({
      ...post.response
    })
  }
  getActionHandlers() {
    return {
      [ActionTypes.RECEIVE_POST] : this.updatePost,
      // you can even execute mutiple methods for a given action
      [ActionTypes.SOME_ACTION] : (action) => {
        this.someOtherMethod()
        this.updatePost(action)
      }
    }
  }
}

dispatcher

the store's dispatcher is set as the dispatcher once the store has been registered. this is useful to emit actions from the store API calls and to fetch other stores (for such features as waitFor)

dispatchToken

the store identifier in dispatcher.

store.addChangeListener(func)

registers func as a change listener. func will be executed each time a change event is emitted.

func will be passed the store's displayName as parameter

store.removeChangeListener(func)

unregisters func from the change listeners.

store.emitChange()

emits a change event.

StoreReceiver

StoreReceiver(ReactComponentClass)

factory to get store's data in ReactComponentClass props.

static stores

in the ReactComponentClass you wrap with StoreReceiver, you define which stores you want to receive data from in an object with displayNames as keys, and the props names you want them in as values.

import React, {Component} from "react"

class ReactComponentClass extends Component {
  // ReactComponentClass will receive PostStore's state in its "post" prop
  // and OtherStores's data in its "foo" prop
  statics stores = {
    PostStore : "post",
    OtherStore : "foo",
  }

  render() {
    return (
      <div>
        {this.props.post.title}
        {this.props.foo.someProp}
      </div>
    )
  }
}

InitialData

the InitialData react component is a component that is meant to be used with the renderToStaticMarkup react method on the server, to export data from the store in <script> tags with text/json type (so that the browser doesn't try to execute the JSON code).

the stores, when initialized, try to find a <script> tag with a data-storename attribute with their displayName as value.

prop stores

just pass the stores displayNames you want in an array passed to the stores prop.

InitialData must have the current dispatcher instance in its context.

import React, {Component} from "react"
import Dispatcher from "./Dispatcher"
import InitialData from "./InitialData"

const dispatcher = new Dispatcher()

// …

class App extends Component {
  static childContextTypes = {
    ...Dispatcher.getContextType()
  }

  getChildContext() {
    return {
      dispatcher
    }
  }

  render() {
    return (
      <this.props.component {...this.props} />
    )
  }
}

const stores = [
  "PostStore",
  "AuthorStore",
]
const initialData = React.renderToStaticMarkup(
  <App component={InitialData} stores={stores} />
)

adding the dispatcher in a react component's context

you need a parent component to add the dispatcher to your react components :

import React, {Component} from "react"
import Dispatcher from "./Dispatcher"

// create your app's dispatcher
// and register some stores
const dispatcher = new Dispatcher()

class App extends Component {
  static childContextTypes = {
    ...Dispatcher.getContextType()
  }
  getChildContext() {
    return {
      dispatcher
    }
  }
  render() {
    // return your component here
  }
}

React.render(<App />, mountNode)

license