diff --git a/step2-05/demo/README.md b/step2-05/demo/README.md index 35ad1b7..fcdf68c 100644 --- a/step2-05/demo/README.md +++ b/step2-05/demo/README.md @@ -26,17 +26,17 @@ A view is a React component that consumes the store as its data. ### Action -[Actions](https://redux.js.org/basics/actions) are messages that represent some event, such as a user's action or a network request. With the aid of _reducers_, they affect the overall state. +[Actions](https://redux.js.org/basics/actions) are serializable JSON messages that represent some event, such as a user's action or a network request. With the aid of _reducers_, they affect the overall state. At the minimum, it should contain a `type` key. Sometimes it contains additional data as a _payload_. ### Store The [store](https://redux.js.org/basics/store) consists of a **state tree**, a **dispatcher**, and **reducers**. -1. A state tree is a **singleton**, **serializable**, **immutable** json data. It is updated from one snapshot to another through `reducers`. +1. The **state tree** is a _singleton_, _serializable_, _immutable_ json data. It is updated from one snapshot to another through `reducers`. -2. A dispatcher accepts actions passing them to the reducers. +2. The **dispatcher** accepts actions passing them to the reducers. -3. Reducers are functions that take in the current state tree and an action, producing the next snapshot of the state tree. +3. **Reducers** are functions that take in the current state tree and an action, producing the next snapshot of the state tree. # Creating the Redux store diff --git a/step2-05/demo/index.html b/step2-05/demo/index.html index 92a9499..3581831 100644 --- a/step2-05/demo/index.html +++ b/step2-05/demo/index.html @@ -6,10 +6,18 @@
- For this step, we look at unit testing. Run -
npm test
- in the command line. +

+ Nothing to show here; just look at your console window for output. Hit F12 (cmd+option+I on Mac) to open console + window. +

+ +

+ To inspect Redux store, use the Redux Dev Tool extension for your browser: + Chrome, + FireFox. (Sorry, no Edge or IE support) +

+ diff --git a/step2-05/demo/src/actions/index.ts b/step2-05/demo/src/actions/index.ts new file mode 100644 index 0000000..78ec851 --- /dev/null +++ b/step2-05/demo/src/actions/index.ts @@ -0,0 +1,9 @@ +import nextId from 'uuid/v4'; + +export const actions = { + addTodo: (label: string) => ({ type: 'addTodo', id: nextId(), label }), + remove: (id: string) => ({ type: 'remove', id }), + complete: (id: string) => ({ type: 'complete', id }), + clear: () => ({ type: 'clear' }), + setFilter: (filter: string) => ({ type: 'setFilter', filter }) +}; diff --git a/step2-05/demo/src/index.tsx b/step2-05/demo/src/index.tsx index 6c057ce..cca1020 100644 --- a/step2-05/demo/src/index.tsx +++ b/step2-05/demo/src/index.tsx @@ -1,6 +1,15 @@ import { reducer } from './reducers'; import { createStore } from 'redux'; +import { actions } from './actions'; +import { composeWithDevTools } from 'redux-devtools-extension'; -const store = createStore(reducer); +const store = createStore(reducer, {}, composeWithDevTools()); + +store.dispatch(actions.addTodo('hello')); + +let action = actions.addTodo('world'); +store.dispatch(action); + +store.dispatch(actions.remove(action.id)); console.log(store.getState()); diff --git a/step2-05/demo/src/reducers/index.ts b/step2-05/demo/src/reducers/index.ts index 9dd64b3..3fac61f 100644 --- a/step2-05/demo/src/reducers/index.ts +++ b/step2-05/demo/src/reducers/index.ts @@ -1,20 +1,43 @@ import { Store } from '../store'; -import { addTodo, remove, complete, clear } from './pureFunctions'; +import { combineReducers } from 'redux'; +import { createReducer } from 'redux-starter-kit'; -export function reducer(state: Store['todos'], payload: any): Store['todos'] { - switch (payload.type) { - case 'addTodo': - return addTodo(state, payload.id, payload.label); +export const todosReducer = createReducer( + {}, + { + addTodo(state, action) { + state[action.id] = { label: action.label, completed: false }; + }, - case 'remove': - return remove(state, payload.id); + remove(state, action) { + delete state[action.id]; + }, - case 'complete': - return complete(state, payload.id); + clear(state, action) { + state[action.id].completed = !state[action.id].completed; + }, - case 'clear': - return clear(state); + complete(state, action) { + Object.keys(state).forEach(key => { + if (state[key].completed) { + delete state[key]; + } + }); + }, + + edit(state, action) { + state[action.id].label = action.label; + } } +); - return state; -} +export const filterReducer = createReducer('all', { + setFilter(state, action) { + return action.filter; + } +}); + +export const reducer = combineReducers({ + todos: todosReducer, + filter: filterReducer +}); diff --git a/step2-05/demo/src/reducers/pureFunctions.spec.ts b/step2-05/demo/src/reducers/pureFunctions.spec.ts deleted file mode 100644 index 99adcfd..0000000 --- a/step2-05/demo/src/reducers/pureFunctions.spec.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { addTodo } from './pureFunctions'; -import { Store } from '../store'; - -describe('TodoApp reducers', () => { - it('can add an item', () => { - const state = {}; - - const newState = addTodo(state, '0', 'item1'); - - const keys = Object.keys(newState); - - expect(newState).not.toBe(state); - expect(keys.length).toBe(1); - expect(newState[keys[0]].label).toBe('item1'); - expect(newState[keys[0]].completed).toBeFalsy(); - }); -}); diff --git a/step2-05/demo/src/reducers/pureFunctions.ts b/step2-05/demo/src/reducers/pureFunctions.ts deleted file mode 100644 index a7f98ce..0000000 --- a/step2-05/demo/src/reducers/pureFunctions.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { Store } from '../store'; - -export function addTodo(state: Store['todos'], id: string, label: string): Store['todos'] { - return { ...state, [id]: { label, completed: false } }; -} - -export function remove(state: Store['todos'], id: string) { - // Clone the Todos - const newTodos = { ...state }; - - // Delete an item in the object by the key - delete newTodos[id]; - - return newTodos; -} - -export function complete(state: Store['todos'], id: string) { - // Clone the todo, overriding - const newTodo = { ...state[id], completed: !state[id].completed }; - return { ...state, [id]: newTodo }; -} - -export function clear(state: Store['todos']) { - // Clone the todos - const newTodos = { ...state }; - - // Delete all todos based on the completed flag, looping over the keys of the todos - Object.keys(state).forEach(key => { - if (state[key].completed) { - delete newTodos[key]; - } - }); - - return newTodos; -} diff --git a/step2-05/exercise/index.html b/step2-05/exercise/index.html index e908cc3..ee7d10d 100644 --- a/step2-05/exercise/index.html +++ b/step2-05/exercise/index.html @@ -5,11 +5,7 @@
-
- For this step, we look at unit testing. Run -
npm test
- in the command line. -
+
diff --git a/step2-05/exercise/src/actions/index.ts b/step2-05/exercise/src/actions/index.ts new file mode 100644 index 0000000..78ec851 --- /dev/null +++ b/step2-05/exercise/src/actions/index.ts @@ -0,0 +1,9 @@ +import nextId from 'uuid/v4'; + +export const actions = { + addTodo: (label: string) => ({ type: 'addTodo', id: nextId(), label }), + remove: (id: string) => ({ type: 'remove', id }), + complete: (id: string) => ({ type: 'complete', id }), + clear: () => ({ type: 'clear' }), + setFilter: (filter: string) => ({ type: 'setFilter', filter }) +}; diff --git a/step2-05/exercise/src/index.tsx b/step2-05/exercise/src/index.tsx index 6c057ce..cca1020 100644 --- a/step2-05/exercise/src/index.tsx +++ b/step2-05/exercise/src/index.tsx @@ -1,6 +1,15 @@ import { reducer } from './reducers'; import { createStore } from 'redux'; +import { actions } from './actions'; +import { composeWithDevTools } from 'redux-devtools-extension'; -const store = createStore(reducer); +const store = createStore(reducer, {}, composeWithDevTools()); + +store.dispatch(actions.addTodo('hello')); + +let action = actions.addTodo('world'); +store.dispatch(action); + +store.dispatch(actions.remove(action.id)); console.log(store.getState()); diff --git a/step2-05/exercise/src/reducers/index.ts b/step2-05/exercise/src/reducers/index.ts index b48bc42..3fac61f 100644 --- a/step2-05/exercise/src/reducers/index.ts +++ b/step2-05/exercise/src/reducers/index.ts @@ -1,16 +1,43 @@ import { Store } from '../store'; -import { addTodo, remove, complete } from './pureFunctions'; +import { combineReducers } from 'redux'; +import { createReducer } from 'redux-starter-kit'; -export function reducer(state: Store['todos'], payload: any): Store['todos'] { - switch (payload.type) { - case 'addTodo': - return addTodo(state, payload.id, payload.label); +export const todosReducer = createReducer( + {}, + { + addTodo(state, action) { + state[action.id] = { label: action.label, completed: false }; + }, - // Fill in the blanks here for: - // - remove - // - complete - // - clear + remove(state, action) { + delete state[action.id]; + }, + + clear(state, action) { + state[action.id].completed = !state[action.id].completed; + }, + + complete(state, action) { + Object.keys(state).forEach(key => { + if (state[key].completed) { + delete state[key]; + } + }); + }, + + edit(state, action) { + state[action.id].label = action.label; + } } +); - return state; -} +export const filterReducer = createReducer('all', { + setFilter(state, action) { + return action.filter; + } +}); + +export const reducer = combineReducers({ + todos: todosReducer, + filter: filterReducer +}); diff --git a/step2-05/exercise/src/reducers/pureFunctions.spec.ts b/step2-05/exercise/src/reducers/pureFunctions.spec.ts deleted file mode 100644 index aa7018e..0000000 --- a/step2-05/exercise/src/reducers/pureFunctions.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { addTodo } from './pureFunctions'; -import { Store } from '../store'; - -describe('TodoApp reducers', () => { - it('can add an item', () => { - const state = {}; - - const newState = addTodo(state, '0', 'item1'); - - const keys = Object.keys(newState); - - // make sure that adding an item would not result in the same instance of state - // TODO: uncomment the below to get started - /* - - expect(newState).not.toBe(state); - - expect(keys.length).toBe(1); - expect(newState[keys[0]].label).toBe('item1'); - expect(newState[keys[0]].completed).toBeFalsy(); - */ - }); - - // TODO: add a test for remove() -}); diff --git a/step2-05/exercise/src/reducers/pureFunctions.ts b/step2-05/exercise/src/reducers/pureFunctions.ts deleted file mode 100644 index 4f2b931..0000000 --- a/step2-05/exercise/src/reducers/pureFunctions.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { Store, FilterTypes } from '../store'; - -export function addTodo(state: Store['todos'], id: string, label: string): Store['todos'] { - // Write code to clone the state object while inserting a new TodoItem inside - // - the new object must be of the type TodoItem - // - the new state should be cloned using the spread syntax - // - return the new state - - return state; -} - -export function remove(state: Store['todos'], id: string) { - // Write code: - // - to clone the state object into new state object - // - remove and item from the new state by using the "delete" keyword - // - return the new state - - return state; -} - -export function complete(state: Store['todos'], id: string) { - // Clone the todo, overriding - const newTodo = { ...state[id], completed: !state[id].completed }; - return { ...state, [id]: newTodo }; -} - -export function clear(state: Store['todos']) { - // Clone the todos - const newTodos = { ...state }; - - // Delete all todos based on the completed flag, looping over the keys of the todos - Object.keys(state).forEach(key => { - if (state[key].completed) { - delete newTodos[key]; - } - }); - - return newTodos; -}