From 7f43cb0ea10865f20badc5a67b1e5a6ebaa5079c Mon Sep 17 00:00:00 2001 From: Tobias Prima <88434271+tobiasprima@users.noreply.github.com> Date: Fri, 22 Mar 2024 00:34:47 +0800 Subject: [PATCH] feat: add comments to explain code (#17) --- src/App.js | 1 + src/app/api/apiSlice.js | 24 +++++----- src/app/store.js | 5 +++ src/features/header/Header.js | 1 + src/features/loader/Loader.js | 1 + src/features/progressbar/ProgressBar.js | 8 ++++ src/features/todos/TodoForm.js | 5 +++ src/features/todos/TodoItem.js | 20 ++++++++- src/features/todos/TodoList.js | 59 ++++++++++++++++--------- src/features/toggle/ToggleSwitch.js | 3 ++ src/index.js | 1 + 11 files changed, 92 insertions(+), 36 deletions(-) diff --git a/src/App.js b/src/App.js index 3b979c0..751a9ba 100644 --- a/src/App.js +++ b/src/App.js @@ -1,6 +1,7 @@ import './App.css'; import TodoList from './features/todos/TodoList'; +// Main component rendering the TodoList feature function App() { return ( <> diff --git a/src/app/api/apiSlice.js b/src/app/api/apiSlice.js index 37e00ed..c7853a4 100644 --- a/src/app/api/apiSlice.js +++ b/src/app/api/apiSlice.js @@ -1,16 +1,19 @@ import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react' +// apiSlice defines an API for interacting with the backend server for managing todos export const apiSlice = createApi({ - // https://todolistapi-kcjj.onrender.com - reducerPath: 'api', - baseQuery: fetchBaseQuery({ baseUrl: 'https://todolistapi-kcjj.onrender.com' }), - tagTypes: ['Todos'], + // https://todolistapi-kcjj.onrender.com --> backend url + reducerPath: 'api', // Path for the reducer + baseQuery: fetchBaseQuery({ baseUrl: 'https://todolistapi-kcjj.onrender.com' }), // To test without db set this to empty string '' + tagTypes: ['Todos'], // Define tag types for caching endpoints: (builder) => ({ + // Endpoint to fetch todos getTodos: builder.query({ query: () => '/todos', transformResponse: res => res.sort((a,b) => b.order - a.order), providesTags: ['Todos'] }), + // Endpoint to add a new todo addTodo: builder.mutation({ query: (todo) => ({ url: '/todo', @@ -19,14 +22,7 @@ export const apiSlice = createApi({ }), invalidatesTags: ['Todos'] }), - updateTodo: builder.mutation({ - query: (todo) => ({ - url: `/todo/${todo.id}/title`, - method: 'PATCH', - body: todo - }), - invalidatesTags: ['Todos'] - }), + // Endpoint to mark todo as done doneTodo: builder.mutation({ query: (todo) => ({ url: `/todo/${todo.id}/status`, @@ -35,6 +31,7 @@ export const apiSlice = createApi({ }), invalidatesTags: ['Todos'] }), + // Endpoint to delete todo deleteTodo: builder.mutation({ query: ({ id })=> ({ url: `/todo/${id}`, @@ -43,6 +40,7 @@ export const apiSlice = createApi({ }), invalidatesTags: ['Todos'] }), + // Endpoint to reorder todo (Gives order value to todos based on todos' status) reorderTodo: builder.mutation({ query: (todos)=> ({ url: '/todos/reorder', @@ -51,6 +49,7 @@ export const apiSlice = createApi({ }), invalidatesTags: ['Todos'] }), + // Endpoint to reset todos' order values resetTodo: builder.mutation({ query: (todos) => ({ url: '/todos/reset', @@ -62,6 +61,7 @@ export const apiSlice = createApi({ }) }) +// Extracting hooks for each API endpoint export const { useGetTodosQuery, useAddTodoMutation, diff --git a/src/app/store.js b/src/app/store.js index d58dc5b..fa6162e 100644 --- a/src/app/store.js +++ b/src/app/store.js @@ -2,12 +2,17 @@ import { configureStore } from "@reduxjs/toolkit"; import { apiSlice } from "./api/apiSlice"; import { setupListeners } from "@reduxjs/toolkit/query"; +// Configure Redux store export const store = configureStore({ + // Combine reducers with the apiSlice reducer reducer: { [apiSlice.reducerPath]: apiSlice.reducer, }, + // Add middleware for handling API requests middleware: getDefaultMiddleware => getDefaultMiddleware().concat(apiSlice.middleware), + // Disable Redux DevTools extension devTools: false }) +// Setup listeners for automatic query lifecycle management setupListeners(store.dispatch); \ No newline at end of file diff --git a/src/features/header/Header.js b/src/features/header/Header.js index 33bef1b..1e4e188 100644 --- a/src/features/header/Header.js +++ b/src/features/header/Header.js @@ -1,5 +1,6 @@ import React from 'react'; +// Header component displays the title and description of the todo list const Header = () => { return (
diff --git a/src/features/loader/Loader.js b/src/features/loader/Loader.js index 4127237..374fda5 100644 --- a/src/features/loader/Loader.js +++ b/src/features/loader/Loader.js @@ -1,6 +1,7 @@ import React from 'react'; import MoonLoader from 'react-spinners/MoonLoader'; +// Loader component displays a spinning loader animation const Loader = () => { return (
diff --git a/src/features/progressbar/ProgressBar.js b/src/features/progressbar/ProgressBar.js index ccff7db..02c8682 100644 --- a/src/features/progressbar/ProgressBar.js +++ b/src/features/progressbar/ProgressBar.js @@ -1,9 +1,17 @@ import React from 'react'; +// ProgressBar component displays the progress of completed todos in the todo list const ProgressBar = ({ todos }) => { + // Extracting progress data from props const progressData = todos || []; + + // Counting the number of completed todos const completedTodos = progressData.filter(todo => todo.status).length; + + // Total number of todos const totalTodos = progressData.length; + + // Calculating the progress percentage const progressPercentage = totalTodos !== 0 ? Math.round((completedTodos / totalTodos) * 100) : 0; return ( diff --git a/src/features/todos/TodoForm.js b/src/features/todos/TodoForm.js index 3e861d7..04ee057 100644 --- a/src/features/todos/TodoForm.js +++ b/src/features/todos/TodoForm.js @@ -2,6 +2,11 @@ import React from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faPlus } from '@fortawesome/free-solid-svg-icons'; +// TodoForm component renders a form to add new todos to the list +// Props: +// - newTodo: Current value of the new todo input field +// - setNewTodo: Function to update the newTodo state when input changes +// - handleSubmit: Function to handle form submission when adding a new todo const TodoForm = ({ newTodo, setNewTodo, handleSubmit }) => { return (
diff --git a/src/features/todos/TodoItem.js b/src/features/todos/TodoItem.js index 5938795..10d1349 100644 --- a/src/features/todos/TodoItem.js +++ b/src/features/todos/TodoItem.js @@ -2,23 +2,39 @@ import React from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faXmark } from '@fortawesome/free-solid-svg-icons'; - -const TodoItem = ({ todo, newTodoId, scrollToRef, doneTodo, deleteTodo, handleToggleStatus, handleDeleteTodo }) => { +// TodoItem component represents an individual todo item in the todo list +const TodoItem = ({ + todo, // Todo object containing title and status + newTodoId, // ID of the newly created todo (used for scrolling) + scrollToRef, // Ref to scroll to the newly created todo + doneTodo, // Function to mark todo as done (if there is connection to database) + deleteTodo, // Function to delete todo (if there is connection to database) + handleToggleStatus, // Function to handle toggle status (if no database interaction) + handleDeleteTodo // Function to delete todo (if no database interaction) +}) => { const toggleStatus = ()=> { + // Function to toggle the status of the todo item if (doneTodo) { + // Call doneTodo to mark todo as done (database connection exist) doneTodo({ id: todo._id, status: !todo.status}) } else if (handleToggleStatus) { + // Call handleToggleStatus to mark todo as done (no database connection) handleToggleStatus(todo._id) } } + // Function to handle deletion of the todo item const handleDelete = () => { if (deleteTodo) { + // Call deleteTodo function to delete todo (database connection exist) deleteTodo({ id: todo._id }) } else if (handleDelete) { + // Call handleDeleteTodo function to delete todo (no database connection) handleDeleteTodo(todo._id) } } + + // Render todo item return (
diff --git a/src/features/todos/TodoList.js b/src/features/todos/TodoList.js index 0914dda..4ddb124 100644 --- a/src/features/todos/TodoList.js +++ b/src/features/todos/TodoList.js @@ -16,13 +16,18 @@ import { } from '../../app/api/apiSlice' const TodoList = () => { - const [ isResetDone, setisResetDone ] = useState(false) - const [ todoWithoutDB, setTodoWithoutDB ] = useState([]) - const [ newTodo, setNewTodo ] = useState('') - const [ moveDoneToEnd, setMoveDoneToEnd ] = useState(false) - const [ newTodoId, setNewTodoId ] = useState(null) - const scrollToRef = useRef(null) + // State variables initialization + // When there is connection to Database we need to reset order value to 0 + const [ isResetDone, setisResetDone ] = useState(false) // Indicates if todos reset is done + const [ todoWithoutDB, setTodoWithoutDB ] = useState([]) // Stores todos without database interaction + const [ newTodo, setNewTodo ] = useState('') // Stores new todo input + const [ moveDoneToEnd, setMoveDoneToEnd ] = useState(false) // Indicates if done todos should move to the end + const [ newTodoId, setNewTodoId ] = useState(null) // Stores id of newly added todo + const scrollToRef = useRef(null) // Reference for scrolling to new todo + + + // Custom hooks for fetching todos and database mutation const { data: todos, isLoading, @@ -41,15 +46,15 @@ const TodoList = () => { useEffect(() => { const storedTodos = JSON.parse(localStorage.getItem('todos')); if (!todos && storedTodos) { - // Set todos to local storage's todos + // If there are cached todos, set them setTodoWithoutDB(storedTodos) } else if (!todos){ - // If there is no DB and nothing on cache, set the todos to empty array + // If no cached todos and no database todos, initialize as empty array setTodoWithoutDB([]) } }, [todos]) - // Reset Todos' Order to 0 when page first mounted + // Effect to reset todos' order when todos are fetched for the first time (When there is database connection) useEffect(()=> { if (todos && !isResetDone){ resetTodo(todos) @@ -59,10 +64,12 @@ const TodoList = () => { // Effect to save todos to local storage when todos change useEffect(() => { - localStorage.setItem('todos', JSON.stringify(todoWithoutDB)) - }, [todoWithoutDB]) + if (!todos){ + localStorage.setItem('todos', JSON.stringify(todoWithoutDB)) + } + }, [todos, todoWithoutDB]) - // Effect to update local state when there is DB and todos are fetched + // Effect to update local state when todos are fetched from the database useEffect(()=> { if (todos && !isError) { setTodoWithoutDB(todos) @@ -78,30 +85,33 @@ const TodoList = () => { } }) - // Handle New Todo + // Handle submission of new todo const handleSubmit = (e)=> { e.preventDefault() if (newTodo.trim() === '') return // Prevent adding empty todos if (!isError){ + // If there is no error, add todo using database mutation addTodo({title: newTodo}).then((response) => { // Scroll to newly created todo setNewTodoId(response.data._id) }) } else { - // Handle submit without database + // Handle adding todo without database interaction const newTodoWithoutDB = {_id: Date.now(), title: newTodo, status: false} if (moveDoneToEnd) { + // If moving done todos to the end, insert new todo at the beginnign of the list setTodoWithoutDB((prevTodos) => [newTodoWithoutDB, ...prevTodos]); } else { + // Otherwise, add new todo to the end setTodoWithoutDB((prevTodos) => [...prevTodos, newTodoWithoutDB]); } setNewTodoId(newTodoWithoutDB._id) // Scroll to newly created todo } - setNewTodo('') + setNewTodo('') // Clear input field after submission } - // Handle Todos' complete checkbox without database + // Handle toggling status of todo without database interaction const handleToggleStatus = (id) => { setTodoWithoutDB(prevTodos => prevTodos.map(todo => todo._id === id ? {...todo, status: !todo.status} : todo @@ -109,35 +119,38 @@ const TodoList = () => { ) } - // Handle delete without database + // Handle deletion of todo without database interaction const handleDeleteTodo = (id) => { setTodoWithoutDB(prevTodos => prevTodos.filter(todo => todo._id !== id)) } - // Handle Moving Done Todos to Bottom of the list + // Handle moving done todos to the end of the list const handleMoveDoneToEnd = async () => { if(todos){ await reorderTodo(todos); await refetchTodos(); } else { + // Toggle moveDoneToEnd state and reorder todos without database interaction setMoveDoneToEnd((prevMoveDoneToEnd) => !prevMoveDoneToEnd) setTodoWithoutDB((prevTodos) => { - // Handle moving done todos without db + // Handle moving done todos without without database interaction const doneTodos = prevTodos.filter((todo)=> todo.status) const undoneTodos = prevTodos.filter((todo)=> !todo.status) return moveDoneToEnd ? [...doneTodos, ...undoneTodos] : - [...undoneTodos, ...doneTodos] - + [...undoneTodos, ...doneTodos] }) } } - let content; + let content; // Define content based on loading state and success/error status if (isLoading || reorderTodoLoading || resetTodoLoading) { + // Display loader if data is loading or mutations are in progress content = } else if (isSuccess){ + // If todos are successfully fetched, display them let sortedTodos = todos; if (todos.length> 1){ + // Sort todos baded on order if there are multiple todos sortedTodos = todos.slice().sort((a, b) => { if (a.order !== 0 && b.order !==0){ return b.order - a.order @@ -146,6 +159,7 @@ const TodoList = () => { }) } content = sortedTodos.map(todo => { + // Map todos to TodoItem components return ( { ) }) } else if (isError) { + // If there's error(No database connection), display todos without database interaction content = ( todoWithoutDB.map(todo => ( { return (
diff --git a/src/index.js b/src/index.js index 232ef2c..092aa31 100644 --- a/src/index.js +++ b/src/index.js @@ -6,6 +6,7 @@ import App from './App'; import { Provider } from 'react-redux'; import { store } from './app/store'; +// Create a root for ReactDOM and render the App component wrapped in Redux Provider const root = ReactDOM.createRoot(document.getElementById('root')); root.render(