forked from shaunpersad/redu
-
Notifications
You must be signed in to change notification settings - Fork 0
/
redu.js
191 lines (160 loc) · 5.49 KB
/
redu.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
/**
* Redu is comprised of two functions:
* storeOf(Component), and subscribe(Component).
* Both functions take in a React.Component, and create and return wrapper components around them.
*
* storeOf(Component) creates and returns a StoreComponent.
* subscribe(Component) creates and returns a SubscriberComponent.
*
* StoreComponents wrap your top-level component and manages the application-level state.
*
* SubscriberComponents can derive props directly out of the StoreComponent's state, props, and action functions,
* and pass them down to the components that they wrap.
*
* SubscriberComponents are linked to a StoreComponent via React's "context" feature:
* https://facebook.github.io/react/docs/context.html
*/
"use strict";
import React from 'react'; // peer dependency
import PropTypes from 'prop-types'; // bundled dependency
/**
* For use with setting up React contexts.
*/
const contextTypes = {
storeComponent: PropTypes.object.isRequired
};
/**
* Creates a StoreComponent that wraps the given component.
*
* The given component should be a top-level component.
* Generally you're only going to want to have one usage of this,
* to wrap your top-most component.
*
* All SubscriberComponent descendants of this StoreComponent will be able to derive any props they need out of
* this StoreComponent's state, props, and actions.
*
* @param {React.Component|SubscriberComponent} Component
* @returns {StoreComponent}
*/
export function storeOf(Component) {
/**
* An object containing action functions that will be bound to the StoreComponent,
* and made available through the "actions" property. Action functions will generally call "this.setState",
* which will update the StoreComponent's state.
*
* Actions can be defined with the "withActions" method.
*
* @type {{}}
* @private
*/
let _actions = {};
/**
* The initial state of the StoreComponent. It should represent a complete picture of your application-level state.
*
* The initial state can be defined with the "withInitialState" method.
*
* @type {{}}
* @private
*/
let _initialState = {};
class StoreComponent extends React.Component {
constructor(props = {}) {
super(props);
this.state = Object.assign({}, _initialState); // set the initial state.
this.actions = {}; // make the action functions available via the "actions" property.
Object.keys(_actions).forEach(action => {
this.actions[action] = _actions[action].bind(this); // bind to the StoreComponent.
});
}
/**
* Use React's "context" feature to maintain a link from the StoreComponent to SubscriberComponents.
*
* @returns {{storeComponent: StoreComponent}}
*/
getChildContext() {
return { storeComponent: this };
}
/**
* Render the supplied component, and pass through any props.
*
* @returns {*|Element}
*/
render() {
return React.createElement(Component, this.props);
}
/**
* Defines the StoreComponent's actions.
*
* @param {{}} actions
* @returns {StoreComponent}
*/
static withActions(actions = {}) {
_actions = actions;
return this;
}
/**
* Defines the StoreComponent's initial state.
*
* @param {{}} initialState
* @returns {StoreComponent}
*/
static withInitialState(initialState = {}) {
_initialState = initialState;
return this;
}
/**
* Necessary to use React's "context" feature.
*
* @returns {*}
*/
static get childContextTypes() {
return Object.assign({}, contextTypes, super.contextTypes || {});
}
}
return StoreComponent;
}
/**
* Creates a SubscriberComponent that wraps the given component.
*
* All SubscriberComponents have the ability to derive any props they need out of
* this StoreComponent's state, props, and actions, by supplying a "toProps" function,
* which can be set via the "withProps" method.
*
* @param {React.Component|StoreComponent} Component
* @param {function} toProps
* @returns {SubscriberComponent}
*/
export function subscribe(
Component,
toProps = (storeComponentState, storeComponentProps, storeComponentActions) => {
return {};
}
) {
class SubscriberComponent extends React.Component {
/**
* Get the StoreComponent out of the context.
* Then, use the toProps function to convert its state, props, and actions
* into the props that the wrapped component has asked for.
*/
render() {
const { storeComponent } = this.context;
if (!storeComponent) {
throw new Error('No StoreComponent found as a parent of this SubscriberComponent.');
}
const { state, props, actions } = storeComponent;
return React.createElement(Component, Object.assign(
toProps(state, props, actions),
this.props
));
}
/**
* Necessary to use React's "context" feature.
*
* @returns {*}
*/
static get contextTypes() {
return Object.assign({}, contextTypes, super.contextTypes || {});
}
}
return SubscriberComponent;
}