diff --git a/step2-06/demo/index.html b/step2-06/demo/index.html
new file mode 100644
index 0000000..ee4b5cb
--- /dev/null
+++ b/step2-06/demo/index.html
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/step2-06/demo/src/actions/index.ts b/step2-06/demo/src/actions/index.ts
new file mode 100644
index 0000000..4a067b9
--- /dev/null
+++ b/step2-06/demo/src/actions/index.ts
@@ -0,0 +1,8 @@
+import uuid from 'uuid/v4';
+
+export const actions = {
+ addTodo: (label: string) => ({ type: 'addTodo', id: uuid(), label }),
+ remove: (id: string) => ({ type: 'remove', id }),
+ complete: (id: string) => ({ type: 'complete', id }),
+ clear: () => ({ type: 'clear' })
+};
diff --git a/step2-06/demo/src/index.tsx b/step2-06/demo/src/index.tsx
new file mode 100644
index 0000000..e4ca608
--- /dev/null
+++ b/step2-06/demo/src/index.tsx
@@ -0,0 +1,19 @@
+import { reducer } from './reducers';
+import { createStore, compose } from 'redux';
+import { actions } from './actions';
+
+/* Goop for making the Redux dev tool to work */
+declare var window: any;
+const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
+function createStoreWithDevTool(reducer, initialStore) {
+ return createStore(reducer, initialStore, composeEnhancers());
+}
+
+const store = createStoreWithDevTool(reducer, {});
+
+console.log(store.getState());
+
+store.dispatch(actions.addTodo('hello'));
+store.dispatch(actions.addTodo('world'));
+
+console.log(store.getState());
diff --git a/step2-06/demo/src/reducers/index.ts b/step2-06/demo/src/reducers/index.ts
new file mode 100644
index 0000000..6eed3c5
--- /dev/null
+++ b/step2-06/demo/src/reducers/index.ts
@@ -0,0 +1,27 @@
+import { Store } from '../store';
+import { addTodo, remove, complete, clear } from './pureFunctions';
+
+function todoReducer(state: Store['todos'] = {}, action: any): Store['todos'] {
+ switch (action.type) {
+ case 'addTodo':
+ return addTodo(state, action.id, action.label);
+
+ case 'remove':
+ return remove(state, action.id);
+
+ case 'clear':
+ return clear(state);
+
+ case 'complete':
+ return complete(state, action.id);
+ }
+
+ return state;
+}
+
+export function reducer(state: Store, action: any): Store {
+ return {
+ todos: todoReducer(state.todos, action),
+ filter: 'all'
+ };
+}
diff --git a/step2-06/demo/src/reducers/pureFunctions.spec.ts b/step2-06/demo/src/reducers/pureFunctions.spec.ts
new file mode 100644
index 0000000..b3815cf
--- /dev/null
+++ b/step2-06/demo/src/reducers/pureFunctions.spec.ts
@@ -0,0 +1,29 @@
+import { addTodo, complete } 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();
+ });
+
+ it('can complete an item', () => {
+ const state = {};
+
+ let newState = addTodo(state, '0', 'item1');
+
+ const key = Object.keys(newState)[0];
+
+ newState = complete(newState, key);
+
+ expect(newState[key].completed).toBeTruthy();
+ });
+});
diff --git a/step2-06/demo/src/reducers/pureFunctions.ts b/step2-06/demo/src/reducers/pureFunctions.ts
new file mode 100644
index 0000000..69e492a
--- /dev/null
+++ b/step2-06/demo/src/reducers/pureFunctions.ts
@@ -0,0 +1,36 @@
+import { Store, FilterTypes } 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) {
+ const newTodos = { ...state };
+
+ delete newTodos[id];
+
+ return newTodos;
+}
+
+export function complete(state: Store['todos'], id: string) {
+ const newTodos = { ...state };
+ newTodos[id].completed = !newTodos[id].completed;
+
+ return newTodos;
+}
+
+export function clear(state: Store['todos']) {
+ const newTodos = { ...state };
+
+ Object.keys(state.todos).forEach(key => {
+ if (state.todos[key].completed) {
+ delete newTodos[key];
+ }
+ });
+
+ return newTodos;
+}
+
+export function setFilter(state: Store['filter'], filter: FilterTypes) {
+ return filter;
+}
diff --git a/step2-06/demo/src/store/index.ts b/step2-06/demo/src/store/index.ts
new file mode 100644
index 0000000..221b5f4
--- /dev/null
+++ b/step2-06/demo/src/store/index.ts
@@ -0,0 +1,14 @@
+export type FilterTypes = 'all' | 'active' | 'completed';
+
+export interface TodoItem {
+ label: string;
+ completed: boolean;
+}
+
+export interface Store {
+ todos: {
+ [id: string]: TodoItem;
+ };
+
+ filter: FilterTypes;
+}
diff --git a/step2-06/exercise/index.html b/step2-06/exercise/index.html
new file mode 100644
index 0000000..af40d4d
--- /dev/null
+++ b/step2-06/exercise/index.html
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/step2-06/exercise/src/actions/index.ts b/step2-06/exercise/src/actions/index.ts
new file mode 100644
index 0000000..d75c2d9
--- /dev/null
+++ b/step2-06/exercise/src/actions/index.ts
@@ -0,0 +1,5 @@
+import uuid from 'uuid/v4';
+
+export const actions = {
+ addTodo: (label: string) => ({ type: 'addTodo', id: uuid(), label })
+};
diff --git a/step2-06/exercise/src/index.tsx b/step2-06/exercise/src/index.tsx
new file mode 100644
index 0000000..e4ca608
--- /dev/null
+++ b/step2-06/exercise/src/index.tsx
@@ -0,0 +1,19 @@
+import { reducer } from './reducers';
+import { createStore, compose } from 'redux';
+import { actions } from './actions';
+
+/* Goop for making the Redux dev tool to work */
+declare var window: any;
+const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
+function createStoreWithDevTool(reducer, initialStore) {
+ return createStore(reducer, initialStore, composeEnhancers());
+}
+
+const store = createStoreWithDevTool(reducer, {});
+
+console.log(store.getState());
+
+store.dispatch(actions.addTodo('hello'));
+store.dispatch(actions.addTodo('world'));
+
+console.log(store.getState());
diff --git a/step2-06/exercise/src/reducers/index.ts b/step2-06/exercise/src/reducers/index.ts
new file mode 100644
index 0000000..6eed3c5
--- /dev/null
+++ b/step2-06/exercise/src/reducers/index.ts
@@ -0,0 +1,27 @@
+import { Store } from '../store';
+import { addTodo, remove, complete, clear } from './pureFunctions';
+
+function todoReducer(state: Store['todos'] = {}, action: any): Store['todos'] {
+ switch (action.type) {
+ case 'addTodo':
+ return addTodo(state, action.id, action.label);
+
+ case 'remove':
+ return remove(state, action.id);
+
+ case 'clear':
+ return clear(state);
+
+ case 'complete':
+ return complete(state, action.id);
+ }
+
+ return state;
+}
+
+export function reducer(state: Store, action: any): Store {
+ return {
+ todos: todoReducer(state.todos, action),
+ filter: 'all'
+ };
+}
diff --git a/step2-06/exercise/src/reducers/pureFunctions.spec.ts b/step2-06/exercise/src/reducers/pureFunctions.spec.ts
new file mode 100644
index 0000000..b3815cf
--- /dev/null
+++ b/step2-06/exercise/src/reducers/pureFunctions.spec.ts
@@ -0,0 +1,29 @@
+import { addTodo, complete } 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();
+ });
+
+ it('can complete an item', () => {
+ const state = {};
+
+ let newState = addTodo(state, '0', 'item1');
+
+ const key = Object.keys(newState)[0];
+
+ newState = complete(newState, key);
+
+ expect(newState[key].completed).toBeTruthy();
+ });
+});
diff --git a/step2-06/exercise/src/reducers/pureFunctions.ts b/step2-06/exercise/src/reducers/pureFunctions.ts
new file mode 100644
index 0000000..69e492a
--- /dev/null
+++ b/step2-06/exercise/src/reducers/pureFunctions.ts
@@ -0,0 +1,36 @@
+import { Store, FilterTypes } 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) {
+ const newTodos = { ...state };
+
+ delete newTodos[id];
+
+ return newTodos;
+}
+
+export function complete(state: Store['todos'], id: string) {
+ const newTodos = { ...state };
+ newTodos[id].completed = !newTodos[id].completed;
+
+ return newTodos;
+}
+
+export function clear(state: Store['todos']) {
+ const newTodos = { ...state };
+
+ Object.keys(state.todos).forEach(key => {
+ if (state.todos[key].completed) {
+ delete newTodos[key];
+ }
+ });
+
+ return newTodos;
+}
+
+export function setFilter(state: Store['filter'], filter: FilterTypes) {
+ return filter;
+}
diff --git a/step2-06/exercise/src/reducers/reducer.spec.ts b/step2-06/exercise/src/reducers/reducer.spec.ts
new file mode 100644
index 0000000..e97c35e
--- /dev/null
+++ b/step2-06/exercise/src/reducers/reducer.spec.ts
@@ -0,0 +1,10 @@
+describe('reducers', () => {
+ it('should listen to addTodo message', () => {
+ const store = createStoreWithDevTool(reducer, {});
+
+ console.log(store.getState());
+
+ store.dispatch(actions.addTodo('hello'));
+ store.dispatch(actions.addTodo('world'));
+ });
+});
diff --git a/step2-06/exercise/src/store/index.ts b/step2-06/exercise/src/store/index.ts
new file mode 100644
index 0000000..221b5f4
--- /dev/null
+++ b/step2-06/exercise/src/store/index.ts
@@ -0,0 +1,14 @@
+export type FilterTypes = 'all' | 'active' | 'completed';
+
+export interface TodoItem {
+ label: string;
+ completed: boolean;
+}
+
+export interface Store {
+ todos: {
+ [id: string]: TodoItem;
+ };
+
+ filter: FilterTypes;
+}