[widgets] PR 리뷰 기반 UI 동작 보정#34
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Code Review
This pull request simplifies the filter state management in ProblemBoard and introduces transition-based loading states using isPending to improve user experience. It also refactors the profile image preview logic in ProfileEditDialog from useMemo to useState and useEffect for better cleanup. The review feedback suggests combining isPending with isFetching from useSuspenseQuery to properly capture loading states during debounced search updates, and splitting the preview URL useEffect into two separate hooks to avoid redundant object URL creations.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| function ProblemBoardResults({ | ||
| keyword, | ||
| currentPage, | ||
| isPending, |
There was a problem hiding this comment.
useDebouncedValue 내부에서 전역 startTransition을 사용하여 디바운스된 검색어를 업데이트하기 때문에, ProblemBoard 컴포넌트의 useTransition에서 반환된 isPending은 검색어 변경으로 인한 재조회(fetching) 상태를 감지하지 못합니다.
이로 인해 사용자가 검색어를 입력하고 결과를 기다리는 동안 화면에 아무런 로딩 표시(불투명도 조절 및 하단 안내 메시지)가 나타나지 않아 UI가 멈춘 것처럼 보일 수 있습니다.
이를 해결하기 위해 useSuspenseQuery가 반환하는 isFetching 상태를 가져와 isPending과 함께 결합한 showPending 변수를 정의하여 사용하는 것을 권장합니다. 이렇게 하면 페이지네이션 전환뿐만 아니라 검색어 입력에 따른 데이터 조회 중에도 로딩 상태가 올바르게 표시됩니다.
추천하는 변경 사항:
useSuspenseQuery에서isFetching을 구조 분해 할당으로 가져오고,showPending변수를 정의합니다:
const { data: problemPage, isFetching } = useSuspenseQuery(
problemKeys.list(currentPage - 1, rowsPerPage, keyword),
);
const showPending = isPending || isFetching;- 아래의 불투명도 조절(line 84) 및 로딩 메시지 표시(line 213) 조건에서
isPending대신showPending을 사용하도록 수정합니다.
| className={`border-line bg-surface overflow-hidden rounded-lg border transition-opacity ${ | ||
| isPending ? "opacity-60" : "opacity-100" | ||
| }`} |
There was a problem hiding this comment.
검색어 입력 시에도 로딩 상태(불투명도 60%)가 올바르게 적용되도록 isPending 대신 showPending을 사용하도록 수정합니다. (상단 컴포넌트 정의 부분에서 showPending 정의가 필요합니다.)
| className={`border-line bg-surface overflow-hidden rounded-lg border transition-opacity ${ | |
| isPending ? "opacity-60" : "opacity-100" | |
| }`} | |
| className={`border-line bg-surface overflow-hidden rounded-lg border transition-opacity ${ | |
| showPending ? "opacity-60" : "opacity-100" | |
| }`} |
| {isPending ? ( | ||
| <p className="text-muted text-center text-xs" aria-live="polite"> | ||
| 문제 목록을 불러오는 중이에요. | ||
| </p> | ||
| ) : null} |
There was a problem hiding this comment.
검색어 입력 시에도 하단의 로딩 안내 메시지가 올바르게 표시되도록 isPending 대신 showPending을 사용하도록 수정합니다. (상단 컴포넌트 정의 부분에서 showPending 정의가 필요합니다.)
| {isPending ? ( | |
| <p className="text-muted text-center text-xs" aria-live="polite"> | |
| 문제 목록을 불러오는 중이에요. | |
| </p> | |
| ) : null} | |
| {showPending ? ( | |
| <p className="text-muted text-center text-xs" aria-live="polite"> | |
| 문제 목록을 불러오는 중이에요. | |
| </p> | |
| ) : null} |
| useEffect(() => { | ||
| if (!selectedFile) { | ||
| setPreviewUrl(profile.profileImageUrl); | ||
| return; | ||
| } | ||
|
|
||
| const objectUrl = URL.createObjectURL(selectedFile); | ||
| setPreviewUrl(objectUrl); | ||
|
|
||
| return () => { | ||
| if (selectedFile && previewUrl.startsWith("blob:")) { | ||
| URL.revokeObjectURL(previewUrl); | ||
| } | ||
| URL.revokeObjectURL(objectUrl); | ||
| }; | ||
| }, [previewUrl, selectedFile]); | ||
| }, [profile.profileImageUrl, selectedFile]); |
There was a problem hiding this comment.
현재 하나의 useEffect에서 profile.profileImageUrl과 selectedFile을 모두 감시하고 있어, 파일이 선택된 상태(selectedFile이 존재할 때)에서 profile.profileImageUrl이 변경되면 불필요하게 URL.createObjectURL이 다시 호출되어 새로운 Object URL을 생성하게 됩니다.
역할을 명확히 분리하여, 프로필 이미지 URL 동기화와 선택된 파일의 Object URL 생성/해제 로직을 두 개의 독립된 useEffect로 나누는 것을 권장합니다. 이렇게 하면 불필요한 부수 효과(Side Effect) 발생을 방지하고 코드의 가독성을 높일 수 있습니다.
useEffect(() => {
if (!selectedFile) {
setPreviewUrl(profile.profileImageUrl);
}
}, [profile.profileImageUrl, selectedFile]);
useEffect(() => {
if (!selectedFile) return;
const objectUrl = URL.createObjectURL(selectedFile);
setPreviewUrl(objectUrl);
return () => {
URL.revokeObjectURL(objectUrl);
};
}, [selectedFile]);
아까 실수로 comment 올라온 것들이 push가 되지 않아 다시 업로드 합니다