redux
An experiment in fully hot-reloadable Flux.
The API might change any day.
Don't use in production just yet.
Why another Flux framework?
Read The Evolution of Flux Frameworks for some context.
Design Goals
- Hot reloading of everything.
- A hook for the future devtools to "commit" a state, and replay actions on top of it during hot reload.
- No wrapper calls in your stores and actions. Your stuff is your stuff.
- Super easy to test things in isolation without mocks.
- I don't mind action constants. Seriously.
- Keep Flux lingo. No cursors or observables in core.
- Have I mentioned hot reloading yet?
Demo
git clone https://github.com/gaearon/redux.git redux
cd redux
npm install
npm start
What does it look like?
Actions
import { INCREMENT_COUNTER, DECREMENT_COUNTER } from '../constants/ActionTypes';
export function increment() {
return {
type: INCREMENT_COUNTER
};
}
export function decrement() {
return {
type: DECREMENT_COUNTER
};
}
export function incrementAsync() {
return dispatch => {
setTimeout(() => {
dispatch(increment());
}, 1000);
};
}
export function incrementIfOdd() {
return (dispatch, { counter }) => {
if (counter % 2 === 0) {
return;
}
dispatch(increment());
};
}
Stores
import { INCREMENT_COUNTER, DECREMENT_COUNTER } from '../constants/ActionTypes';
export default function counter(state = 0, action) {
switch (action.type) {
case INCREMENT_COUNTER:
return state + 1;
case DECREMENT_COUNTER:
return state - 1;
default:
return state;
}
}
Components
Dumb Components
import React, { PropTypes } from 'react';
export default 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>
);
}
}
Smart Components
import React from 'react';
import { bindActionCreators } from 'redux';
import { Connector } from 'redux/react';
import Counter from '../components/Counter';
import * as CounterActions from '../actions/CounterActions';
function select(state) {
return { counter: state.counter };
}
export default class CounterApp {
render() {
return (
<Connector select={select}>
{({ counter, dispatch }) =>
/* Yes this is child as a function. */
<Counter counter={counter}
{...bindActionCreators(CounterActions, dispatch)} />
}
</Connector>
);
}
}
Decorators
The @connect
decorator lets you create smart components less verbosely:
import React from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'redux/react';
import Counter from '../components/Counter';
import * as CounterActions from '../actions/CounterActions';
@connect(state => ({
counter: state.counter
}))
export default class CounterApp {
render() {
const { counter, dispatch } = this.props;
return (
<Counter counter={counter}
{...bindActionCreators(CounterActions, dispatch)} />
);
}
}
React Native
To use Redux with React Native, just replace imports from redux/react
with redux/react-native
:
import { bindActionCreators } from 'redux';
import { Provider, Connector } from 'redux/react-native';
Initializing Redux
The simplest way to initialize a Redux instance is to give it an object whose values are your Store functions, and whose keys are their names. You may import *
from the file with all your Store definitions to obtain such an object:
import { createRedux } from 'redux';
import { Provider } from 'redux/react';
import * as stores from '../stores/index';
const redux = createRedux(stores);
Then pass redux
as a prop to <Provider>
component in the root component of your app, and you're all set:
export default class App {
render() {
return (
<Provider redux={redux}>
{() =>
<CounterApp />
}
</Provider>
);
}
}
Running the same code on client and server
The redux
instance returned by createRedux
also has the dispatch(action)
, subscribe()
and getState()
methods that you may call outside the React components.
You may optionally specify the initial state as the second argument to createRedux
. This is useful for hydrating the state you received from running Redux on the server:
const redux = createRedux(stores);
redux.dispatch(MyActionCreators.doSomething());
const state = redux.getState();
const initialState = window.STATE_FROM_SERVER;
const redux = createRedux(stores, initialState);
Additional customization
There is also a longer way to do the same thing, if you need additional customization.
This:
import { createRedux } from 'redux';
import * as stores from '../stores/index';
const redux = createRedux(stores);
is in fact a shortcut for this:
import { createRedux, createDispatcher, composeStores } from 'redux';
import thunkMiddleware from 'redux/lib/middleware/thunk';
import * as stores from '../stores/index';
const store = composeStores(stores);
const dispatcher = createDispatcher(
store,
getState => [thunkMiddleware(getState)]
);
const redux = createRedux(dispatcher);
Why would you want to write it longer? Maybe you're an advanced user and want to provide a custom Dispatcher function, or maybe you have a different idea of how to compose your Stores (or you're satisfied with a single Store). Redux lets you do all of this.
createDispatcher()
also gives you the ability to specify middleware -- for example, to add support for promises. Learn more about how to create and use middleware in Redux.
When in doubt, use the shorter option!
FAQ
How does hot reloading work?
Can I use this in production?
I wouldn't. Many use cases haven't been considered yet. If you find some use cases this lib can't handle yet, please file an issue.
But there are switch statements!
(state, action) => state
is as simple as a Store can get. You are free to implement your own createStore
:
export default function createStore(initialState, handlers) {
return (state = initialState, action) =>
handlers[action.type] ?
handlers[action.type](state, action) :
state;
}
and use it for your Stores:
export default createStore(0, {
[INCREMENT_COUNTER]: x => x + 1,
[DECREMENT_COUNTER]: x => x - 1
});
It's all just functions.
Fancy stuff like generating stores from handler maps, or generating action creator constants, should be in userland.
Redux has no opinion on how you do this in your project.
What about waitFor
?
I wrote a lot of vanilla Flux code and my only use case for it was to avoid emitting a change before a related Store consumes the action. This doesn't matter in Redux because the change is only emitted after all Stores have consumed the action.
If several of your Stores want to read data from each other and depend on each other, it's a sign that they should've been a single Store instead. See this discussion on how waitFor
can be replaced by the composition of stateless Stores.
My views aren't updating!
Redux makes a hard assumption that you never mutate the state passed to you. It's easy! For example, instead of
function (state, action) {
state.isAuthenticated = true;
state.email = action.email;
return state;
}
you should write
function (state, action) {
return {
...state,
isAuthenticated: true,
email: action.email
};
}
Read more about the spread properties ES7 proposal.
Inspiration and Thanks
Special thanks go to Jamie Paton for handing over the redux
NPM package name.