Skip to content

Commit 5e9cda5

Browse files
committed
final
1 parent 6ff5577 commit 5e9cda5

File tree

1 file changed

+247
-24
lines changed

1 file changed

+247
-24
lines changed

src/index.js

Lines changed: 247 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,57 @@
11
import React from 'react';
22
import ReactDOM from 'react-dom';
3-
import { combineReducers, createStore } from 'redux';
3+
import { applyMiddleware, combineReducers, createStore } from 'redux';
44
import { Provider, connect } from 'react-redux';
5+
import { createLogger } from 'redux-logger';
6+
import createSagaMiddleware, { delay } from 'redux-saga';
7+
import { put, takeEvery } from 'redux-saga/effects';
8+
import { schema, normalize } from 'normalizr';
9+
import uuid from 'uuid/v4';
510
import './index.css';
611

12+
// filters
13+
14+
const VISIBILITY_FILTERS = {
15+
SHOW_COMPLETED: item => item.completed,
16+
SHOW_INCOMPLETED: item => !item.completed,
17+
SHOW_ALL: item => true,
18+
};
19+
20+
// schemas
21+
22+
const todoSchema = new schema.Entity('todo');
23+
724
// action types
825

926
const TODO_ADD = 'TODO_ADD';
1027
const TODO_TOGGLE = 'TODO_TOGGLE';
1128
const FILTER_SET = 'FILTER_SET';
29+
const NOTIFICATION_HIDE = 'NOTIFICATION_HIDE';
30+
const TODO_ADD_WITH_NOTIFICATION = 'TODO_ADD_WITH_NOTIFICATION';
1231

1332
// reducers
1433

1534
const todos = [
16-
{ id: '0', name: 'learn redux' },
17-
{ id: '1', name: 'learn mobx' },
35+
{ id: '1', name: 'Hands On: Redux Standalone with advanced Actions' },
36+
{ id: '2', name: 'Hands On: Redux Standalone with advanced Reducers' },
37+
{ id: '3', name: 'Hands On: Bootstrap App with Redux' },
38+
{ id: '4', name: 'Hands On: Naive Todo with React and Redux' },
39+
{ id: '5', name: 'Hands On: Sophisticated Todo with React and Redux' },
40+
{ id: '6', name: 'Hands On: Connecting State Everywhere' },
41+
{ id: '7', name: 'Hands On: Todo with advanced Redux' },
42+
{ id: '8', name: 'Hands On: Todo but more Features' },
43+
{ id: '9', name: 'Hands On: Todo with Notifications' },
44+
{ id: '10', name: 'Hands On: Hacker News with Redux' },
1845
];
1946

20-
function todoReducer(state = todos, action) {
47+
const normalizedTodos = normalize(todos, [todoSchema]);
48+
49+
const initialTodoState = {
50+
entities: normalizedTodos.entities.todo,
51+
ids: normalizedTodos.result,
52+
};
53+
54+
function todoReducer(state = initialTodoState, action) {
2155
switch(action.type) {
2256
case TODO_ADD : {
2357
return applyAddTodo(state, action);
@@ -30,16 +64,18 @@ function todoReducer(state = todos, action) {
3064
}
3165

3266
function applyAddTodo(state, action) {
33-
const todo = Object.assign({}, action.todo, { completed: false });
34-
return state.concat(todo);
67+
const todo = { ...action.todo, completed: false };
68+
const entities = { ...state.entities, [todo.id]: todo };
69+
const ids = [ ...state.ids, action.todo.id ];
70+
return { ...state, entities, ids };
3571
}
3672

3773
function applyToggleTodo(state, action) {
38-
return state.map(todo =>
39-
todo.id === action.todo.id
40-
? Object.assign({}, todo, { completed: !todo.completed })
41-
: todo
42-
);
74+
const id = action.todo.id;
75+
const todo = state.entities[id];
76+
const toggledTodo = { ...todo, completed: !todo.completed };
77+
const entities = { ...state.entities, [id]: toggledTodo };
78+
return { ...state, entities };
4379
}
4480

4581
function filterReducer(state = 'SHOW_ALL', action) {
@@ -55,8 +91,47 @@ function applySetFilter(state, action) {
5591
return action.filter;
5692
}
5793

94+
function notificationReducer(state = {}, action) {
95+
switch(action.type) {
96+
case TODO_ADD : {
97+
return applySetNotifyAboutAddTodo(state, action);
98+
}
99+
case NOTIFICATION_HIDE : {
100+
return applyRemoveNotification(state, action);
101+
}
102+
default : return state;
103+
}
104+
}
105+
106+
function applySetNotifyAboutAddTodo(state, action) {
107+
const { name, id } = action.todo;
108+
return { ...state, [id]: 'Todo Created: ' + name };
109+
}
110+
111+
function applyRemoveNotification(state, action) {
112+
const {
113+
[action.id]: notificationToRemove,
114+
...restNotifications,
115+
} = state;
116+
return restNotifications;
117+
}
118+
58119
// action creators
59120

121+
function doAddTodoWithNotification(id, name) {
122+
return {
123+
type: TODO_ADD_WITH_NOTIFICATION,
124+
todo: { id, name },
125+
};
126+
}
127+
128+
function doHideNotification(id) {
129+
return {
130+
type: NOTIFICATION_HIDE,
131+
id
132+
};
133+
}
134+
60135
function doAddTodo(id, name) {
61136
return {
62137
type: TODO_ADD,
@@ -78,27 +153,146 @@ function doSetFilter(filter) {
78153
};
79154
}
80155

156+
// selectors
157+
158+
function getTodosAsIds(state) {
159+
return state.todoState.ids
160+
.map(id => state.todoState.entities[id])
161+
.filter(VISIBILITY_FILTERS[state.filterState])
162+
.map(todo => todo.id);
163+
}
164+
165+
function getTodo(state, todoId) {
166+
return state.todoState.entities[todoId];
167+
}
168+
169+
function getNotifications(state) {
170+
return getArrayOfObject(state.notificationState);
171+
}
172+
173+
function getArrayOfObject(object) {
174+
return Object.keys(object).map(key => object[key]);
175+
}
176+
177+
// sagas
178+
179+
function* watchAddTodoWithNotification() {
180+
yield takeEvery(TODO_ADD_WITH_NOTIFICATION, handleAddTodoWithNotification);
181+
}
182+
183+
function* handleAddTodoWithNotification(action) {
184+
const { todo } = action;
185+
const { id, name } = todo;
186+
yield put(doAddTodo(id, name));
187+
yield delay(5000);
188+
yield put(doHideNotification(id));
189+
}
190+
81191
// store
82192

83193
const rootReducer = combineReducers({
84194
todoState: todoReducer,
85195
filterState: filterReducer,
196+
notificationState: notificationReducer,
86197
});
87198

88-
const store = createStore(rootReducer);
199+
const logger = createLogger();
200+
const saga = createSagaMiddleware();
201+
202+
const store = createStore(
203+
rootReducer,
204+
undefined,
205+
applyMiddleware(saga, logger)
206+
);
207+
208+
saga.run(watchAddTodoWithNotification);
89209

90-
// view layer
210+
// components
91211

92212
function TodoApp() {
93-
return <ConnectedTodoList />;
213+
return (
214+
<div>
215+
<ConnectedFilter />
216+
<ConnectedTodoCreate />
217+
<ConnectedTodoList />
218+
<ConnectedNotifications />
219+
</div>
220+
);
221+
}
222+
223+
function Notifications({ notifications }) {
224+
return (
225+
<div>
226+
{notifications.map(note => <div key={note}>{note}</div>)}
227+
</div>
228+
);
94229
}
95230

96-
function TodoList({ todos }) {
231+
function Filter({ onSetFilter }) {
97232
return (
98233
<div>
99-
{todos.map(todo => <ConnectedTodoItem
100-
key={todo.id}
101-
todo={todo}
234+
Show
235+
<button
236+
type="text"
237+
onClick={() => onSetFilter('SHOW_ALL')}>
238+
All</button>
239+
<button
240+
type="text"
241+
onClick={() => onSetFilter('SHOW_COMPLETED')}>
242+
Completed</button>
243+
<button
244+
type="text"
245+
onClick={() => onSetFilter('SHOW_INCOMPLETED')}>
246+
Incompleted</button>
247+
</div>
248+
);
249+
}
250+
251+
class TodoCreate extends React.Component {
252+
constructor(props) {
253+
super(props);
254+
255+
this.state = {
256+
value: '',
257+
};
258+
259+
this.onCreateTodo = this.onCreateTodo.bind(this);
260+
this.onChangeName = this.onChangeName.bind(this);
261+
}
262+
263+
onChangeName(event) {
264+
this.setState({ value: event.target.value });
265+
}
266+
267+
onCreateTodo(event) {
268+
this.props.onAddTodo(this.state.value);
269+
this.setState({ value: '' });
270+
event.preventDefault();
271+
}
272+
273+
render() {
274+
return (
275+
<div>
276+
<form onSubmit={this.onCreateTodo}>
277+
<input
278+
type="text"
279+
placeholder="Add Todo..."
280+
value={this.state.value}
281+
onChange={this.onChangeName}
282+
/>
283+
<button type="submit">Add</button>
284+
</form>
285+
</div>
286+
);
287+
}
288+
}
289+
290+
function TodoList({ todosAsIds }) {
291+
return (
292+
<div>
293+
{todosAsIds.map(todoId => <ConnectedTodoItem
294+
key={todoId}
295+
todoId={todoId}
102296
/>)}
103297
</div>
104298
);
@@ -119,24 +313,53 @@ function TodoItem({ todo, onToggleTodo }) {
119313
);
120314
}
121315

122-
function mapStateToProps(state) {
316+
// Connecting React and Redux
317+
318+
function mapStateToPropsList(state) {
123319
return {
124-
todos: state.todoState,
320+
todosAsIds: getTodosAsIds(state),
125321
};
126322
}
127323

128-
function mapDispatchToProps(dispatch) {
324+
function mapStateToPropsItem(state, props) {
325+
return {
326+
todo: getTodo(state, props.todoId),
327+
};
328+
}
329+
330+
function mapDispatchToPropsItem(dispatch) {
129331
return {
130332
onToggleTodo: id => dispatch(doToggleTodo(id)),
131333
};
132334
}
133335

134-
const ConnectedTodoList = connect(mapStateToProps)(TodoList);
135-
const ConnectedTodoItem = connect(null, mapDispatchToProps)(TodoItem);
336+
function mapDispatchToPropsCreate(dispatch) {
337+
return {
338+
onAddTodo: name => dispatch(doAddTodoWithNotification(uuid(), name)),
339+
};
340+
}
341+
342+
function mapDispatchToPropsFilter(dispatch) {
343+
return {
344+
onSetFilter: filterType => dispatch(doSetFilter(filterType)),
345+
};
346+
}
347+
348+
function mapStateToPropsNotifications(state, props) {
349+
return {
350+
notifications: getNotifications(state),
351+
};
352+
}
353+
354+
const ConnectedTodoList = connect(mapStateToPropsList)(TodoList);
355+
const ConnectedTodoItem = connect(mapStateToPropsItem, mapDispatchToPropsItem)(TodoItem);
356+
const ConnectedTodoCreate = connect(null, mapDispatchToPropsCreate)(TodoCreate);
357+
const ConnectedFilter = connect(null, mapDispatchToPropsFilter)(Filter);
358+
const ConnectedNotifications = connect(mapStateToPropsNotifications)(Notifications);
136359

137360
ReactDOM.render(
138361
<Provider store={store}>
139362
<TodoApp />
140363
</Provider>,
141364
document.getElementById('root')
142-
);
365+
);

0 commit comments

Comments
 (0)