Skip to content

Commit

Permalink
feat: add comments to explain code (#17)
Browse files Browse the repository at this point in the history
  • Loading branch information
tobiasprima authored Mar 21, 2024
1 parent aad52ec commit 7f43cb0
Show file tree
Hide file tree
Showing 11 changed files with 92 additions and 36 deletions.
1 change: 1 addition & 0 deletions src/App.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import './App.css';
import TodoList from './features/todos/TodoList';

// Main component rendering the TodoList feature
function App() {
return (
<>
Expand Down
24 changes: 12 additions & 12 deletions src/app/api/apiSlice.js
Original file line number Diff line number Diff line change
@@ -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',
Expand All @@ -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`,
Expand All @@ -35,6 +31,7 @@ export const apiSlice = createApi({
}),
invalidatesTags: ['Todos']
}),
// Endpoint to delete todo
deleteTodo: builder.mutation({
query: ({ id })=> ({
url: `/todo/${id}`,
Expand All @@ -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',
Expand All @@ -51,6 +49,7 @@ export const apiSlice = createApi({
}),
invalidatesTags: ['Todos']
}),
// Endpoint to reset todos' order values
resetTodo: builder.mutation({
query: (todos) => ({
url: '/todos/reset',
Expand All @@ -62,6 +61,7 @@ export const apiSlice = createApi({
})
})

// Extracting hooks for each API endpoint
export const {
useGetTodosQuery,
useAddTodoMutation,
Expand Down
5 changes: 5 additions & 0 deletions src/app/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
1 change: 1 addition & 0 deletions src/features/header/Header.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react';

// Header component displays the title and description of the todo list
const Header = () => {
return (
<header>
Expand Down
1 change: 1 addition & 0 deletions src/features/loader/Loader.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';
import MoonLoader from 'react-spinners/MoonLoader';

// Loader component displays a spinning loader animation
const Loader = () => {
return (
<div className='loader'>
Expand Down
8 changes: 8 additions & 0 deletions src/features/progressbar/ProgressBar.js
Original file line number Diff line number Diff line change
@@ -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 (
Expand Down
5 changes: 5 additions & 0 deletions src/features/todos/TodoForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<div className='new-item-section'>
Expand Down
20 changes: 18 additions & 2 deletions src/features/todos/TodoItem.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<article ref={todo._id === newTodoId ? scrollToRef : null}>
<div className="todo">
Expand Down
59 changes: 37 additions & 22 deletions src/features/todos/TodoList.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -78,66 +85,72 @@ 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
)
)
}

// 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 = <Loader />
} 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
Expand All @@ -146,6 +159,7 @@ const TodoList = () => {
})
}
content = sortedTodos.map(todo => {
// Map todos to TodoItem components
return (
<TodoItem
key={todo._id}
Expand All @@ -158,6 +172,7 @@ const TodoList = () => {
)
})
} else if (isError) {
// If there's error(No database connection), display todos without database interaction
content = (
todoWithoutDB.map(todo => (
<TodoItem
Expand Down
3 changes: 3 additions & 0 deletions src/features/toggle/ToggleSwitch.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import React from 'react';

// ToggleSwitch component renders a switch to toggle moving done items to the end of the list
// Props:
// - handleMoveDoneToEnd: Function to handle moving done todos to the bottom of the list
const ToggleSwitch = ({ handleMoveDoneToEnd }) => {
return (
<div className="toggle-switch">
Expand Down
1 change: 1 addition & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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(
<React.StrictMode>
Expand Down

0 comments on commit 7f43cb0

Please sign in to comment.