Skip to content

Commit ac02eba

Browse files
committed
Rewrite
1 parent 789b548 commit ac02eba

File tree

17 files changed

+180
-372
lines changed

17 files changed

+180
-372
lines changed

README.md

Lines changed: 29 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ Read **[The Evolution of Flux Frameworks](https://medium.com/@dan_abramov/the-ev
1616
* A hook for the future devtools to "commit" a state, and replay actions on top of it during hot reload.
1717
* No `createAction`, `createStores`, `wrapThisStuff`. Your stuff is your stuff.
1818
* I don't mind action constants. Seriously.
19-
* Embrace decorators for React components.
2019
* Keep Flux lingo. No cursors or observables in core.
2120
* Have I mentioned hot reloading yet?
2221

@@ -114,18 +113,21 @@ export default function counterStore(state = initialState, action) {
114113

115114
### Components
116115

117-
#### Observing a single Store
116+
#### Dumb Components
118117

119118
```js
120-
// We're gonna need some decorators
121-
import React from 'react';
122-
import { observes } from 'redux';
119+
// The dumb component receives everything using props:
120+
import React, { PropTypes } from 'react';
123121

124-
// Gonna subscribe it
125-
@observes('counterStore')
126122
export default class Counter {
123+
static propTypes = {
124+
counter: PropTypes.number.isRequired,
125+
increment: PropTypes.func.isRequired,
126+
decrement: PropTypes.func.isRequired
127+
};
128+
127129
render() {
128-
const { counter } = this.props; // injected by @observes
130+
const { counter } = this.props;
129131
return (
130132
<p>
131133
Clicked: {counter} times
@@ -135,96 +137,40 @@ export default class Counter {
135137
}
136138
```
137139

138-
#### Observing many Stores
140+
#### Smart Components
139141

140142
```js
141-
// We're gonna need some decorators
142-
import React from 'react';
143-
import { observes } from 'redux';
144-
145-
// With multiple stores, you might want to specify a prop mapper as last argument.
146-
// You can also access `props` inside the prop mapper.
147-
@observes('counterStore', 'todoStore', (state, props) => ({
148-
counter: state.counterStore.counter,
149-
todos: state.todoStore.todos
150-
}))
151-
export default class TodosWithCounter {
152-
/* ... */
153-
}
154-
```
143+
// The smart component may inject actions
144+
// and observe stores using <Container />:
155145

156-
#### Performing a single Action
146+
import React, { Component } from 'react';
147+
import { Root, Container } from 'redux';
148+
import { increment, decrement } from './actions/CounterActions';
149+
import counterStore from './stores/counterStore';
150+
import Counter from './Counter';
157151

158-
```js
159-
// We're gonna need some decorators
160-
import React from 'react';
161-
import { performs } from 'redux';
162-
163-
// Gonna inject it
164-
@performs('increment')
165-
export default class IncrementButton {
152+
export default class CounterContainer {
166153
render() {
167-
const { increment } = this.props; // injected by @performs
154+
// stores can be a single store or an array.
155+
// actions can only be a string -> function map.
156+
// props passed to children will combine these actions and state.
168157
return (
169-
<button onClick={increment}>+</button>
158+
<Container stores={counterStore}
159+
actions={{ increment, decrement }}>
160+
{props => <Counter {...props} />}
161+
</Container>
170162
);
171163
}
172164
}
173165
```
174166

175-
#### Performing many Actions
167+
#### The root component
176168

177169
```js
178-
// We're gonna need some decorators
179170
import React from 'react';
180-
import { performs } from 'redux';
181-
182-
// With multiple actions, you might want to specify a prop mapper as last argument.
183-
// You can also access `props` inside the prop mapper.
184-
@performs('increment', 'decrement', (actions, props) => ({
185-
increment: props.invert ? actions.decrement : actions.increment,
186-
decrement: props.invert ? actions.increment : actions.decrement
187-
}))
188-
export default class IncrementButton {
189-
/* .... */
190-
}
191-
```
192-
193-
### Dispatcher
171+
import { Root } from 'redux';
194172

195-
#### Creating a hot-reloadable dispatcher
196-
197-
```js
198-
import * as stores from './stores/index';
199-
import * as actions from './actions/index';
200-
import { createDispatcher } from 'redux';
201-
202-
// Prefer to use existing dispatcher
203-
const dispatcher =
204-
module.hot && module.hot.data && module.hot.data.dispatcher ||
205-
createDispatcher();
206-
207-
// Pass (potentially hot-reloaded) stores and actions
208-
dispatcher.receive(stores, actions);
209-
210-
// Store the dispatcher for the next hot reload
211-
if (module.hot) {
212-
module.hot.dispose(data => {
213-
data.dispatcher = dispatcher;
214-
});
215-
}
216-
217-
export default dispatcher;
218-
```
219-
220-
#### Attaching the dispatcher to the root component
221-
222-
```js
223-
import React from 'react';
224-
import { provides } from 'redux';
225-
import dispatcher from './dispatcher';
226-
227-
@provides(dispatcher)
173+
@Root
228174
export default class App {
229175
/* ... */
230176
}
@@ -238,10 +184,6 @@ export default class App {
238184
* http://gaearon.github.io/react-hot-loader/
239185
* those `module.hot` lines in the dispatcher example above
240186

241-
### But you're using strings for injecting actions and store state!
242-
243-
I'm not super happy about strings. If you find a better way, let me know and file an issue with your suggestions.
244-
245187
### Can I use this in production?
246188

247189
I wouldn't. Many use cases are not be considered yet. If you find some use cases this lib can't handle yet, please file an issue.

examples/counter/App.js

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
import React, { Component } from 'react';
2+
import { Root, Container } from 'redux';
3+
import { increment, decrement } from './actions/CounterActions';
4+
import counterStore from './stores/counterStore';
25
import Counter from './Counter';
3-
import { provides } from 'redux';
4-
import dispatcher from './dispatcher';
56

6-
@provides(dispatcher)
7+
@Root
78
export default class App extends Component {
89
render() {
910
return (
10-
<Counter />
11+
<Container stores={counterStore}
12+
actions={{ increment, decrement }}>
13+
{props => <Counter {...props} />}
14+
</Container>
1115
);
1216
}
1317
}

examples/counter/Counter.js

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
1-
import React from 'react';
2-
import { performs, observes } from 'redux';
1+
import React, { PropTypes } from 'react';
32

4-
@performs('increment', 'decrement')
5-
@observes('counterStore')
63
export default class Counter {
4+
static propTypes = {
5+
increment: PropTypes.func.isRequired,
6+
decrement: PropTypes.func.isRequired,
7+
counter: PropTypes.number.isRequired
8+
};
9+
710
render() {
8-
const { increment, decrement } = this.props;
11+
const { increment, decrement, counter } = this.props;
912
return (
1013
<p>
11-
Clicked: {this.props.counter} times
14+
Clicked: {counter} times
1215
{' '}
1316
<button onClick={() => increment()}>+</button>
1417
{' '}

examples/counter/actions/index.js

Lines changed: 0 additions & 1 deletion
This file was deleted.

examples/counter/dispatcher.js

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

examples/counter/stores/index.js

Lines changed: 0 additions & 1 deletion
This file was deleted.

examples/todo/App.js

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,23 @@
11
import React from 'react';
22
import Header from './Header';
33
import Body from './Body';
4-
import { provides } from 'redux';
5-
import dispatcher from './dispatcher';
4+
import { Root, Container } from 'redux';
5+
import { todoStore } from './stores/index';
6+
import { addTodo } from './actions/index';
67

7-
@provides(dispatcher)
8+
@Root
89
export default class App {
910
render() {
1011
return (
11-
<div>
12-
<Header />
13-
<Body />
14-
</div>
12+
<Container stores={todoStore}
13+
actions={{ addTodo }}>
14+
{props =>
15+
<div>
16+
<Header addTodo={props.addTodo} />
17+
<Body todos={props.todos} />
18+
</div>
19+
}
20+
</Container>
1521
);
1622
}
1723
}

examples/todo/Body.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
import React from 'react';
2-
import { observes } from 'redux';
32

4-
@observes('todoStore')
53
export default class Body {
64
render() {
75
return (

examples/todo/Header.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
import React from 'react';
2-
import { performs } from 'redux';
32

4-
@performs('addTodo')
53
export default class Header {
64
render() {
75
return (

examples/todo/dispatcher.js

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

src/Container.js

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import { Component, PropTypes } from 'react';
2+
import mapValues from 'lodash/object/mapValues';
3+
import identity from 'lodash/utility/identity';
4+
5+
export default class ReduxContainer extends Component {
6+
static contextTypes = {
7+
wrapActionCreator: PropTypes.func.isRequired,
8+
observeStores: PropTypes.func.isRequired
9+
};
10+
11+
static propTypes = {
12+
children: PropTypes.func.isRequired,
13+
actions: PropTypes.object.isRequired,
14+
stores: PropTypes.oneOfType([
15+
PropTypes.func.isRequired,
16+
PropTypes.arrayOf(PropTypes.func.isRequired).isRequired,
17+
PropTypes.object.isRequired
18+
]).isRequired
19+
}
20+
21+
static defaultProps = {
22+
stores: [],
23+
actions: {}
24+
};
25+
26+
constructor(props, context) {
27+
super(props, context);
28+
this.handleChange = this.handleChange.bind(this);
29+
this.update(props);
30+
}
31+
32+
componentWillReceiveProps(nextProps) {
33+
this.update(nextProps);
34+
}
35+
36+
componentWillUnmount() {
37+
this.unsubscribe();
38+
}
39+
40+
update(props) {
41+
this.actions = mapValues(props.actions, this.context.wrapActionCreator);
42+
if (this.unsubscribe) {
43+
this.unsubscribe();
44+
}
45+
46+
let stores = props.stores;
47+
let mapState = identity;
48+
if (typeof props.stores === 'function') {
49+
const store = props.stores;
50+
stores = [store];
51+
mapState = state => state[store.name];
52+
}
53+
54+
this.mapState = mapState;
55+
this.unsubscribe = this.context.observeStores(stores, this.handleChange);
56+
}
57+
58+
handleChange(stateFromStores) {
59+
const nextState = this.mapState(stateFromStores);
60+
if (this.state) {
61+
this.setState(nextState);
62+
} else {
63+
this.state = nextState;
64+
}
65+
}
66+
67+
render() {
68+
return this.props.children({
69+
...this.actions,
70+
...this.state
71+
});
72+
}
73+
}

src/Root.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import React, { Component, PropTypes } from 'react';
2+
import createDispatcher from './createDispatcher';
3+
4+
export default function root(DecoratedComponent) {
5+
return class ReduxRoot extends Component {
6+
static childContextTypes = {
7+
observeStores: PropTypes.func.isRequired,
8+
wrapActionCreator: PropTypes.func.isRequired
9+
};
10+
11+
getChildContext() {
12+
return {
13+
observeStores: this.dispatcher.observeStores,
14+
wrapActionCreator: this.dispatcher.wrapActionCreator
15+
};
16+
}
17+
18+
constructor(props, context) {
19+
super(props, context);
20+
this.dispatcher = createDispatcher();
21+
}
22+
23+
render() {
24+
return <DecoratedComponent {...this.props} />;
25+
}
26+
};
27+
}

0 commit comments

Comments
 (0)