Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 68fca63

Browse files
authored
Merge pull request #38 from r-park/dev
refactor(core): add immutability and selectors
2 parents f03f82b + 866f5ce commit 68fca63

File tree

22 files changed

+211
-178
lines changed

22 files changed

+211
-178
lines changed

‎package.json‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
"firebase": "~3.1.0",
5050
"firebase-tools": "3.0.4",
5151
"html-webpack-plugin": "~2.22.0",
52+
"immutable": "~3.8.1",
5253
"jasmine-core": "~2.4.1",
5354
"karma": "~1.0.0",
5455
"karma-chrome-launcher": "~1.0.1",
@@ -70,6 +71,7 @@
7071
"react-router-redux": "~4.0.5",
7172
"redux": "~3.5.2",
7273
"redux-thunk": "~2.1.0",
74+
"reselect": "~2.5.3",
7375
"sass-loader": "~4.0.0",
7476
"sinon": "~1.17.4",
7577
"style-loader": "~0.13.1",

‎src/components/app/app.js‎

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import React, { Component, PropTypes } from 'react';
22
import { connect } from 'react-redux';
3+
import { createSelector } from 'reselect';
34
import { POST_SIGN_IN_PATH, POST_SIGN_OUT_PATH } from 'src/config';
4-
import { authActions } from 'src/core/auth';
5+
import { authActions,getAuth } from 'src/core/auth';
56

67

78
export class App extends Component {
@@ -62,6 +63,17 @@ export class App extends Component {
6263
}
6364
}
6465

65-
export default connect(state => ({
66-
auth: state.auth
67-
}), authActions)(App);
66+
67+
//=====================================
68+
// CONNECT
69+
//-------------------------------------
70+
71+
const mapStateToProps = createSelector(
72+
getAuth,
73+
auth => ({auth})
74+
);
75+
76+
export default connect(
77+
mapStateToProps,
78+
authActions
79+
)(App);

‎src/components/tasks/task-item.js‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import classNames from 'classnames';
22
import React, { Component, PropTypes } from 'react';
3+
import { Task } from 'src/core/tasks';
34

45

56
export class TaskItem extends Component {
67
static propTypes = {
78
deleteTask: PropTypes.func.isRequired,
8-
task: PropTypes.object.isRequired,
9+
task: PropTypes.instanceOf(Task).isRequired,
910
updateTask: PropTypes.func.isRequired
1011
};
1112

@@ -22,6 +23,11 @@ export class TaskItem extends Component {
2223
this.onKeyUp = ::this.onKeyUp;
2324
}
2425

26+
shouldComponentUpdate(nextProps, nextState) {
27+
return nextProps.task !== this.props.task ||
28+
nextState.editing !== this.state.editing;
29+
}
30+
2531
delete() {
2632
this.props.deleteTask(this.props.task);
2733
}

‎src/components/tasks/task-item.spec.js‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Simulate } from 'react-addons-test-utils';
22
import { createTestComponent } from 'test/utils';
3+
import { Task } from 'src/core/tasks';
34
import { TaskItem } from './task-item';
45

56

@@ -9,7 +10,7 @@ describe('TaskItem', () => {
910

1011

1112
beforeEach(() => {
12-
task = {completed: true, title: 'test'};
13+
task = newTask({completed: true, title: 'test'});
1314

1415
taskItem = createTestComponent(TaskItem, {
1516
task,
@@ -24,8 +25,8 @@ describe('TaskItem', () => {
2425
expect(taskItem.state.editing).toEqual(false);
2526
});
2627

27-
it('should initialize #props.task with a task object', () => {
28-
expect(typeoftaskItem.props.task).toBe('object');
28+
it('should initialize #props.task with a Task instance', () => {
29+
expect(taskItem.props.taskinstanceofTask).toBe(true);
2930
});
3031
});
3132

‎src/components/tasks/task-list.js‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import React, { Component, PropTypes } from 'react';
2+
import { List } from 'immutable';
23
import { TaskItem } from './task-item';
34

45

56
export class TaskList extends Component {
67
static propTypes = {
78
deleteTask: PropTypes.func.isRequired,
89
filter: PropTypes.string,
9-
tasks: PropTypes.array.isRequired,
10+
tasks: PropTypes.instanceOf(List).isRequired,
1011
updateTask: PropTypes.func.isRequired
1112
};
1213

‎src/components/tasks/task-list.spec.js‎

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
import { List } from 'immutable';
12
import { scryRenderedComponentsWithType } from 'react-addons-test-utils';
23
import { createTestComponent } from 'test/utils';
4+
import { Task } from 'src/core/tasks';
35
import { TaskList } from './task-list';
46
import { TaskItem } from './task-item';
57

@@ -11,10 +13,10 @@ describe('TaskList', () => {
1113

1214

1315
beforeEach(() => {
14-
tasks = [
15-
{completed: false, title: 'active task'},
16-
{completed: true, title: 'completed task'}
17-
];
16+
tasks = newList([
17+
newTask({completed: false, title: 'active task'}),
18+
newTask({completed: true, title: 'completed task'})
19+
]);
1820

1921
props = {
2022
tasks,
@@ -27,8 +29,8 @@ describe('TaskList', () => {
2729

2830

2931
describe('Instantiation:', () => {
30-
it('should set #props.tasks with an array', () => {
31-
expect(Array.isArray(taskList.props.tasks)).toEqual(true);
32+
it('should set #props.tasks', () => {
33+
expect(taskList.props.tasks).toBe(tasks);
3234
});
3335
});
3436

‎src/components/tasks/tasks.js‎

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1+
import { List } from 'immutable';
12
import React, { Component, PropTypes } from 'react';
23
import { connect } from 'react-redux';
4+
import { createSelector } from 'reselect';
35

4-
import { notificationActions } from 'src/core/notification';
5-
import { tasksActions } from 'src/core/tasks';
6+
import { getNotification,notificationActions } from 'src/core/notification';
7+
import { getTaskList,tasksActions } from 'src/core/tasks';
68
import { Notification } from './notification';
79
import { TaskFilters } from './task-filters';
810
import { TaskForm } from './task-form';
@@ -17,7 +19,7 @@ export class Tasks extends Component {
1719
location: PropTypes.object.isRequired,
1820
notification: PropTypes.object.isRequired,
1921
registerListeners: PropTypes.func.isRequired,
20-
tasks: PropTypes.array.isRequired,
22+
tasks: PropTypes.instanceOf(List).isRequired,
2123
undeleteTask: PropTypes.func.isRequired,
2224
updateTask: PropTypes.func.isRequired
2325
};
@@ -36,8 +38,10 @@ export class Tasks extends Component {
3638
return (
3739
<Notification
3840
action={undeleteTask}
41+
actionLabel={notification.actionLabel}
3942
dismiss={dismissNotification}
40-
{...notification}
43+
display={notification.display}
44+
message={notification.message}
4145
/>
4246
);
4347
}
@@ -76,7 +80,27 @@ export class Tasks extends Component {
7680
}
7781
}
7882

79-
export default connect(state => ({
80-
notification: state.notification,
81-
tasks: state.tasks.list
82-
}), Object.assign({}, tasksActions, notificationActions))(Tasks);
83+
84+
//=====================================
85+
// CONNECT
86+
//-------------------------------------
87+
88+
const mapStateToProps = createSelector(
89+
getNotification,
90+
getTaskList,
91+
(notification, tasks) => ({
92+
notification,
93+
tasks
94+
})
95+
);
96+
97+
const mapDispatchToProps = Object.assign(
98+
{},
99+
tasksActions,
100+
notificationActions
101+
);
102+
103+
export default connect(
104+
mapStateToProps,
105+
mapDispatchToProps
106+
)(Tasks);

‎src/core/auth/index.js‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ import * as authActions from './actions';
44

55
export { authActions };
66
export * from './action-types';
7-
export * from './reducer';
7+
export {authReducer} from './reducer';
88
export * from './route-resolver';
9+
export { getAuth } from './selectors';
910

1011

1112
export function initAuth(dispatch) {

‎src/core/auth/reducer.js‎

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,24 @@
1-
/* eslint-disable no-case-declarations */
1+
import{Record}from'immutable';
22
import { INIT_AUTH, SIGN_IN_SUCCESS, SIGN_OUT_SUCCESS } from './action-types';
33

44

5-
export const initialState = {
5+
export const AuthState = newRecord({
66
authenticated: false,
77
id: null
8-
};
8+
});
99

1010

11-
export function authReducer(state = initialState, {payload, type}) {
11+
export function authReducer(state = newAuthState(), {payload, type}) {
1212
switch (type) {
1313
case INIT_AUTH:
14-
return {
14+
case SIGN_IN_SUCCESS:
15+
return state.merge({
1516
authenticated: !!payload,
1617
id: payload ? payload.uid : null
17-
};
18-
19-
case SIGN_IN_SUCCESS:
20-
return {
21-
authenticated: true,
22-
id: payload.uid
23-
};
18+
});
2419

2520
case SIGN_OUT_SUCCESS:
26-
return {
27-
authenticated: false,
28-
id: null
29-
};
21+
return new AuthState();
3022

3123
default:
3224
return state;

‎src/core/auth/reducer.spec.js‎

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,57 @@
1-
/* eslint-disable no-undefined */
21
import {
32
INIT_AUTH,
43
SIGN_IN_SUCCESS,
54
SIGN_OUT_SUCCESS
65
} from './action-types';
76

8-
import {
9-
authReducer,
10-
initialState
11-
} from './reducer';
7+
import { authReducer } from './reducer';
128

139

1410
describe('Auth reducer', () => {
15-
it('should return the initial state when action.type is not found', () => {
16-
expect(authReducer(undefined, {})).toEqual(initialState);
17-
});
18-
19-
2011
describe('INIT_AUTH', () => {
21-
it('should return state as `unauthenticated` when payload is `null`', () => {
22-
let state = authReducer(initialState, {
12+
it('should set AuthState.authenticated to false when payload is null', () => {
13+
let state = authReducer(undefined, {
2314
type: INIT_AUTH,
2415
payload: null
2516
});
2617

27-
expect(state).toEqual(initialState);
18+
expect(state.authenticated).toBe(false);
19+
expect(state.id).toBe(null);
20+
});
21+
22+
it('should set AuthState.authenticated to true when payload provided', () => {
23+
let state = authReducer(undefined, {
24+
type: INIT_AUTH,
25+
payload: {uid: '123'}
26+
});
27+
28+
expect(state.authenticated).toBe(true);
29+
expect(state.id).toBe('123');
2830
});
2931
});
3032

3133

3234
describe('SIGN_IN_SUCCESS', () => {
33-
it('should return state as `authenticated`', () => {
34-
let state = authReducer(initialState, {
35+
it('should set AuthState.authenticated to true', () => {
36+
let state = authReducer(undefined, {
3537
type: SIGN_IN_SUCCESS,
3638
payload: {uid: '123'}
3739
});
3840

39-
expect(state).toEqual({
40-
authenticated: true,
41-
id: '123'
42-
});
41+
expect(state.authenticated).toBe(true);
42+
expect(state.id).toBe('123');
4343
});
4444
});
4545

4646

4747
describe('SIGN_OUT_SUCCESS', () => {
48-
it('should return state as `unauthenticated`', () => {
49-
let state = authReducer(initialState, {
50-
type: SIGN_OUT_SUCCESS,
51-
payload: null
48+
it('should set AuthState.authenticated to false', () => {
49+
let state = authReducer(undefined, {
50+
type: SIGN_OUT_SUCCESS
5251
});
5352

54-
expect(state).toEqual(initialState);
53+
expect(state.authenticated).toBe(false);
54+
expect(state.id).toBe(null);
5555
});
5656
});
5757
});

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /