Skip to content

Commit e53f6ad

Browse files
committed
Initial Implmentation of the lib
Current Code based on idea from this thread: reduxjs/redux#159 and initial experiments from https://github.com/taylorhakes
1 parent 391b341 commit e53f6ad

15 files changed

+499
-11
lines changed

examples/simple/.babelrc

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"stage": 0,
3+
"loose": "all"
4+
}

examples/simple/components/App.js

+25-5
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,32 @@
1-
import React, { Component } from 'react';
2-
import { add } from 'redux-component-state';
1+
import React, { Component, PropTypes } from 'react'
2+
3+
import { createStore, combineReducers, compose } from 'redux';
4+
import { Provider } from 'react-redux';
5+
6+
import { reduxComponentStateStore } from 'redux-component-state';
7+
8+
import appInfo from '../reducers/appInfo';
9+
10+
import CounterPanel from './CounterPanel';
11+
12+
const combinedReducers = combineReducers({ appInfo });
13+
14+
let createFinalStore = compose(
15+
reduxComponentStateStore,
16+
createStore
17+
);
18+
19+
const store = createFinalStore(combinedReducers);
20+
321

422
export default class App extends Component {
23+
524
render() {
625
return (
7-
<p>
8-
2 + 2 = {add(2, 2)}
9-
</p>
26+
<Provider store={store}>
27+
{() => <CounterPanel />}
28+
</Provider>
1029
);
1130
}
1231
}
32+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import React, { Component, PropTypes } from 'react'
2+
3+
import reduxComponentState from 'redux-component-state';
4+
5+
import * as actions from './redux/actions';
6+
import counter from './redux/counterReducer';
7+
8+
class CounterPanel extends Component {
9+
10+
static propTypes = {
11+
increment: PropTypes.func.isRequired,
12+
decrement: PropTypes.func.isRequired,
13+
counter: PropTypes.shape({
14+
value: PropTypes.number.isRequired,
15+
interactionCount: PropTypes.number.isRequired
16+
}).isRequired
17+
}
18+
19+
static defaultProps = {}
20+
21+
render() {
22+
const {
23+
increment, decrement,
24+
counter: { value, interactionCount }
25+
} = this.props;
26+
27+
return (
28+
<div>
29+
Current Count: {value} <br/>
30+
Number of interactions: {interactionCount}
31+
<br/>
32+
<button onClick={ e => increment() }>increment</button>
33+
<button onClick={ e => decrement() }>decrement</button>
34+
</div>
35+
);
36+
}
37+
}
38+
39+
const componentStateConfig = {
40+
getKey(props) {
41+
let id = props.id || 'defaultCounterPanel';
42+
return `counter-${id}`;
43+
},
44+
reducers: { counter },
45+
actions
46+
};
47+
export default reduxComponentState(componentStateConfig)(CounterPanel);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import CounterPanel from './CounterPanel';
2+
3+
export default CounterPanel;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export const INCREMENT = 'counter-panel/increment';
2+
export const DECREMENT = 'counter-panel/decrement';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import {
2+
INCREMENT,
3+
DECREMENT
4+
} from './actionTypes';
5+
6+
export function increment(multiplier = 1) {
7+
return {
8+
type: INCREMENT,
9+
value: multiplier
10+
};
11+
}
12+
13+
export function decrement(multiplier = 1){
14+
return {
15+
type: INCREMENT,
16+
value: (-multiplier)
17+
};
18+
}
19+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import {
2+
INCREMENT,
3+
DECREMENT
4+
} from './actionTypes';
5+
6+
const initialState = {
7+
value: 0,
8+
interactionCount: 0
9+
};
10+
11+
function update({value, interactionCount}, {value: valueModifier}){
12+
interactionCount++;
13+
value = value + valueModifier;
14+
return { value, interactionCount };
15+
}
16+
17+
export default function(state = initialState, action) {
18+
switch(action.type) {
19+
20+
case INCREMENT:
21+
return update(state, action);
22+
break;
23+
24+
case DECREMENT:
25+
return update(state, action);
26+
break;
27+
28+
default:
29+
return state;
30+
}
31+
}

examples/simple/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
},
2020
"homepage": "https://github.com/redux-component-state-author/redux-component-state",
2121
"dependencies": {
22-
"react": "^0.13.3"
22+
"react-redux": "^0.9.0"
2323
},
2424
"devDependencies": {
2525
"babel-core": "^5.6.18",

examples/simple/reducers/appInfo.js

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
const initialState = {
2+
name: 'Simple Component State'
3+
};
4+
5+
export default function(state = initialState, action) {
6+
return state;
7+
}

examples/simple/webpack.config.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ var path = require('path');
22
var webpack = require('webpack');
33

44
module.exports = {
5-
devtool: 'eval',
5+
devtool: 'source-maps',
66
entry: [
77
'webpack-dev-server/client?http://localhost:3000',
88
'webpack/hot/only-dev-server',

package.json

+7-1
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,15 @@
4343
"mocha": "2.2.5",
4444
"rimraf": "2.3.4",
4545
"webpack": "1.9.6",
46-
"webpack-dev-server": "1.8.2"
46+
"webpack-dev-server": "1.8.2",
47+
"react": "^0.13.3",
48+
"redux": "^1.0.0"
4749
},
4850
"dependencies": {
4951
"invariant": "2.0.0"
52+
},
53+
"peerDependencies": {
54+
"react": "^0.13.3",
55+
"redux": "^1.0.0 || 1.0.0-rc"
5056
}
5157
}

src/actionTypes.js

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export const LOCAL = 'redux-component-state/local';
2+
export const LOCAL_MOUNT = 'redux-component-state/localMount';
3+
export const LOCAL_UNMOUNT = 'redux-component-state/localUnmount';
4+
export const LOCAL_INIT = 'redux-component-state/localInit';
5+
export const LOCAL_ACTION = 'redux-component-state/localAction';
6+
export const LOCAL_KEY = 'redux-component-state/';

src/componentState.js

+147
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
import React, { Component, PropTypes } from 'react';
2+
import { bindActionCreators } from 'redux';
3+
import {
4+
LOCAL,
5+
LOCAL_ACTION
6+
} from './actionTypes';
7+
8+
// TODO: move in an utilities module
9+
function getDisplayName(Comp) {
10+
return Comp.displayName || Comp.name || 'Component';
11+
}
12+
13+
function createComponentStateDecorator(componentStoreConfig) {
14+
// TODO: add validation of shape fo the storeConfig
15+
16+
return (DecoratedComponent) =>
17+
class ReduxComponentState extends Component {
18+
19+
// ****************************************************************
20+
// Component initialization
21+
// ****************************************************************
22+
23+
static displayName = `ReduxComponentState(${getDisplayName(DecoratedComponent)})`;
24+
static DecoratedComponent = DecoratedComponent;
25+
26+
static contextTypes = {
27+
store: PropTypes.shape({
28+
componentState: PropTypes.shape({
29+
subscribe: PropTypes.func,
30+
unsubscribe: PropTypes.func,
31+
getState: PropTypes.func
32+
}).isRequired
33+
})
34+
};
35+
36+
// static propTypes = {
37+
// componentStoreConfig: PropTypes.shape({
38+
// getKey: PropTypes.func,
39+
// reducers: PropTypes.object,
40+
// actions: PropTypes.object,
41+
// getInitialState: PropTypes.func,
42+
// shared: PropTypes.bool
43+
// }).isRequired
44+
// };
45+
46+
constructor(props, context) {
47+
super(props, context);
48+
this.state = this.getComponentStoreState();
49+
}
50+
51+
// ****************************************************************
52+
// Component LifeCycle
53+
// ****************************************************************
54+
55+
componentDidMount() {
56+
const {
57+
getKey,
58+
reducers,
59+
actions,
60+
getInitialState,
61+
shared
62+
} = componentStoreConfig;
63+
64+
let initialState = (getInitialState || (() => undefined))(this.props);
65+
this.unsubscribe = this.context.store.componentState.subscribe({
66+
key: getKey(this.props),
67+
reducers,
68+
initialState,
69+
shared,
70+
onChange: this.handleChange
71+
});
72+
this.handleChange();
73+
}
74+
75+
componentWillUnmount() {
76+
this.unsubscribe();
77+
}
78+
79+
// ****************************************************************
80+
// Internal API
81+
// ****************************************************************
82+
83+
getComponentStateKey(){
84+
return componentStoreConfig.getKey(this.props);
85+
}
86+
87+
getComponentStoreState(){
88+
return this.context.store.componentState.getState(this.getComponentStateKey());
89+
}
90+
91+
handleChange = () => {
92+
// TODO: add conditions to prevent rendering if the
93+
// state is not changed
94+
this.setState(this.getComponentStoreState());
95+
}
96+
97+
dispatchLocal = (action) => {
98+
this.context.store.dispatch({
99+
type: LOCAL,
100+
subType: LOCAL_ACTION,
101+
key: this.getComponentStateKey(),
102+
data: action
103+
});
104+
}
105+
106+
// ****************************************************************
107+
// Render component
108+
// ****************************************************************
109+
110+
render() {
111+
const { ...stuff } = this.props;
112+
let actions = componentStoreConfig.actions || {};
113+
const boundActionCreators = bindActionCreators(actions, this.dispatchLocal);
114+
115+
// FIXME: temporary fix because first render of the component fires
116+
// before the component store is initialized
117+
if(!this.state) return <span />;
118+
119+
// const renderDecoratedComp = () =>
120+
// <DecoratedComponent
121+
// dispatch={this.dispatchLocal}
122+
// {...this.stuff}
123+
// {...this.state}
124+
// {...boundActionCreators}
125+
// />;
126+
127+
return (
128+
<div>
129+
<DecoratedComponent ref="ReduxComponentStateWrapper"
130+
dispatch={this.dispatchLocal}
131+
{...this.stuff}
132+
{...this.state}
133+
{...boundActionCreators}
134+
/>
135+
</div>
136+
);
137+
}
138+
}
139+
}
140+
141+
142+
export default function reduxComponentState(componentStoreConfig) {
143+
const decorator = createComponentStateDecorator(componentStoreConfig);
144+
// TODO: is required this intermediate function?
145+
// there's need to manipulate the decorator here?
146+
return decorator;
147+
}

0 commit comments

Comments
 (0)