/** * A reducer for a single todo * @param state * @param action * @returns {*} */ const todo = ( state, action ) => { switch ( action.type ) { case 'ADD_TODO': return { id: action.id, text: action.text, completed: false }; case 'TOGGLE_TODO': if ( state.id !== action.id ) { return state; } return { ...state, completed: !state.completed }; default: return state; } }; /** * The reducer for the todos * @param state * @param action * @returns {*} */ const todos = ( state = [], action ) => { switch ( action.type ) { case 'ADD_TODO': return [ ...state, todo( undefined, action ) ]; case 'TOGGLE_TODO': return state.map( t => todo( t, action ) ); default: return state; } }; /** * Reducer for the visibilityFilter * @param state * @param action * @returns {*} */ const visibilityFilter = ( state = 'SHOW_ALL', action ) => { switch ( action.type ) { case 'SET_VISIBILITY_FILTER': return action.filter; default: return state; } }; const getVisibleTodos = ( todos, filter ) => { switch (filter) { case 'SHOW_ALL': return todos; case 'SHOW_COMPLETED': return todos.filter( t => t.completed ); case 'SHOW_ACTIVE': return todos.filter( t => !t.completed ); } } /** * combineReducers: used for merge reducers togethger * createStore: create a redux store */ const { combineReducers, createStore } = Redux; const todoApp = combineReducers( { todos, visibilityFilter } ); const store = createStore( todoApp ); const FilterLink = ({ filter, currentFilter, children }) => { if (filter === currentFilter) { return <span>{children}</span>; } return ( <a href='#' onClick={e => { e.preventDefault(); store.dispatch({ type: 'SET_VISIBILITY_FILTER', filter }); }} > {children} </a> ); }; /** * For generate todo's id * @type {number} */ let nextTodoId = 0; /** * React related */ const {Component} = React; class TodoApp extends Component { render() { const {todos, visibilityFilter} = this.props; let visibleTodos = getVisibleTodos(todos, visibilityFilter); return ( <div> <input ref={ (node)=>{ this.input = node } }/> <button onClick={ ()=>{ //After clicking the button, dispatch a add todo action store.dispatch({ type: 'ADD_TODO', id: nextTodoId++, text: this.input.value }) this.input.value = ""; } }>ADD todo </button> <ul> {visibleTodos.map( ( todo )=> { return ( <li key={todo.id} style={{ textDecoration: todo.completed ? 'line-through' : 'none' }} onClick={ ()=>{ store.dispatch({ type: 'TOGGLE_TODO', id: todo.id }) }} > {todo.text} </li> ) } )} </ul> <p> Show: {' '} <FilterLink filter="SHOW_ALL" currentFilter={visibilityFilter}> All </FilterLink> {' '} <FilterLink filter="SHOW_ACTIVE" currentFilter={visibilityFilter}> Active </FilterLink> {' '} <FilterLink filter="SHOW_COMPLETED" currentFilter={visibilityFilter}> Completed </FilterLink> {' '} </p> </div> ); } } const render = () => { ReactDOM.render( <TodoApp {...store.getState()}/>, document.getElementById( 'root' ) ); }; //Every time, store updated, also fire the render() function store.subscribe( render ); render();