To Do Diagram




Read / Fetch



Constant

  • File: app\containers\SampleFullstackApps\Todo\reducers\todoConstants.js
  • Define constant:
                              
    export const LOAD_TASK = 'LOAD_TASK';
    export const LOAD_TASKS_FULFILLED = 'LOAD_TASKS_FULFILLED';
                              
                            

Actions

  • File: app\containers\SampleFullstackApps\Todo\reducers\todoActions.js
  • Put each constant in actions
                              
    export const loadTasksFulfilled = tasks => ({
      type: types.LOAD_TASKS_FULFILLED,
      payload: { tasks }
    });
                              
                            

Saga

  • File: app\containers\SampleFullstackApps\Todo\reducers\todoSagas.js
  • Call load task action in saga's function
                              
    function* read() {
      const channel = yield call(subscribe);
      while (true) {
        const action = yield take(channel);
        yield put(action);
      }
    }
    
    function* watchLoadTask() {
      while (true) {
        taskList.path = 'tasks/';
        const job = yield fork(read);
    
        yield take([LOAD_TASK]);
        yield cancel(job);
      }
    }
                              
                            
  • Subscribe from models
                              
    import TodoModels from '../models';
    import Init from '../models/init';
    
    import {
      loadTasksFulfilled,
    } from './todoActions';
    
    function subscribe() {
      return eventChannel(emit => taskList.subscribe(emit));
    }
    
    const taskList = new TodoModels({
      onLoad: loadTasksFulfilled,
    }, Init);
                              
                            

Reducer

  • File: app\containers\SampleFullstackApps\Todo\reducers\todoReducer.js
  • Put all payload action with constant LOAD_TASKS_FULFILLED in reducer state. So the font-end part (React components) can get the state to modify ui or contents.
                              
    case LOAD_TASKS_FULFILLED:
      return state.merge({
        list: new List(payload.tasks.reverse()),
        loading: false,
      });
                              
                            

Container

  • File: app/containers/SampleFullstackApps/Todo/index.js
  • Load state data to container as prop
                              
    import { getVisibleTasks } from './reducers/selectors';
    
    const { task } = this.props;
    
    return (
      <TaskList
        loading={loading}
        removeTask={removeTask}
        tasks={tasks}
        updateTask={updateTask}
      />
    )
                
    const mapStateToProps = state => ({
      tasks: getVisibleTasks(state.get(reducerTodo)),
    });
    
                              
                            

Components

  • File: app/components/TodoList/TaskList.js
  • Populate tasks prop data to component
                              
    const taskItems = tasks.map((task, index) => (
      <TaskItem
        removeTask={removeTask}
        key={index.toString()}
        task={task}
        updateTask={updateTask}
      />
    ));
                              
                            


Create



Constant

  • File: app\containers\SampleFullstackApps\Todo\reducers\todoConstants.js
  • Define constant:
                      
    export const CREATE_TASK = 'CREATE_TASK';
    export const CREATE_TASK_FAILED = 'CREATE_TASK_FAILED';
    export const CREATE_TASK_FULFILLED = 'CREATE_TASK_FULFILLED';
                      
                    

Actions

  • File: \app\containers\SampleFullstackApps\Todo\reducers\todoActions.js
  • Put each constant in actions
                      
    export const createTaskAction = title => ({
      type: types.CREATE_TASK,
      payload: { task: { title, completed: false } }
    });
    
    export const createTaskFailed = error => ({
      type: types.CREATE_TASK_FAILED,
      payload: { error }
    });
    
    export const createTaskFulfilled = task => ({
      type: types.CREATE_TASK_FULFILLED,
      payload: { task }
    });
                      
                    

Saga

  • File: app\containers\SampleFullstackApps\Todo\reducers\todoSagas.js
  • Define create models
                      
    const createTask = write.bind(null, taskList, taskList.push, createTaskFailed);
                      
                    
  • Call create task action in saga's function
                      
    function* watchCreateTask() {
      while (true) {
        const { payload } = yield take(CREATE_TASK);
        yield fork(createTask, payload.task);
      }
    }
                      
                    

Reducer

  • File: app\containers\SampleFullstackApps\Todo\reducers\todoReducer.js
  • Put all payload action with constant CREATE_TASK_FULFILLED in reducer state. So the font-end part (React components) can get the state to modify ui or contents.
                      
    case CREATE_TASK_FULFILLED:
      return state.set('list', state.list.unshift(payload.task));
                      
                    

Container

  • File: app/containers/SampleFullstackApps/Todo/index.js
  • Dispatch create action in container
                      
    import { createTaskAction } from './reducers/todoActions';
    
    const mapDispatchToProps = dispatch => ({
      createTask: bindActionCreators(createTaskAction, dispatch),
    });
    
    <TaskForm handleSubmit={createTask} />
                      
                    

Component

  • File: \app\components\TodoList\TaskForm.js
  • Get and submit New task
                      
    handleSubmit(event) {
      event.preventDefault();
      const { title } = this.state;
      const { handleSubmit } = this.props;
      const titleString = title.trim();
      if (titleString.length) handleSubmit(titleString);
      this.clearInput();
    }
    
    return (
      <form onSubmit={this.handleSubmit} noValidate>
         ...
      </form>
    )
                      
                    


Update



Constant

  • File: app\containers\SampleFullstackApps\Todo\reducers\todoConstants.js
  • Define constant:
                      
    export const UPDATE_TASK = 'UPDATE_TASK';
    export const UPDATE_TASK_FAILED = 'UPDATE_TASK_FAILED';
    export const UPDATE_TASK_FULFILLED = 'UPDATE_TASK_FULFILLED';
                      
                    

Actions

  • File: \app\containers\SampleFullstackApps\Todo\reducers\todoActions.js
  • Put each constant in actions
                      
    export const updateTaskAction = (task, changes) => ({
      type: types.UPDATE_TASK,
      payload: { task, changes }
    });
    
    export const updateTaskFailed = error => ({
      type: types.UPDATE_TASK_FAILED,
      payload: { error }
    });
    
    export const updateTaskFulfilled = task => ({
      type: types.UPDATE_TASK_FULFILLED,
      payload: { task }
    });
                      
                    

Saga

  • File: app\containers\SampleFullstackApps\Todo\reducers\todoSagas.js
  • Define update models
                      
    const updateTask = write.bind(null, taskList, taskList.update, updateTaskFailed);
                      
                    
  • Call update task action in saga's function
                      
    function* watchUpdateTask() {
      while (true) {
        const { payload } = yield take(UPDATE_TASK);
        yield fork(updateTask, payload.task.key, payload.changes);
      }
    }
                      
                    

Reducer

  • File: app\containers\SampleFullstackApps\Todo\reducers\todoReducer.js
  • Put all payload action with constant UPDATE_TASK_FULFILLED in reducer state. So the font-end part (React components) can get the state to modify ui or contents.
                      
    case UPDATE_TASK_FULFILLED:
      return state.set('list', state.list.map(task => (task.key === payload.task.key ? payload.task : task)));
                      
                    

Container

  • File: app/containers/SampleFullstackApps/Todo/index.js
  • Dispatch create action in container
                      
    import { updateTaskAction } from './reducers/todoActions';
    
    const mapDispatchToProps = dispatch => ({
      updateTask: bindActionCreators(updateTaskAction, dispatch),
    });
    
    return (
      <TaskList
        loading={loading}
        removeTask={removeTask}
        tasks={tasks}
        updateTask={updateTask}
      />
    )
                      
                    

Component

  • File: app\components\TodoList\TaskItem.js
  • Get and send the updated value
                      
    save(event) {
      const { editing } = this.state;
      const { updateTask } = this.props;
      if (editing) {
        const { task } = this.props;
        const title = event.target.value.trim();
    
        if (title.length && title !== task.title) {
          updateTask(task, { title });
        }
    
        this.stopEditing();
      }
    }
    
    return (
      <input
        autoFocus // eslint-disable-line
        autoComplete="off"
        defaultValue={task.title}
        maxLength="64"
        onKeyUp={this.handleKeyUp}
        type="text"
      />
    )
      
      
                      
                    


Delete



Constants

  • File: app\containers\SampleFullstackApps\Todo\reducers\todoConstants.js
  • Define constant
                      
    export const REMOVE_TASK = 'REMOVE_TASK';
    export const REMOVE_TASK_FAILED = 'REMOVE_TASK_FAILED';
    export const REMOVE_TASK_FULFILLED = 'REMOVE_TASK_FULFILLED';
                      
                    

Actions

  • File: \app\containers\SampleFullstackApps\Todo\reducers\todoActions.js
  • Put each constant in actions
                      
    export const removeTaskAction = task => ({
      type: types.REMOVE_TASK,
      payload: { task }
    });
    
    export const removeTaskFailed = error => ({
      type: types.REMOVE_TASK_FAILED,
      payload: { error }
    });
    
    export const removeTaskFulfilled = task => ({
      type: types.REMOVE_TASK_FULFILLED,
      payload: { task }
    });
                      
                    

Saga

  • File: app\containers\SampleFullstackApps\Todo\reducers\todoSagas.js
  • Define Remove models
                      
    const removeTask = write.bind(null, taskList, taskList.remove, removeTaskFailed);
                      
                    
  • Call remove task action in saga's function
                      
    function* watchRemoveTask() {
      while (true) {
        const { payload } = yield take(REMOVE_TASK);
        yield fork(removeTask, payload.task.key);
      }
    }
                      
                    

Reducer

  • File: app\containers\SampleFullstackApps\Todo\reducers\todoReducer.js
  • Put all payload action with constant REMOVE_TASK_FULFILLED in reducer state. So the font-end part (React components) can get the state to modify ui or contents.
                    
    case REMOVE_TASK_FULFILLED:
        return state.set('list', state.list.filter(task => task.key !== payload.task.key));
                      
                    

Container

  • File: app/containers/SampleFullstackApps/Todo/index.js
  • Dispatch remove action in container
                    
    import { removeTaskAction } from './reducers/todoActions';
    
    const mapDispatchToProps = dispatch => ({
      removeTask: bindActionCreators(removeTaskAction, dispatch),
    });
    
    return (
      <TaskList
        loading={loading}
        removeTask={removeTask}
        tasks={tasks}
        updateTask={updateTask}
      />
    )
                      
                    

Component

  • File: app\components\TodoList\TaskItem.js
  • Call remove function by onClick remove button
                      
    remove() {
      const { removeTask, task } = this.props;
      removeTask(task);
      this.setState({ anchorEl: null });
    }
    
    return (
      <IconButton
        className={
          classNames(
            classes.button,
            editing && classes.hide
          )
        }
        size="small"
        onClick={this.remove}
      >
        <DeleteIcon />
      </IconButton>
    )