Kid's To-Do: Redux
Tags: tutorial, redux, react, explainlikeimfive
Published: 2019-08-21 17:22:54

Let's create a To-Do app in Redux.

I want to start a component tree that can display list of toggle-able todos.

I'm gonna bootstrap Redux instance right inside React's default `index.js`, file management may come forth later in lifetime of this app. I'll write another post about storing your Redux multi-modular instance properly.

I'll build new root `<TodoApp />`, which will be wired up o the Redux state management.

Then passing properties down the tree: `<TodoList />` with `<TodoItem />`. Latter will show the name of the todo and has a toggle on it's surface.

It's yet unreasonable to *connect* each of these components to Redux with `connect`, because they're not far away. However consider using this HOC in larger applications. 

Pretty straigthforward. Make sure you've got internet connection, and a screwdriver in case your diskette is jammed. 

## Step 1: Begin with creating React app

`npx create-react-app todo-app`

`cd todo-app`

Install Redux

`npm i --save redux`

`npm start`

## Step 2: Redux Instance

Modify `index.js` as follows to import Redux functionalities, *provide* Redux state to the component tree by wrapping it inside `<Provider />`. `TodoApp` component is *connected* with `connect` HOC, exposing state and dispatcher to the tree  

    import { combineReducers, createStore } from 'redux'
    import { Provider } from 'react-redux'
    import TodoApp from './todoApp'
    
    /*
    * action types 
    * initial state
    * reducers
    * action creators
    * reducer combination
    * store declaration
    */
    
    function mapStateToProps(state) {
      return {
        todos: state.todoState,
      };
    }
    function mapDispatchToProps(dispatch) {
      return {
         onToggleTodo: id => dispatch(doToggleTodo(id)),
      };
    }
    
    const ConnectedTodoApp = connect(mapStateToProps, mapDispatchToProps)(TodoApp);
    
    ReactDOM.render(
      <Provider store={store}>
        <ConnectedTodoApp />
      </Provider>,
      document.getElementById('root')
    );

### Action Types

    // action types
    const TODO_ADD = 'TODO_ADD';
    const TODO_TOGGLE = 'TODO_TOGGLE'; 
    const FILTER_SET = 'FILTER_SET';

### Initial State

    // initial state
    const todos = [
    	{ id: '0', name: 'Outline blog post' }, 
    	{ id: '1', name: 'Build TodoApp' },
    ];

### Reducers

    // reducers
    function todoReducer(state = todos, action) {
    	switch(action.type) {
    		case TODO_ADD: {
    			return applyAddTodo(state, action);
    		}
    		case TODO_TOGGLE: {
    			return applyToggleTodo(state, action); 
    		}
    		default : return state; 
    	}
    } 
    
    function applyAddTodo(state, action) {
    	const todo = Object.assign({}, action.todo, {completed: false});
    	return state.concat(todo);
    }
    
    function applyToggleTodo(state, action) {
    	return state.map(todo => todo.id === action.todo.id ?
    		Object.assign({}, todo, {completed: !todo.completed})
    		: todo
    	);
    }
    
    function filterReducer(state = 'SHOW_ALL', action) {
    	switch(action.type) {
    	case FILTER_SET: {
    		return applySetFilter(state, action);
    	}
    	default: return state;
    	}
    }
    
    function applySetFilter(state, action) {
    	return action.filter;
    }

### Action Creators

    // action creators
    function doAddTodo(id, name) {
    	return {
    		type: TODO_ADD,
    		todo: {id, name}
    	};
    }
    
    function doToggleTodo(id) {
    	return {
    		type: TODO_TOGGLE,
    		todo: { id }
    	};
    }
    
    function doSetFilter(filter) {
    	return {
    		type: FILTER_SET,
    		filter
    	};
    }

Finally, let's combine `todoReducer` with `filterReducer` and create store

    const rootReducer = combineReducers({
    	todoState: todoReducer,
    	filterState: filterReducer
    });
    
    const store = createStore(rootReducer);

Having installed Redux, let's build To-Do application component tree starting with a new root `TodoApp`.

## Step 3: Components

### `TodoApp`

    import React from 'react'
    import TodoList from './todoList'
    
    export default function TodoApp({ todos, onToggleTodo }) { 
    	return (<TodoList
    				    todos={store.getState().todoState}
    					  onToggleTodo={id => store.dispatch(doToggleTodo(id))}
    				  />);
    }

### `TodoList`

    import React from 'react'
    import TodoItem from './todoItem'
    
    export default function TodoList({ todos, onToggleTodo }) { 
    	return (
        <div>
          {todos.map(todo => 
    				<TodoItem
    	        key={todo.id}
    	        todo={todo}
    	        onToggleTodo={onToggleTodo}
    				/>)} 
    		</div>
    	); 
    }
    

### `TodoItem`

    import React from 'react'
    
    export default function TodoItem({ todo, onToggleTodo }) { 
    	const { name, id, completed } = todo; 
    		return (
    			<div> {name}
    		    <button
    	        type="button"
    	        onClick={() => onToggleTodo(id)}
    	      >
            {completed ? "Incomplete" : "Complete"}
    				</button>
    			</div>
    		);
    }

None of these components are aware of Redux. They simply display todos and use callbacks to propagate *toggle* of todo items.

The store does two things: it makes state accessible and exposes functionalities to alter the state. The todos props are passed down to the `TodoApp` by retrieveing them from the `store` instance. In addition, `onToggleTodo` function is passed down the tree as a property, notice that it's a HOC that wraps the dispatching of an action that is created by its action creator.