Skip to content

[AIBE6/4팀/김락현] TODO APP 만들기 완료#57

Open
Rakhyunn wants to merge 13 commits intosik2:mainfrom
Rakhyunn:main
Open

[AIBE6/4팀/김락현] TODO APP 만들기 완료#57
Rakhyunn wants to merge 13 commits intosik2:mainfrom
Rakhyunn:main

Conversation

@Rakhyunn
Copy link

@Rakhyunn Rakhyunn commented Mar 19, 2026

✅ 기능 구현

  • 할 일 추가 / 개별 삭제 / 전체 삭제
  • 체크박스 클릭으로 완료 토글
  • localStorage 자동 저장 및 초기 로드
  • 메인 페이지에서 남은 할 일 수, 완료 수, 진행률 표시

📁 프로젝트 구조

src/
├── components/
│ ├── TodoForm.jsx # 할 일 입력 폼
│ ├── TodoItem.jsx # 개별 할 일 항목
│ └── TodoList.jsx # 할 일 목록 렌더링
├── context/
│ └── TodoContext.jsx # Context API 전역 상태 제공
├── hooks/
│ └── useTodos.js # 할 일 CRUD 로직 Custom Hook
├── pages/
│ ├── Main.jsx # 메인 페이지
│ └── Todo.jsx # TODO 관리 페이지
├── utils/
│ └── storage.js # 로컬 스토리지 처리
├── App.jsx # 라우팅 설정
└── main.jsx # 앱 진입점

💻 실행 화면

스크린 샷

영상

❗참고 사항

디자인 관련

  • 디자인을 구상 하였는데 아무래도 처음이라 원하는 디테일하게 만들기가 어려워 AI를 활용하였습니다.
  • 현재 사용된 속성을 표로 정리하여 공부하고 있어 추후 프로젝트에서는 직접 디자인까지 구현해보겠습니다.

피드백 관련

  • 적극적인 피드백 환영입니다! 리뷰 잘 부탁드립니다!! 😄

@gemini-code-assist
Copy link

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

이 PR은 React와 Vite를 활용하여 기본적인 할 일 관리 애플리케이션의 개발을 완료합니다. 주요 기능으로는 할 일 추가, 삭제, 완료 상태 토글, 그리고 localStorage를 통한 데이터 영구 저장이 포함됩니다. 또한, 메인 페이지에서는 현재 할 일의 진행 상황을 한눈에 볼 수 있도록 통계 정보를 제공하여 사용자 경험을 향상시켰습니다.

Highlights

  • 할 일 관리 기능 구현: 할 일 추가, 개별 삭제, 전체 삭제 기능을 구현하고, 체크박스 클릭으로 할 일 완료 상태를 토글할 수 있도록 했습니다.
  • 데이터 영속성 및 상태 관리: localStorage를 이용해 할 일을 자동으로 저장하고 앱 로드 시 불러오도록 구현했으며, React Context API와 Custom Hook을 활용하여 전역 상태 관리 및 할 일 CRUD 로직을 분리했습니다.
  • 메인 페이지 통계 및 진행률 표시: 메인 페이지에서 남은 할 일 수, 완료된 할 일 수, 그리고 전체 할 일 대비 진행률을 시각적으로 표시하도록 추가했습니다.
  • 개발 환경 설정: Prettier, ESLint, TailwindCSS를 포함한 개발 환경 설정을 완료하여 코드 품질과 일관성을 유지하도록 했습니다.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

전반적으로 훌륭하게 TODO 앱의 기능들을 구현하셨습니다. 컴포넌트 구조화, Context API를 통한 전역 상태 관리, 그리고 Custom Hook을 활용한 로직 분리 등 React의 주요 개념들을 잘 이해하고 적용하신 점이 인상 깊습니다. 코드의 안정성과 효율성을 높일 수 있는 몇 가지 개선점에 대해 리뷰를 남겼으니 확인해보시면 좋겠습니다.

Comment on lines +9 to +11
useEffect(() => {
saveTodos('todos', todos)
})

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

useEffect에 의존성 배열이 없어 컴포넌트가 리렌더링될 때마다 saveTodos 함수가 불필요하게 호출됩니다. 이는 성능 저하의 원인이 될 수 있습니다. todos 상태가 변경될 때만 localStorage에 저장하도록 의존성 배열에 [todos]를 추가하는 것이 좋습니다.

Suggested change
useEffect(() => {
saveTodos('todos', todos)
})
useEffect(() => {
saveTodos('todos', todos)
}, [todos])

Comment on lines +12 to +15
export function useTodoContext() {
const context = useContext(TodoContext)
return context
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

useTodoContext 훅이 TodoProvider 외부에서 사용될 경우 context 값이 undefined가 되어 에러가 발생할 수 있습니다. 개발 중에 이런 실수를 쉽게 발견하고 디버깅을 용이하게 하기 위해, contextundefined일 때 명시적으로 에러를 발생시키는 것이 좋은 습관입니다.

Suggested change
export function useTodoContext() {
const context = useContext(TodoContext)
return context
}
export function useTodoContext() {
const context = useContext(TodoContext)
if (context === undefined) {
throw new Error('useTodoContext must be used within a TodoProvider')
}
return context
}

Comment on lines +5 to +8
} catch (err) {
console.log('load error')
return null
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

localStorage에서 데이터를 불러오는 중 에러가 발생했을 때 null을 반환하고 있습니다. useTodos 훅에서 useState(getTodos('todos'))의 초기값으로 null이 들어가게 되면, todos를 배열로 예상하고 사용하는 다른 컴포넌트(예: Main.jsxtodos.length)에서 런타임 에러가 발생할 수 있습니다. 에러 발생 시에도 빈 배열 []을 반환하여 예상치 못한 에러를 방지하는 것이 더 안전합니다.

Suggested change
} catch (err) {
console.log('load error')
return null
}
} catch (err) {
console.error('Failed to load todos from localStorage', err)
return []
}

Comment on lines +7 to +17
const handleOnSubmit = (e) => {
e.preventDefault() // form 기본 동작(페이지 새로고침) 방지
const form = e.target
// 공백만 입력한 경우도 막기 위해 trim() 사용
if (form.input.value.trim().length == 0) {
alert('할 일을 입력해주세요')
return
}
addTodo(form.input.value)
form.input.value = ''
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

현재 폼의 입력값을 DOM에 직접 접근하여 가져오고 있습니다 (e.target.input.value). React에서는 useState를 사용하여 입력값을 상태로 관리하는 '제어 컴포넌트(Controlled Component)' 패턴을 사용하는 것이 더 안정적이고 일반적입니다. 이렇게 하면 React의 상태 관리 시스템 안에서 값을 다룰 수 있어 코드가 더 예측 가능해집니다.

아래와 같이 리팩토링하는 것을 추천합니다.

  1. useState를 import하고 컴포넌트 상단에 상태를 추가합니다.
import { useState } from 'react'
// ...
const [text, setText] = useState('')
  1. handleOnSubmit 함수를 수정하여 state를 사용합니다.
const handleOnSubmit = (e) => {
    e.preventDefault()
    if (text.trim().length === 0) {
        alert('할 일을 입력해주세요')
        return
    }
    addTodo(text)
    setText('')
}
  1. <input> 엘리먼트를 수정하여 state와 연결합니다.
<input
    // ... other props
    value={text}
    onChange={(e) => setText(e.target.value)}
/>

}

const removeTodo = (id) => {
const filterTodo = todos.filter((todo) => todo.id != id)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

느슨한 비교(!=) 대신 타입까지 엄격하게 비교하는 !== 연산자를 사용하는 것이 예기치 않은 타입 변환으로 인한 버그를 방지하는 데 도움이 됩니다.

Suggested change
const filterTodo = todos.filter((todo) => todo.id != id)
const filterTodo = todos.filter((todo) => todo.id !== id)

}

const toggleTodo = (id) => {
const updateTodo = todos.map((todo) => (todo.id == id ? { ...todo, completed: !todo.completed } : todo))

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

느슨한 비교(==) 대신 타입까지 엄격하게 비교하는 === 연산자를 사용하는 것이 예기치 않은 타입 변환으로 인한 버그를 방지하는 데 도움이 됩니다.

Suggested change
const updateTodo = todos.map((todo) => (todo.id == id ? { ...todo, completed: !todo.completed } : todo))
const updateTodo = todos.map((todo) => (todo.id === id ? { ...todo, completed: !todo.completed } : todo))

setTodos([])
}

return { todos, setTodos, addTodo, removeTodo, toggleTodo, removeAllTodos }

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

setTodos를 직접 노출하면 이 hook을 사용하는 컴포넌트에서 addTodo, removeTodo 등의 로직을 거치지 않고 todos 상태를 마음대로 변경할 수 있습니다. 이는 상태 관리의 일관성을 해칠 수 있습니다. hook의 캡슐화를 위해 setTodos를 직접 반환하기보다는, 상태 변경이 필요할 때 사용할 수 있는 함수들(addTodo, removeTodo 등)만 노출하는 것이 좋습니다.

Suggested change
return { todos, setTodos, addTodo, removeTodo, toggleTodo, removeAllTodos }
return { todos, addTodo, removeTodo, toggleTodo, removeAllTodos }

Comment on lines +9 to +11
const leftCount = todos.filter((todo) => !todo.completed).length
const completedCount = todos.filter((todo) => todo.completed).length
const percent = count === 0 ? 0 : Math.round((completedCount / count) * 100)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

leftCountcompletedCount를 계산하기 위해 todos 배열을 두 번 순회하고 있습니다. 할 일의 개수가 많아지면 성능에 미미한 영향을 줄 수 있습니다. reduce를 사용하여 배열을 한 번만 순회하면서 두 값을 동시에 계산하면 더 효율적입니다.

    const { leftCount, completedCount } = todos.reduce(
        (acc, todo) => {
            if (todo.completed) {
                acc.completedCount++
            } else {
                acc.leftCount++
            }
            return acc
        },
        { leftCount: 0, completedCount: 0 },
    )
    const count = todos.length
    const percent = count === 0 ? 0 : Math.round((completedCount / count) * 100)

@tke0329
Copy link

tke0329 commented Mar 19, 2026

removeAllTodo, 진행률을 나타내는 ui 좋은 기능인것 같아요!! 역시 킹락현

@haxxru
Copy link

haxxru commented Mar 19, 2026

todo.id 를 lastId.current + 1 의 방식이 아닌 crypto.randomUUID() 를 이용하여 고유의 id 를 부여하는 게 인상적입니다! 진행률 프로그래스바와 UI도 사용자가 사용하기에 간편해보입니다!

@Sinou3936
Copy link

UI 면에서 있어서 잘 짜여진 코드 인거 같네요
제 생각에는 한 화면에 보여주는게 오히려 낫지 않았나 싶기도 해요 카운트 되는 것을 만드신다고 고생 많았어요
만약 넣는다면 리스트 위에 가운데 부분에 칸 만들어서 사용해보는것도 나쁘진 않을거 같네요

@Rakhyunn
Copy link
Author

UI 면에서 있어서 잘 짜여진 코드 인거 같네요 제 생각에는 한 화면에 보여주는게 오히려 낫지 않았나 싶기도 해요 카운트 되는 것을 만드신다고 고생 많았어요 만약 넣는다면 리스트 위에 가운데 부분에 칸 만들어서 사용해보는것도 나쁘진 않을거 같네요

@Sinou3936 한 화면에 UI를 넣으면 간단한 TODO 관리 앱이라 가볍게 사용하기 좋을 것 같아요!
이번에 배운 리액트 라우터 부분과 전역 상태 관리를 적용해보고 싶어서 페이지 단위로 나누어봤습니다. 리뷰 감사합니다 😃

@m1nhy2uk
Copy link

완료한 할일과 진행률을 보여주는 기능이 인상 깊어요. 사용자가 사용하기 편해보입니다.
고생하셨습니다

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants