Todo App

A frontend development challenge that showcases a todo application.

Challenge provided by Frontend Mentor.

Skills

  • Frontend Development
  • Accessibility Compliance
  • User Interface Design

Tools

Timeline

September - October 2023

Screenshot of a todo app web interface

Preface

This is a solution to the Todo app challenge on Frontend Mentor. Frontend Mentor challenges help you improve your coding skills by building realistic projects.

The Challenge

Users should be able to:

  • View the optimal layout for the app depending on their device's screen size
  • See hover states for all interactive elements on the page
  • Add new todos to the list
  • Mark todos as complete
  • Delete todos from the list
  • Filter by all/active/complete todos
  • Clear all completed todos
  • Toggle light and dark mode
  • Bonus: Drag and drop to reorder items on the list

Learnings

How to use a Reducer within a React project. I've had some exposure to this pattern before, but this was the perfect opportunity to dig into it a little further. I think I came away with a sold understanding, and clear structure.

My reducer actions:

Javascript
export const ACTIONS = {
  ADD_TODO: "ADD_TODO",
  DELETE_TODO: "DELETE_TODO",
  TOGGLE_TODO: "TOGGLE_TODO",
  CLEAR_COMPLETE: "CLEAR_COMPLETE",
  SHOW_ALL_TODOS: "SHOW_ALL_TODOS",
  SHOW_ACTIVE_TODOS: "SHOW_ACTIVE_TODOS",
  SHOW_COMPLETE_TODOS: "SHOW_COMPLETE_TODOS",
};

export function AppReducer(state, action) {
  switch (action.type) {
    case ACTIONS.ADD_TODO:
      return {
        ...state,
        todos: [...state.todos, action.payload],
      };
    case ACTIONS.DELETE_TODO:
      return {
        ...state,
        todos: state.todos.filter(
          (item) => item.id !== action.payload
        ),
        activeTodos: state.activeTodos.filter(
          (item) => item.id !== action.payload
        ),
        completeTodos: state.completeTodos.filter(
          (item) => item.id !== action.payload
        ),
      };
    case ACTIONS.TOGGLE_TODO:
      return {
        ...state,
        todos: state.todos.map((item) => {
          if (item.id === action.payload) {
            return { ...item, completed: !item.completed };
          }
          return item;
        }),
        activeTodos: state.activeTodos.map((item) => {
          if (item.id === action.payload) {
            return { ...item, completed: !item.completed };
          }
          return item;
        }),
        completeTodos: state.completeTodos.map((item) => {
          if (item.id === action.payload) {
            return { ...item, completed: !item.completed };
          }
          return item;
        }),
      };
    case ACTIONS.CLEAR_COMPLETE:
      return {
        ...state,
        todos: state.todos.filter((item) => item.completed === false),
        completeTodos: []
      };
    case ACTIONS.SHOW_ALL_TODOS:
      return {
        ...state,
        showTodos: true,
        showActiveTodos: false,
        showCompleteTodos: false,
      };
    case ACTIONS.SHOW_ACTIVE_TODOS:
      return {
        ...state,
        activeTodos: [
          ...state.todos.filter((item) => item.completed === false),
        ],
        showTodos: false,
        showActiveTodos: true,
        showCompleteTodos: false,
      };
    case ACTIONS.SHOW_COMPLETE_TODOS:
      return {
        ...state,
        completeTodos: [
          ...state.todos.filter((item) => item.completed === true),
        ],
        showTodos: false,
        showActiveTodos: false,
        showCompleteTodos: true,
      };
    default:
      return state;
  }
}

How I setup the context within the application:

Javascript
"use client";

import React, { createContext, useReducer, useContext } from "react";
import { ACTIONS, AppReducer } from "@/context/reducer";

export const AppContext = createContext();

export function AppContextProvider({ children }) {

  const initialState = {
    todos: [],
    activeTodos: [],
    completeTodos: [],
    showTodos: true,
    showActiveTodos: false,
    showCompleteTodos: false,
  };

  const [state, dispatch] = useReducer(AppReducer, initialState);

  const addTodo = (object) => {
    dispatch({ type: ACTIONS.ADD_TODO, payload: object });
  };

  const deleteTodo = (id) => {
    dispatch({ type: ACTIONS.DELETE_TODO, payload: id });
  };

  const toggleTodo = (id) => {
    dispatch({ type: ACTIONS.TOGGLE_TODO, payload: id });
  };

  const clearTodos = () => {
    dispatch({ type: ACTIONS.CLEAR_COMPLETE });
  };

  const showAllTodos = () => {
    dispatch({ type: ACTIONS.SHOW_ALL_TODOS });
  };

  const showActiveTodos = () => {
    dispatch({ type: ACTIONS.SHOW_ACTIVE_TODOS });
  };

  const showCompleteTodos = () => {
    dispatch({ type: ACTIONS.SHOW_COMPLETE_TODOS });
  };

  return (
    <AppContext.Provider
      value={{ state,
        addTodo,
        deleteTodo,
        toggleTodo,
        clearTodos,
        showAllTodos,
        showActiveTodos,
        showCompleteTodos }}
    >
      {children}
    </AppContext.Provider>
  );
}

export function useAppContext() {
  return useContext(AppContext);
}

Continued Development

Something I would be interested in furthing develop would be the drag and drop interface. I already implemented a lot of new patterns with the project, and I thought that would be overload. A tool that I'm interested in using would be:

Has anyone with the Frontend Mentor community used this tool? What would it take to integrate into this project?

Resources