Create a gist now

Instantly share code, notes, and snippets.

@acdlite /flux.js
Last active May 8, 2017

What would you like to do?
A Redux-like Flux implementation in <75 lines of code
/**
* Basic proof of concept.
* - Hot reloadable
* - Stateless stores
* - Stores and action creators interoperable with Redux.
*/
import React, { Component } from 'react';
export default function dispatch(store, atom, action) {
return store(atom, action);
}
export class Dispatcher extends Component {
static propTypes = {
store: React.PropTypes.func.isRequired
};
static childContextTypes = {
dispatch: React.PropTypes.func,
atom: React.PropTypes.any
};
getChildContext() {
return {
atom: this.state.atom,
dispatch: this.dispatch.bind(this)
};
}
constructor(props, context) {
super(props, context);
this.state = { atom: dispatch(props.store, undefined, {}) };
}
dispatch(payload) {
this.setState(prevState => ({
atom: dispatch(this.props.store, prevState.atom, payload)
}));
}
render() {
return typeof this.props.children === 'function'
? this.props.children(this.state.atom)
: this.props.children;
}
}
export class Injector extends Component {
static contextTypes = {
dispatch: React.PropTypes.func.isRequired,
atom: React.PropTypes.any
};
static propTypes = {
actions: React.PropTypes.object
};
performAction(actionCreator, ...args) {
const { dispatch } = this.context;
const payload = actionCreator(...args);
return typeof payload === 'function'
? payload(dispatch)
: dispatch(payload);
};
render() {
const { dispatch, atom } = this.context;
const { actions: _actions } = this.props;
const actions = Object.keys(_actions).reduce((result, key) => {
result[key] = this.performAction.bind(this, _actions[key]);
return result;
}, {});
return this.props.children({ actions, atom });
}
}
/**
* Example usage
*
* Based on Redux's counter example
* https://github.com/gaearon/redux/tree/master/examples/counter
*/
import React, { Component, PropTypes } from 'react';
import { Dispatcher, Injector } from '../';
const INCREMENT_COUNTER = 'INCREMENT_COUNTER';
const DECREMENT_COUNTER = 'DECREMENT_COUNTER';
function counterStore(counter = 0, action) {
switch (action.type) {
case INCREMENT_COUNTER:
return counter + 1;
case DECREMENT_COUNTER:
return counter - 10;
default:
return counter;
}
}
function increment() {
return {
type: INCREMENT_COUNTER
};
}
function decrement() {
return {
type: DECREMENT_COUNTER
};
}
export default class CounterApp {
render() {
return (
<Dispatcher
// Instead of specifying an object of keys mapped to stores, just use a
// higher-order store!
store={(state = {}, action) => ({
counter: counterStore(state.counter, action)
})}
>
{() => (
<Injector actions={{ increment, decrement }}>
{({ actions, atom }) => (
<Counter
increment={actions.increment}
decrement={actions.decrement}
counter={atom.counter}
/>
)}
</Injector>
)}
</Dispatcher>
);
}
}
class Counter {
static propTypes = {
increment: PropTypes.func.isRequired,
decrement: PropTypes.func.isRequired,
counter: PropTypes.number.isRequired
};
render() {
const { increment, decrement, counter } = this.props;
return (
<p>
Clicked: {counter} times
{' '}
<button onClick={increment}>+</button>
{' '}
<button onClick={decrement}>-</button>
</p>
);
}
}
Owner

acdlite commented Jun 6, 2015

Two key differences from Redux:

  1. Instead of specifying an object of stores, just a single "higher-order store." See Dan's gist about combining stateless stores: https://gist.github.com/gaearon/d77ca812015c0356654f
  2. No subscribing to individual stores; the entire atom is sent on each change. This is simpler for both the user and the library author, and certainly more flexible, IMO. Use container components to pass down more specific parts of the state tree. If you're concerned about performance, that's what shouldComponentUpdate() is for.
Owner

acdlite commented Jun 6, 2015

Actually I guess "higher-order store" is technically incorrect, since it's not a store that returns another store. Oh well :D

gaearon commented Jun 6, 2015

This makes my weekend. 👍

gaearon commented Jun 6, 2015

Say we provide compose that takes an array of a map of Stores and combines them into a single Store. Now that would be a higher-order Store :-)

gaearon commented Jun 6, 2015

No subscribing to individual stores; the entire atom is sent on each change.

Yeah, I suppose we could do that. But technically I'd still put redux.observe in context instead of atom because context doesn't work well with shouldComponentUpdate currently.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment