Skip to content

Commit db3f611

Browse files
authored
Merge pull request #41 from r-park/dev
refactor(tasks): use selector to filter tasks
2 parents c0f3b0f + 2f69fa3 commit db3f611

File tree

10 files changed

+190
-98
lines changed

10 files changed

+190
-98
lines changed

src/components/tasks/task-list.js

Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,35 +6,27 @@ import { TaskItem } from './task-item';
66
export class TaskList extends Component {
77
static propTypes = {
88
deleteTask: PropTypes.func.isRequired,
9-
filter: PropTypes.string,
109
tasks: PropTypes.instanceOf(List).isRequired,
1110
updateTask: PropTypes.func.isRequired
1211
};
1312

1413
renderTaskItems() {
1514
const {
1615
deleteTask,
17-
filter,
1816
tasks,
1917
updateTask
2018
} = this.props;
2119

22-
return tasks
23-
.filter(task => {
24-
if (filter === 'active') return !task.completed;
25-
if (filter === 'completed') return task.completed;
26-
return task;
27-
})
28-
.map((task, index) => {
29-
return (
30-
<TaskItem
31-
deleteTask={deleteTask}
32-
key={index}
33-
task={task}
34-
updateTask={updateTask}
35-
/>
36-
);
37-
});
20+
return tasks.map((task, index) => {
21+
return (
22+
<TaskItem
23+
deleteTask={deleteTask}
24+
key={index}
25+
task={task}
26+
updateTask={updateTask}
27+
/>
28+
);
29+
});
3830
}
3931

4032
render() {

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

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -36,25 +36,9 @@ describe('TaskList', () => {
3636

3737

3838
describe('DOM:', () => {
39-
it('should render all tasks', () => {
39+
it('should render tasks', () => {
4040
let taskItems = scryRenderedComponentsWithType(taskList, TaskItem);
4141
expect(taskItems.length).toEqual(2);
4242
});
43-
44-
it('should render active tasks', () => {
45-
taskList = createTestComponent(TaskList, {filter: 'active', ...props});
46-
let taskItems = scryRenderedComponentsWithType(taskList, TaskItem);
47-
48-
expect(taskItems.length).toEqual(1);
49-
expect(taskItems[0].props.task.completed).toEqual(false);
50-
});
51-
52-
it('should render completed tasks', () => {
53-
taskList = createTestComponent(TaskList, {filter: 'completed', ...props});
54-
let taskItems = scryRenderedComponentsWithType(taskList, TaskItem);
55-
56-
expect(taskItems.length).toEqual(1);
57-
expect(taskItems[0].props.task.completed).toEqual(true);
58-
});
5943
});
6044
});

src/components/tasks/tasks.js

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { connect } from 'react-redux';
44
import { createSelector } from 'reselect';
55

66
import { getNotification, notificationActions } from 'src/core/notification';
7-
import { getTaskList, tasksActions } from 'src/core/tasks';
7+
import { getTaskFilter, getVisibleTasks, tasksActions } from 'src/core/tasks';
88
import { Notification } from './notification';
99
import { TaskFilters } from './task-filters';
1010
import { TaskForm } from './task-form';
@@ -16,6 +16,8 @@ export class Tasks extends Component {
1616
createTask: PropTypes.func.isRequired,
1717
deleteTask: PropTypes.func.isRequired,
1818
dismissNotification: PropTypes.func.isRequired,
19+
filterTasks: PropTypes.func.isRequired,
20+
filterType: PropTypes.string.isRequired,
1921
location: PropTypes.object.isRequired,
2022
notification: PropTypes.object.isRequired,
2123
registerListeners: PropTypes.func.isRequired,
@@ -26,6 +28,13 @@ export class Tasks extends Component {
2628

2729
componentWillMount() {
2830
this.props.registerListeners();
31+
this.props.filterTasks(this.props.location.query.filter);
32+
}
33+
34+
componentWillReceiveProps(nextProps) {
35+
if (nextProps.location.query.filter !== this.props.location.query.filter) {
36+
this.props.filterTasks(nextProps.location.query.filter);
37+
}
2938
}
3039

3140
renderNotification() {
@@ -50,25 +59,22 @@ export class Tasks extends Component {
5059
const {
5160
createTask,
5261
deleteTask,
53-
location,
62+
filterType,
5463
notification,
5564
tasks,
5665
updateTask
5766
} = this.props;
5867

59-
const { filter } = location.query;
60-
6168
return (
6269
<div className="g-row">
6370
<div className="g-col">
6471
<TaskForm createTask={createTask} />
6572
</div>
6673

6774
<div className="g-col">
68-
<TaskFilters filter={filter} />
75+
<TaskFilters filter={filterType} />
6976
<TaskList
7077
deleteTask={deleteTask}
71-
filter={filter}
7278
tasks={tasks}
7379
updateTask={updateTask}
7480
/>
@@ -87,9 +93,11 @@ export class Tasks extends Component {
8793

8894
const mapStateToProps = createSelector(
8995
getNotification,
90-
getTaskList,
91-
(notification, tasks) => ({
96+
getTaskFilter,
97+
getVisibleTasks,
98+
(notification, filterType, tasks) => ({
9299
notification,
100+
filterType,
93101
tasks
94102
})
95103
);

src/core/tasks/action-types.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,5 @@ export const DELETE_TASK_SUCCESS = 'DELETE_TASK_SUCCESS';
66

77
export const UPDATE_TASK_ERROR = 'UPDATE_TASK_ERROR';
88
export const UPDATE_TASK_SUCCESS = 'UPDATE_TASK_SUCCESS';
9+
10+
export const FILTER_TASKS = 'FILTER_TASKS';

src/core/tasks/actions.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ import {
66
DELETE_TASK_ERROR,
77
DELETE_TASK_SUCCESS,
88
UPDATE_TASK_ERROR,
9-
UPDATE_TASK_SUCCESS
9+
UPDATE_TASK_SUCCESS,
10+
FILTER_TASKS
1011
} from './action-types';
1112

1213

@@ -79,6 +80,14 @@ export function updateTask(task, changes) {
7980
}
8081

8182

83+
export function filterTasks(filterType) {
84+
return {
85+
type: FILTER_TASKS,
86+
payload: {filterType}
87+
};
88+
}
89+
90+
8291
export function registerListeners() {
8392
return (dispatch, getState) => {
8493
const { auth } = getState();

src/core/tasks/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@ import * as tasksActions from './actions';
44
export { tasksActions };
55
export * from './action-types';
66
export { tasksReducer } from './reducer';
7-
export { getTaskList } from './selectors';
7+
export { getTaskFilter, getVisibleTasks } from './selectors';
88
export { Task } from './task';

src/core/tasks/reducer.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,14 @@ import {
77
import {
88
CREATE_TASK_SUCCESS,
99
DELETE_TASK_SUCCESS,
10+
FILTER_TASKS,
1011
UPDATE_TASK_SUCCESS
1112
} from './action-types';
1213

1314

1415
export const TasksState = new Record({
1516
deleted: null,
17+
filter: '',
1618
list: new List(),
1719
previous: null
1820
});
@@ -36,6 +38,9 @@ export function tasksReducer(state = new TasksState(), {payload, type}) {
3638
list: state.list.filter(task => task.key !== payload.key)
3739
});
3840

41+
case FILTER_TASKS:
42+
return state.set('filter', payload.filterType || '');
43+
3944
case UPDATE_TASK_SUCCESS:
4045
return state.merge({
4146
deleted: null,

src/core/tasks/reducer.spec.js

Lines changed: 72 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -4,86 +4,105 @@ import { SIGN_OUT_SUCCESS } from 'src/core/auth';
44
import {
55
CREATE_TASK_SUCCESS,
66
DELETE_TASK_SUCCESS,
7+
FILTER_TASKS,
78
UPDATE_TASK_SUCCESS
89
} from './action-types';
910

10-
import { tasksReducer, TasksState } from './reducer';
1111
import { Task } from './task';
12+
import { tasksReducer, TasksState } from './reducer';
1213

1314

14-
describe('Tasks reducer', () => {
15-
let task1;
16-
let task2;
15+
describe('tasks', () => {
16+
describe('tasksReducer', () => {
17+
let task1;
18+
let task2;
1719

18-
beforeEach(() => {
19-
task1 = new Task({completed: false, key: '0', title: 'task 1'});
20-
task2 = new Task({completed: false, key: '1', title: 'task 2'});
21-
});
20+
beforeEach(() => {
21+
task1 = new Task({completed: false, key: '0', title: 'task 1'});
22+
task2 = new Task({completed: false, key: '1', title: 'task 2'});
23+
});
2224

2325

24-
describe('CREATE_TASK_SUCCESS', () => {
25-
it('should prepend new task to list', () => {
26-
let state = new TasksState({list: new List([task1])});
26+
describe('CREATE_TASK_SUCCESS', () => {
27+
it('should prepend new task to list', () => {
28+
let state = new TasksState({list: new List([task1])});
2729

28-
let nextState = tasksReducer(state, {
29-
type: CREATE_TASK_SUCCESS,
30-
payload: task2
31-
});
30+
let nextState = tasksReducer(state, {
31+
type: CREATE_TASK_SUCCESS,
32+
payload: task2
33+
});
3234

33-
expect(nextState.list.get(0)).toBe(task2);
34-
expect(nextState.list.get(1)).toBe(task1);
35+
expect(nextState.list.get(0)).toBe(task2);
36+
expect(nextState.list.get(1)).toBe(task1);
37+
});
3538
});
36-
});
3739

3840

39-
describe('DELETE_TASK_SUCCESS', () => {
40-
it('should remove task from list', () => {
41-
let state = new TasksState({list: new List([task1, task2])});
41+
describe('DELETE_TASK_SUCCESS', () => {
42+
it('should remove task from list', () => {
43+
let state = new TasksState({list: new List([task1, task2])});
4244

43-
let nextState = tasksReducer(state, {
44-
type: DELETE_TASK_SUCCESS,
45-
payload: task2
46-
});
45+
let nextState = tasksReducer(state, {
46+
type: DELETE_TASK_SUCCESS,
47+
payload: task2
48+
});
4749

48-
expect(nextState.deleted).toBe(task2);
49-
expect(nextState.list.size).toBe(1);
50-
expect(nextState.list.get(0)).toBe(task1);
51-
expect(nextState.previous).toBe(state.list);
50+
expect(nextState.deleted).toBe(task2);
51+
expect(nextState.list.size).toBe(1);
52+
expect(nextState.list.get(0)).toBe(task1);
53+
expect(nextState.previous).toBe(state.list);
54+
});
5255
});
53-
});
5456

5557

56-
describe('UPDATE_TASK_SUCCESS', () => {
57-
it('should update task', () => {
58-
let state = new TasksState({list: new List([task1, task2])});
59-
let changedTask = task2.set('title', 'changed');
58+
describe('FILTER_TASKS', () => {
59+
it('should set filter with provided value', () => {
60+
let state = new TasksState();
6061

61-
let nextState = tasksReducer(state, {
62-
type: UPDATE_TASK_SUCCESS,
63-
payload: changedTask
64-
});
62+
let nextState = tasksReducer(state, {
63+
type: FILTER_TASKS,
64+
payload: {
65+
filterType: 'completed'
66+
}
67+
});
6568

66-
expect(nextState.list.get(0)).toBe(task1);
67-
expect(nextState.list.get(1)).toBe(changedTask);
69+
expect(nextState.filter).toBe('completed');
70+
});
6871
});
69-
});
7072

7173

72-
describe('SIGN_OUT_SUCCESS', () => {
73-
it('should reset state', () => {
74-
let state = new TasksState({
75-
delete: task1,
76-
list: new List([task1, task2]),
77-
previous: new List()
78-
});
74+
describe('UPDATE_TASK_SUCCESS', () => {
75+
it('should update task', () => {
76+
let state = new TasksState({list: new List([task1, task2])});
77+
let changedTask = task2.set('title', 'changed');
78+
79+
let nextState = tasksReducer(state, {
80+
type: UPDATE_TASK_SUCCESS,
81+
payload: changedTask
82+
});
7983

80-
let nextState = tasksReducer(state, {
81-
type: SIGN_OUT_SUCCESS
84+
expect(nextState.list.get(0)).toBe(task1);
85+
expect(nextState.list.get(1)).toBe(changedTask);
8286
});
87+
});
88+
89+
90+
describe('SIGN_OUT_SUCCESS', () => {
91+
it('should reset state', () => {
92+
let state = new TasksState({
93+
delete: task1,
94+
list: new List([task1, task2]),
95+
previous: new List()
96+
});
8397

84-
expect(nextState.deleted).toBe(null);
85-
expect(nextState.list.size).toBe(0);
86-
expect(nextState.previous).toBe(null);
98+
let nextState = tasksReducer(state, {
99+
type: SIGN_OUT_SUCCESS
100+
});
101+
102+
expect(nextState.deleted).toBe(null);
103+
expect(nextState.list.size).toBe(0);
104+
expect(nextState.previous).toBe(null);
105+
});
87106
});
88107
});
89108
});

0 commit comments

Comments
 (0)