Skip to content

Conversation

@LeeTaegyung
Copy link
Collaborator

요구사항

기본

  • ‘로고’ 버튼을 클릭하면 ‘/’ 페이지로 이동합니다. (새로고침)
  • 진행 중인 할 일과 완료된 할 일을 나누어 볼 수 있습니다.
  • 상단 입력창에 할 일 텍스트를 입력하고 추가하기 버튼을 클릭하거나 엔터를 치면 할 일을 새로 생성합니다.
  • 진행 중 할 일 항목의 왼쪽 버튼을 클릭하면 체크 표시가 되면서 완료 상태가 됩니다.
  • 완료된 할 일 항목의 왼쪽 버튼을 다시 클릭하면 체크 표시가 사라지면서 진행 중 상태가 됩니다.

주요 변경사항

  • 투두리스트에서만 사용되는 Empty 관련 컴포넌트 /app/_components/Empty 폴더로 위치 이동
  • 투두 추가시 router.refresh()로 데이터 동기화 하는 코드를 서버 액션과 revalidateTag 조합으로 변경

스크린샷

image

멘토에게

  • PR 계속 클로즈 해서 죄송합니다.. 이상하게 계속 이전 커밋 히스토리가 포함되서 PR이 되어서 해결하느라고 계속 PR을 닫게 되었네요 ㅠㅠ...
  • 바로 미션10으로 넘어갈까 하다가... 궁금한게 있어서 코드리뷰 요청드립니다.
  • 이전 코드 리뷰에서 Empty 관련해서 route group과 private folder 언급을 해주셔서 파일 위치를 이동을 해보았는데요. 이러고 나니깐 넥스트는 폴더구조를 보통 어떻게 구성하는지 좀 헷갈리더라구요.
    검색을 해봤을 땐 프로젝트의 규모에 따라 다르겠지만 app 폴더와 형제 폴더로 components, api, ... 이런식으로 사용한다는 포스팅을 보기도 했었고, 큰 규모로 가면 feature 폴더를 추가하기도 한다고 봤었어요. 참고했던 포스트 입니다!
    그래서 강사님이 route group과 private folder를 언급 해주셨을 때 좀 혼란이 오기도 했었습니다! 말씀하셨던 뜻이 이렇게 진행을 하라고 하셨던게 맞는지도 궁금하기도 하구요 ㅎㅎ 곧 있으면 심화 프로젝트에 들어가기도 해서 혹시 넥스트로 진행을 하게 된다면 폴더 구조를 어떻게 짜는게 좋을지 혹시 참고할만한 글이 있을지 (구글링을 했을 땐 거의 첨부 드린 링크랑 내용이 거의 비슷했어요 ㅎㅎ) 궁금합니다!
  • 셀프 코드 리뷰를 통해 질문 이어가겠습니다.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

/src/features/todo/components → /src/app/_components/Empty 이동되었습니다!

Copy link
Collaborator

Choose a reason for hiding this comment

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

네, 만약 공용 폴더라면 app 바깥에 components 폴더를 만들어 유지해주시고,
특징 라우트그룹/ 페이지에서만 쓰이는 컴포넌트라면 이렇게 유지해주시면 될것같아요 :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

서버 액션을 사용해봤습니다. 파일 위치가 app 폴더 내에 있는게 좀 이상한거 같긴 한데, 공식 문서에서도 app 폴더 내에 위치하고 있어서 여기에 생성을 했습니다.

Comment on lines +17 to 24
const initialDataRef = useRef<TodoItemType[]>(data);
const [optimisticState, toggleOptimisticState] = useOptimistic<
TodoItemType[],
number
>(todoAll, (currentState, id) => {
>(initialDataRef.current, (currentState, id) => {
return currentState.map((todo) =>
todo.id === id ? { ...todo, isCompleted: !todo.isCompleted } : todo
);
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

말씀하셨던대로 useOptimistic의 초기값을 data로 설정했지만 여전히 이전 데이터로 돌아가더라구요....
뭔가 page에서 내려주는 데이터가 최신화가 아닌 상태로 props로 계속 내려주고 있는 와중에 TodoListArea가 계속 렌더링 되면서 useOptimistic를 계속 초기화값으로 되돌리는게 아닌가라고 추측을 해서..(사실인지 아닌지는 잘 모르겠습니다..) 어쩔수 없이 useRef를 사용할 수 밖에 없었습니다..
여기 부분은 미션10에서 리액트쿼리 사용하면서 다시 적용해보겠습니다!

Copy link
Collaborator

Choose a reason for hiding this comment

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

ㅎㅎ 제가 이 문제를 전체 코드베이스 훑어보며 좀 파보니까, 구조적으로 이런 문제가 있더라고요

  • page.tsx 컴포넌트의 <TodoAddForm/>에서 새로운 할 일을 추가할 때 createTodoItemAction이 실행됨
  • 이 액션에서 revalidateTag("todoList")가 호출되어 캐시가 무효화됨
  • Next.js는 캐시가 무효화되면 서버 컴포넌트를 다시 렌더링하므로, page.tsx의 getTodoList()가 다시 호출되어 새로운 데이터를 가져오고
  • 이 과정을 통해 새로운 data prop이 TodoListArea에 전달됨
  • 새로운 data prop이 전달되면 useOptimistic의 초기값이 재설정됨

결론적으로 이 문제는 서버 컴포넌트의 리렌더링 때문에 발생하는 현상입니다.
서버 상태와 클라이언트 상태를 분리하셔야할것같아요.

예를 들면

const TodoListArea = ({ data }: Props) => {
  // 클라이언트 상태로 서버 데이터 관리
  const [clientData, setClientData] = useState(data);
...

이런식으로 클라이언트 상태로 서버 데이터를 관리하는게 부가적으로 필요할것같고,
useOptimistic의 초기값을 clientData로 고정한다음
서버 데이터가 변경될 때 + API 응답 성공시 클라이언트 데이터를 업데이트하는 과정이 필연적일것같네요.

결과를 보면 이전에 태경님이 생각하셨던 방식과 비슷하게 풀어가는게 좋을 것 같아요.
다만, 이제 정확한 구조적 원인을 파악했으니 이 문제를 클라이언트 / 서버 상태를 분리하는 방식으로 해결해본다는 점이 이전에 드렸던 피드백과 살짝 달라지겠네요 :)

한번 참고해보시고, 더 좋은 방법이 있을지 고민해보세요!
한단계 더 나아가기위한 좋은 리팩토링 주제입니다 👍

@LeeTaegyung LeeTaegyung added the 매운맛🔥 뒤는 없습니다. 그냥 필터 없이 말해주세요. 책임은 제가 집니다. label Aug 13, 2025
@addiescode-sj addiescode-sj self-requested a review August 14, 2025 05:02
Copy link
Collaborator

@addiescode-sj addiescode-sj left a comment

Choose a reason for hiding this comment

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

리팩토링 수고하셨습니다~! 🤩

Copy link
Collaborator

Choose a reason for hiding this comment

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

네, 만약 공용 폴더라면 app 바깥에 components 폴더를 만들어 유지해주시고,
특징 라우트그룹/ 페이지에서만 쓰이는 컴포넌트라면 이렇게 유지해주시면 될것같아요 :)

Comment on lines +17 to 24
const initialDataRef = useRef<TodoItemType[]>(data);
const [optimisticState, toggleOptimisticState] = useOptimistic<
TodoItemType[],
number
>(todoAll, (currentState, id) => {
>(initialDataRef.current, (currentState, id) => {
return currentState.map((todo) =>
todo.id === id ? { ...todo, isCompleted: !todo.isCompleted } : todo
);
Copy link
Collaborator

Choose a reason for hiding this comment

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

ㅎㅎ 제가 이 문제를 전체 코드베이스 훑어보며 좀 파보니까, 구조적으로 이런 문제가 있더라고요

  • page.tsx 컴포넌트의 <TodoAddForm/>에서 새로운 할 일을 추가할 때 createTodoItemAction이 실행됨
  • 이 액션에서 revalidateTag("todoList")가 호출되어 캐시가 무효화됨
  • Next.js는 캐시가 무효화되면 서버 컴포넌트를 다시 렌더링하므로, page.tsx의 getTodoList()가 다시 호출되어 새로운 데이터를 가져오고
  • 이 과정을 통해 새로운 data prop이 TodoListArea에 전달됨
  • 새로운 data prop이 전달되면 useOptimistic의 초기값이 재설정됨

결론적으로 이 문제는 서버 컴포넌트의 리렌더링 때문에 발생하는 현상입니다.
서버 상태와 클라이언트 상태를 분리하셔야할것같아요.

예를 들면

const TodoListArea = ({ data }: Props) => {
  // 클라이언트 상태로 서버 데이터 관리
  const [clientData, setClientData] = useState(data);
...

이런식으로 클라이언트 상태로 서버 데이터를 관리하는게 부가적으로 필요할것같고,
useOptimistic의 초기값을 clientData로 고정한다음
서버 데이터가 변경될 때 + API 응답 성공시 클라이언트 데이터를 업데이트하는 과정이 필연적일것같네요.

결과를 보면 이전에 태경님이 생각하셨던 방식과 비슷하게 풀어가는게 좋을 것 같아요.
다만, 이제 정확한 구조적 원인을 파악했으니 이 문제를 클라이언트 / 서버 상태를 분리하는 방식으로 해결해본다는 점이 이전에 드렸던 피드백과 살짝 달라지겠네요 :)

한번 참고해보시고, 더 좋은 방법이 있을지 고민해보세요!
한단계 더 나아가기위한 좋은 리팩토링 주제입니다 👍

@addiescode-sj
Copy link
Collaborator

질문에 대한 답변

멘토에게

  • PR 계속 클로즈 해서 죄송합니다.. 이상하게 계속 이전 커밋 히스토리가 포함되서 PR이 되어서 해결하느라고 계속 PR을 닫게 되었네요 ㅠㅠ...
  • 바로 미션10으로 넘어갈까 하다가... 궁금한게 있어서 코드리뷰 요청드립니다.
  • 이전 코드 리뷰에서 Empty 관련해서 route group과 private folder 언급을 해주셔서 파일 위치를 이동을 해보았는데요. 이러고 나니깐 넥스트는 폴더구조를 보통 어떻게 구성하는지 좀 헷갈리더라구요.
    검색을 해봤을 땐 프로젝트의 규모에 따라 다르겠지만 app 폴더와 형제 폴더로 components, api, ... 이런식으로 사용한다는 포스팅을 보기도 했었고, 큰 규모로 가면 feature 폴더를 추가하기도 한다고 봤었어요. 참고했던 포스트 입니다!
    그래서 강사님이 route group과 private folder를 언급 해주셨을 때 좀 혼란이 오기도 했었습니다! 말씀하셨던 뜻이 이렇게 진행을 하라고 하셨던게 맞는지도 궁금하기도 하구요 ㅎㅎ 곧 있으면 심화 프로젝트에 들어가기도 해서 혹시 넥스트로 진행을 하게 된다면 폴더 구조를 어떻게 짜는게 좋을지 혹시 참고할만한 글이 있을지 (구글링을 했을 땐 거의 첨부 드린 링크랑 내용이 거의 비슷했어요 ㅎㅎ) 궁금합니다!
  • 셀프 코드 리뷰를 통해 질문 이어가겠습니다.

폴더 구조를 짜는데 완벽한 정답은 없습니다 :)
route group의 경우 라우트 경로에서 무시할수있지만, 비슷한 기능끼리 묶어둘수있어 만약 비슷한 기능을 하는 페이지 단위 폴더가 있다면 묶어서 사용하기 괜찮고, private folder의 경우 app 디렉토리 아래에서 관리할때 라우팅 경로에 포함되지않게끔 할수있다는 정도라서,

만약

  • 특정 컴포넌트 폴더가 특정 페이지/ 라우트 그룹 아래에서만 쓰인다면 private folder 사용을 권장드리고
  • 공용이라면 app 폴더 바깥에서 자유롭게 구성해서 쓰시는걸 권장드립니다 :)

@addiescode-sj addiescode-sj merged commit 81df29e into codeit-bootcamp-frontend:Next-이태경 Aug 14, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

매운맛🔥 뒤는 없습니다. 그냥 필터 없이 말해주세요. 책임은 제가 집니다.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants