
More akin to Redux than Facebook flux, fluxx
lets you manage
your application state in a terse, centralized and scalable way.
It also has first-class support for typescript.
Content
Store
There are two kinds of Stores.
GlobalStore
GlobalStores hold global state, are always active and can be depended upon by many views.
import { GlobalStore, Action } from 'fluxx'
import otherStore from './otherStore'
const increment = Action('increment')
const decrement = Action('decrement')
const store = GlobalStore({
state: 0,
handlers: {
[action.increment]: (state, by) => state + by,
[action.decrement]: (state, by) => state - by
}
});
store.subscribe(newState => console.log(newState))
action.increment(30)
action.decrement(5)
console.log(store.state === 25)
LocalStore
LocalStore
s are transient stores that should be created when its view needs it and destroyed when that view is no longer displayed.
They have the same API as GlobalStore
s, and an additional onDispose
callback, which is called when the last unsubscribe
function (returned from a subscribe
call) is invoked.
Important note: Inside stores, Received actions are matched by reference, so it is best to only use LocalStore
s for singleton views, and not reusable components.
Otherwise, one action dispatched by one of the views would be received by all the currently active LocalStore instances of the same kind.
Preventing the store from dispatching the change event
Simply return the same state reference in the update handler and the store won't dispatch this event.
Manually redrawing the view on store change
import { store, action } from './valueStore'
const unsubscribe = store.subscribe(render)
function render() {
console.log('render!')
}
action.increment(33)
Async actions
Fluxx don't have these as they are not necessary.
As an example, here's a suggestion of how one could structure her code performing ajax calls:
import { savingTodo, todoSaved, todoSaveFailed } from './actions';
export default function(todo) {
savingTodo(todo)
fetch('/todos', { method: 'post', body: todo })
.then(res => todoSaved(todo))
.catch(err => todoSaveFailed(todo))
}
React connector
To use both React
and fluxx
, a connector add-on is provided to connect any component to the store.
The connected component will only re-render when the store's datum of interest actually changed.
This means you can not just mutate the store's state, its reference should change if it was actually updated.
This is consistent with the various React practices needed to get good performances.
If you wish to use basic Objects and Arrays as your store's state, you may want to check immupdate.
Which components should be connected to the store? The answer is 'some of them'.
Too few connected components and you will have to write a lot of boilerplate to manually pass down props instead.
Furthermore, you will suffer mild performance penalties as parents will 'update' simply because they need to pass
some data to their children as props.
Too many connected components and the data flow becomes a bit harder to follow.
As a rule of thumb, connect at least the smart components managing a particular routing hierarchy, plus any heavily nested
smart component that needs some data its parents don't.
import connect from 'fluxx/lib/ReactConnector'
import store, { incrementBy } from './store'
class Blue extends React.Component {
render() {
const { count } = this.props
return (
<p onClick={ incrementBy10 }>{ count }</p>
)
}
}
function incrementBy10() { incrementBy(10) }
export default connect(Blue, store, state => (
{ count: state.blue.count }
))
connect
can work with either GlobalStore
or LocalStore
instances. A LocalStore
should be wrapped inside a function so that the wrapping component can create a new Store
instance everytime the component is mounted. That function is passed the view's initial props
as its only argument.
Enabling logging
This will log all action dispatching along with the updated state of the store afterwards.
import { Store } from 'fluxx'
Store.log = true
Full example
Coming soon...
Typescript store
fluxx has first class support for typescript. This means EVERYTHING will be type-safe!
However, to achieve that, a few changes are required compared to using fluxx with plain javascript:
Action declaration
import { Action } from 'fluxx'
export const increment = Action('increment')
export const incrementBy = Action<number>('incrementBy')
Store creation
import { GlobalStore } from 'fluxx'
import update from 'immupdate'
interface State {
count: number,
somethingElse: string
}
const initialState = { count: 0, somethingElse: '' }
GlobalStore(initialState, on => {
on(action.incrementBy, (state, by) => update(state, { count: c => c + by }))
})
Connecting a React component to the store
import connect from 'fluxx/lib/ReactConnector'
import store, { incrementBy } from './store'
interface ParentProps {
params: { id: string },
children: React.ReactElement<any>
}
interface StoreProps {
count: number
}
type Props = ParentProps & StoreProps;
class Blue extends React.Component<Props, void> {
render() {
const { count, params: { id } } = this.props
return (
<p onClick={ incrementBy10 }>{ count }</p>
)
}
}
function incrementBy10() { incrementBy(10) }
export default connect(Blue, store, (state): StoreProps => (
{ count: state.count }
))
Running the tests
npm run test