11import React from 'react' ;
22import ReactDOM from 'react-dom' ;
3- import { combineReducers , createStore } from 'redux' ;
3+ import { applyMiddleware , combineReducers , createStore } from 'redux' ;
44import { 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' ;
510import './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
926const TODO_ADD = 'TODO_ADD' ;
1027const TODO_TOGGLE = 'TODO_TOGGLE' ;
1128const FILTER_SET = 'FILTER_SET' ;
29+ const NOTIFICATION_HIDE = 'NOTIFICATION_HIDE' ;
30+ const TODO_ADD_WITH_NOTIFICATION = 'TODO_ADD_WITH_NOTIFICATION' ;
1231
1332// reducers
1433
1534const 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
3266function 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
3773function 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
4581function 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+
60135function 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
83193const 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
92212function 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
137360ReactDOM . render (
138361 < Provider store = { store } >
139362 < TodoApp />
140363 </ Provider > ,
141364 document . getElementById ( 'root' )
142- ) ;
365+ ) ;
0 commit comments