在上一章Todolist with React (3),使用 React-redux 完成了渲染任務清單、和任務新增刪除的動作,就讓我們繼續完成最後一個部分 — Filter 篩選器,控制任務清單的顯示。

切換上方的 Filter,可以切換對應的任務清單
首先先處理顯示篩選器按鈕的部分,被選擇的按鈕要顯示 Selected (黃底) 的樣式,總共會有三種狀態 SHOW_ALL、SHOW_TODO、SHOW_DONE。
在 filter.js 定義控制 Filter 的 Reducer,State 參數的預設值為 SHOW_ALL,目前尚未定義 Action,直接回傳當前原本的 State。然後要在 index.js 裡整合 Filter Reducer。
reducer/filter.js
import * as types from '../actions/ActionTypes';
export default function filter(state = 'SHOW_ALL', action) {
    switch (action.type) {
      default:
        return state;
    }
}
reducer/index.js
...
import filterReducer from './filter';
const todoApp = combineReducers({
    filterReducer,
    todosReducer
});
因為在 TaskList Component 內也會需要 Filter 的狀態來顯示對應的任務清單,所以我們在 TaskList Component 使用 useSelector 取得目前的 Filter 狀態,再把它傳入 Filter Component。
components/TaskList.js
...
import { useSelector } from "react-redux";
function TaskList() {
...
  const filter = useSelector((store) => store.filterReducer);
  const renderItems = ...
  return (
    <Wrapper>
      <Filter selected = {filter}/>
      ...
    </Wrapper>
  );
}
利用 Styled-components 根據 Props 可以改變樣式的功能,只要按鈕是 Selected 的狀態,傳入的 active 就是 true,會切換背景顏色變成黃色。
components/Filter.js
const Button = styled.div`
  background-color: ${(props) => (props.active ? "#ffc236" : "#bebebe")};
  ...
`;
function Filter(props) {
  return (
    <ButtonContainer>
      <Button active={props.selected === "SHOW_ALL"}>
        ALL
      </Button>
      <Button active={props.selected === "SHOW_TODO"}>
        TODO
      </Button>
      <Button active={props.selected === "SHOW_DONE"}>
        DONE
      </Button>
    </ButtonContainer>
  );
}
可以顯示被選擇的按鈕後,接下來要新增 Filter 切換的功能,在 ActionTypes.js 定義 SET_FILTER 動作。
actions/ActionTypes.js
...
export const SET_FILTER = 'SET_FILTER';
Step1:建立 Action Creator 產生設定 Filter 的動作 Function setFilter,並取得切換的 filter 名稱。
actions/filter.js
import * as types from './ActionTypes';
// action creator
export function setFilter(filter){
    return {
        type: types.SET_FILTER,
        filter
    };
}
Step2:處理 Filter Reducer,讀取 action.type 為 SET_FILTER 時,直接回傳新的 Filter 狀態。
reducer/filter.js
import * as types from '../actions/ActionTypes';
export default function filter(state = 'SHOW_ALL', action) {
    switch (action.type) {
      case types.SET_FILTER:
        return action.filter;
      default:
        return state;
    }
}
Step3:完成 Action 和 Reducer 的設定後,在 Filter Component 利用 useDispatch() 來調用 Action。點擊  後直接呼叫 setFilter(),傳入對應的篩選器名稱。
components/Filter.js
import { useDispatch } from "react-redux";
function Filter(props) {
  const dispatch = useDispatch();
  return (
    <ButtonContainer>
      <Button
        active={...}
        onClick={() => dispatch(actions.setFilter("SHOW_ALL"))}
      >
        ALL
      </Button>
      <Button
        active={...}
        onClick={() => dispatch(actions.setFilter("SHOW_TODO"))}
      >
        TODO
      </Button>
      <Button
        active={...}
        onClick={() => dispatch(actions.setFilter("SHOW_DONE"))}
      >
        DONE
      </Button>
    </ButtonContainer>
  );
}

切換篩選器的同時,下方的任務清單也要可以根據狀態切換對應的列表,我們只在 forEach 裡面新增 if 條件式判斷,總共會有三種情況:
filter === "SHOW_ALL":當 Filter 是 SHOW_ALL 時,判斷式永遠為 true,顯示該任務。filter === "SHOW_TODO" && !item.isCompleted:當 Filter 是 SHOW_TODO 時,任務的狀態 isCompleted 要是 false,判斷式會為 true,才能顯示該任務。filter === "SHOW_DONE" && item.isCompleted:當 Filter 是 SHOW_DONE 時,任務的狀態 isCompleted 要是 true,判斷式會為 true,才能顯示該任務。components/TaskList.js
function TaskList() {
  ...
  const renderItems = () => {
    let list = [];
    
    tasks.forEach((item, index) => {
      if (
        (filter === "SHOW_ALL") ||
        (filter === "SHOW_TODO" && !item.isCompleted) ||
        (filter === "SHOW_DONE" && item.isCompleted)
      ) {
        list.push(
          <TaskItem key={item.taskName} task={{ ...item, idx: index }} />
        );
      }
    });
    
    return list;
  };
  return (
    <Wrapper>
      ...
      <TaskItemContainer>{renderItems()}</TaskItemContainer>
    </Wrapper>
  );
}

終於完成任務清單和篩選器的功能,剩下最後一個功能 — 完成任務的時候要可以勾選框框,設定任務的狀態。一樣在 ActionTypes.js 定義勾選任務的動作 TOGGLE_TASK。
actions/ActionTypes.js
...
export const TOGGLE_TASK = 'TOGGLE_TASK';
Step1:建立 Action Creator 產生勾選 Task 的動作 Function toggleTask,並取得對應的任務索引值。
actions/todos.js
export function toggleTask(idx){
    return {
        type: types.TOGGLE_TASK,
        idx
    };
}
Step2:處理 Todo Reducer,讀取 action.type 為 TOGGLE_TASK 時,修改該任務的完成狀態並回傳的新 State。
reducer/todos.js
...
export default function todos(state = initialTasks, action) {
  switch (action.type) {
    case types.ADD_TASK:...
    case types.DELETE_TASK:...
    case types.TOGGLE_TASK:
      let newState = [...state];
      newState[action.idx].isCompleted = !newState[action.idx].isCompleted;
      return newState;
    default:...
  }
}
Step3:完成 Action 和 Reducer 的設定後,在 TaskItem Component 利用 useDispatch() 來調用 Action。點擊 <CheckBox /> 後直接呼叫 toggleTask(),傳入對應的任務索引值。
components/TaskItem.js
function TaskItem(props) {
  const dispatch = useDispatch();
  return (
    <Container>
      <CheckBox
        type="checkbox"
        checked={...}
        onChange={() => dispatch(actions.toggleTask(props.task.idx))}
      />
      ...
    </Container>
  );
}

這樣就完成了一個簡單的 Todolist 囉
如果文章中有錯誤的地方,要麻煩各位大大不吝賜教;喜歡的話,也要記得幫我按讚訂閱喔❤️