Skip to content

Commit d5c2b16

Browse files
committed
Merge pull request #60 from gaearon/middleware-single-extension-point
Support custom dispatchers
2 parents 241a488 + 09f4190 commit d5c2b16

15 files changed

+191
-155
lines changed

README.md

Lines changed: 72 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -54,23 +54,23 @@ export function decrement() {
5454

5555
// Can also be async if you return a function
5656
export function incrementAsync() {
57-
return perform => {
57+
return dispatch => {
5858
setTimeout(() => {
59-
// Yay! Can invoke sync or async actions with `perform`
60-
perform(increment());
59+
// Yay! Can invoke sync or async actions with `dispatch`
60+
dispatch(increment());
6161
}, 1000);
6262
};
6363
}
6464

6565

6666
// Could also read state of a store in the callback form
6767
export function incrementIfOdd() {
68-
return (perform, { counter }) => {
68+
return (dispatch, { counter }) => {
6969
if (counter % 2 === 0) {
7070
return;
7171
}
7272

73-
perform(increment());
73+
dispatch(increment());
7474
};
7575
}
7676
```
@@ -137,10 +137,10 @@ export default class Counter {
137137

138138
```js
139139
// The smart component may observe stores using `<Connector />`,
140-
// and bind actions to the dispatcher with `bindActions`.
140+
// and bind actions to the dispatcher with `bindActionCreators`.
141141

142142
import React from 'react';
143-
import { Connector, bindActions } from 'redux';
143+
import { Connector, bindActionCreators } from 'redux';
144144
import Counter from '../components/Counter';
145145
import * as CounterActions from '../actions/CounterActions';
146146

@@ -155,10 +155,10 @@ export default class CounterApp {
155155
render() {
156156
return (
157157
<Connector select={select}>
158-
{({ counter, dispatcher }) =>
158+
{({ counter, dispatch }) =>
159159
/* Yes this is child as a function. */
160160
<Counter counter={counter}
161-
{...bindActions(CounterActions, dispatcher)} />
161+
{...bindActionCreators(CounterActions, dispatch)} />
162162
}
163163
</Connector>
164164
);
@@ -172,7 +172,7 @@ The `@connect` decorator lets you create smart components less verbosely:
172172

173173
```js
174174
import React from 'react';
175-
import { connect, bindActions } from 'redux';
175+
import { connect, bindActionCreators } from 'redux';
176176
import Counter from '../components/Counter';
177177
import * as CounterActions from '../actions/CounterActions';
178178

@@ -181,49 +181,92 @@ import * as CounterActions from '../actions/CounterActions';
181181
}))
182182
export default class CounterApp {
183183
render() {
184-
const { counter, dispatcher } = this.props;
184+
const { counter, dispatch } = this.props;
185185
return (
186186
<Counter counter={counter}
187-
{...bindActions(CounterActions, dispatcher)} />
187+
{...bindActionCreators(CounterActions, dispatch)} />
188188
);
189189
}
190190
}
191191
```
192192

193-
#### The root component
193+
#### Initializing Redux
194194

195-
Decorate your top-level component with `@provider(dispatcher)` (or `<Provider dispatcher={dispatcher}>` inside) to bind it to a Redux dispatcher instance.
196-
197-
Redux dispatcher accepts a single Store as an argument. Usually Flux apps have many Stores, so Redux provides a `composeStore` method that turns an object with Store functions as values (such as what you'd get from `import * as stores`) into a Store that [composes](https://gist.github.com/gaearon/d77ca812015c0356654f) them.
198-
199-
Think of `composeStores` as a “higher-order” Store because it creates a Store from several Stores. (You don't have to use it! You can just pass your own top-level Store function if that's what you prefer.)
195+
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:
200196

201197
```js
202-
import React from 'react';
203-
import { createDispatcher, Provider, composeStores } from 'redux';
204-
import CounterApp from './CounterApp';
205-
import TodoApp from './TodoApp';
198+
import { createRedux, Provider } from 'redux';
206199
import * as stores from '../stores/index';
207200

208-
const dispatcher = createDispatcher(composeStores(stores));
201+
const redux = createRedux(stores);
202+
```
203+
204+
Then pass `redux` as a prop to `<Provider>` component in the root component of your app, and you're all set:
209205

206+
```js
210207
export default class App {
211208
render() {
212209
return (
213-
<Provider dispatcher={dispatcher}>
210+
<Provider redux={redux}>
214211
{() =>
215-
/* Yep, function as a child. */
216-
<div>
217-
<CounterApp />
218-
<TodoApp />
219-
</div>
212+
<CounterApp />
220213
}
221214
</Provider>
222215
);
223216
}
224217
}
225218
```
226219

220+
#### Running the same code on client and server
221+
222+
The `redux` instance returned by `createRedux` also has the `dispatch(action)`, `subscribe()` and `getState()` methods that you may call outside the React components.
223+
224+
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:
225+
226+
```js
227+
// server
228+
const redux = createRedux(stores);
229+
redux.dispatch(MyActionCreators.doSomething()); // fire action creators to fill the state
230+
const state = redux.getState(); // somehow pass this state to the client
231+
232+
// client
233+
const initialState = window.STATE_FROM_SERVER;
234+
const redux = createRedux(stores, initialState);
235+
```
236+
237+
#### Additional customization
238+
239+
There is also a longer way to do the same thing, if you need additional customization.
240+
241+
This:
242+
243+
```js
244+
import { createRedux } from 'redux';
245+
import * as stores from '../stores/index';
246+
247+
const redux = createRedux(stores);
248+
```
249+
250+
is in fact a shortcut for this:
251+
252+
```js
253+
import { createRedux, createDispatcher, composeStores } from 'redux';
254+
import * as stores from '../stores/index';
255+
256+
// Compose all your Stores into a single Store function with `composeStores`:
257+
const store = composeStores(stores);
258+
259+
// Create a Dispatcher function for your composite Store:
260+
const dispatcher = createDispatcher(store);
261+
262+
// Create a Redux instance using the dispatcher function:
263+
const redux = createRedux(dispatcher);
264+
```
265+
266+
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.
267+
268+
When in doubt, use the shorter option!
269+
227270
## FAQ
228271

229272
### How does hot reloading work?

examples/actions/CounterActions.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,19 @@ export function increment() {
77
}
88

99
export function incrementIfOdd() {
10-
return (perform, { counter }) => {
10+
return (dispatch, { counter }) => {
1111
if (counter % 2 === 0) {
1212
return;
1313
}
1414

15-
perform(increment());
15+
dispatch(increment());
1616
};
1717
}
1818

1919
export function incrementAsync() {
20-
return perform => {
20+
return dispatch => {
2121
setTimeout(() => {
22-
perform(increment());
22+
dispatch(increment());
2323
}, 1000);
2424
};
2525
}

examples/containers/App.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
import React from 'react';
2-
import { createDispatcher, Provider, composeStores } from 'redux';
32
import CounterApp from './CounterApp';
43
import TodoApp from './TodoApp';
4+
import { createRedux, Provider } from 'redux';
55
import * as stores from '../stores/index';
66

7-
const dispatcher = createDispatcher(composeStores(stores));
7+
const redux = createRedux(stores);
88

99
export default class App {
1010
render() {
1111
return (
12-
<Provider dispatcher={dispatcher}>
12+
<Provider redux={redux}>
1313
{() =>
1414
<div>
1515
<CounterApp />

examples/containers/CounterApp.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React from 'react';
2-
import { connect, bindActions } from 'redux';
2+
import { connect, bindActionCreators } from 'redux';
33
import Counter from '../components/Counter';
44
import * as CounterActions from '../actions/CounterActions';
55

@@ -8,10 +8,10 @@ import * as CounterActions from '../actions/CounterActions';
88
}))
99
export default class CounterApp {
1010
render() {
11-
const { counter, dispatcher } = this.props;
11+
const { counter, dispatch } = this.props;
1212
return (
1313
<Counter counter={counter}
14-
{...bindActions(CounterActions, dispatcher)} />
14+
{...bindActionCreators(CounterActions, dispatch)} />
1515
);
1616
}
1717
}

examples/containers/TodoApp.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React from 'react';
2-
import { bindActions, Connector } from 'redux';
2+
import { bindActionCreators, Connector } from 'redux';
33
import AddTodo from '../components/AddTodo';
44
import TodoList from '../components/TodoList';
55
import * as TodoActions from '../actions/TodoActions';
@@ -13,8 +13,8 @@ export default class TodoApp {
1313
);
1414
}
1515

16-
renderChild({ todos, dispatcher }) {
17-
const actions = bindActions(TodoActions, dispatcher);
16+
renderChild({ todos, dispatch }) {
17+
const actions = bindActionCreators(TodoActions, dispatch);
1818
return (
1919
<div>
2020
<AddTodo {...actions} />

src/Dispatcher.js

Lines changed: 0 additions & 59 deletions
This file was deleted.

src/Redux.js

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import createDispatcher from './createDispatcher';
2+
import composeStores from './utils/composeStores';
3+
4+
export default class Redux {
5+
constructor(dispatcher, initialState) {
6+
if (typeof dispatcher === 'object') {
7+
// A shortcut notation to use the default dispatcher
8+
dispatcher = createDispatcher(composeStores(dispatcher));
9+
}
10+
11+
this.state = initialState;
12+
this.listeners = [];
13+
this.replaceDispatcher(dispatcher);
14+
}
15+
16+
getDispatcher() {
17+
return this.dispatcher;
18+
}
19+
20+
replaceDispatcher(nextDispatcher) {
21+
this.dispatcher = nextDispatcher;
22+
this.dispatchFn = nextDispatcher(this.state, ::this.setState);
23+
}
24+
25+
dispatch(action) {
26+
return this.dispatchFn(action);
27+
}
28+
29+
getState() {
30+
return this.state;
31+
}
32+
33+
setState(nextState) {
34+
this.state = nextState;
35+
this.listeners.forEach(listener => listener());
36+
}
37+
38+
subscribe(listener) {
39+
this.listeners.push(listener);
40+
41+
return () => {
42+
const index = this.listeners.indexOf(listener);
43+
this.listeners.splice(index, 1);
44+
};
45+
}
46+
}

0 commit comments

Comments
 (0)