Create Week10 Mission1,2 - #85
Conversation
📝 WalkthroughWalkthrough
ChangesMission01: TMDB 영화 검색 앱
Practice01: useCallback 실습 앱
Practice02: useMemo 실습 앱
Sequence Diagram(s)sequenceDiagram
rect rgba(173, 216, 230, 0.5)
Note over HomePage,TMDB_API: Mission01 영화 검색 흐름
end
participant 사용자
participant HomePage
participant useFetch
participant axiosClient
participant TMDB_API
사용자->>HomePage: 검색어/필터 입력 후 제출
HomePage->>HomePage: draftFilters → submittedFilters 반영
HomePage->>useFetch: useFetch(submittedFilters)
useFetch->>axiosClient: GET /search/movie (params: filters)
axiosClient->>TMDB_API: HTTP GET + api_key 주입
TMDB_API-->>axiosClient: MovieResponse { results, page, total_pages }
axiosClient-->>useFetch: data
useFetch-->>HomePage: { data, isLoading, error }
HomePage->>HomePage: useMemo로 movies 가공
사용자->>HomePage: 영화 카드 클릭
HomePage->>HomePage: selectedMovie 설정
HomePage-->>사용자: MovieModal 표시 (평점/개봉일/인기도/IMDb 링크)
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 7
🧹 Nitpick comments (7)
Week10/jaewoni314/Practice02/src/UseMemoPage.tsx (1)
25-28: ⚡ Quick win
memo(TextInput)최적화가 현재는 거의 무효화됩니다.
onChange를 인라인으로 넘겨서 렌더마다 새 함수 참조가 생성됩니다.note변경 시에도TextInput이 다시 렌더링되므로, 핸들러를 고정해 주세요.수정 예시
-import { useMemo, useState } from "react"; +import { useCallback, useMemo, useState } from "react"; @@ + const handleNumberChange = useCallback( + (e: React.ChangeEvent<HTMLInputElement>) => { + setNumber(e.target.value); + }, + [] + ); @@ <TextInput text={number} - onChange={(e) => setNumber(e.target.value)} + onChange={handleNumberChange} />🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@Week10/jaewoni314/Practice02/src/UseMemoPage.tsx` around lines 25 - 28, The inline onChange handler passed to the memo-wrapped TextInput component creates a new function reference on every render, defeating the memo optimization. Extract the onChange handler from the TextInput component and wrap it with the useCallback hook to maintain a stable function reference across renders. The handler should remain the same logic that sets the number state, but now it will be memoized so TextInput will only re-render when its props actually change, not when the parent component re-renders due to other state changes like note updates.Week10/jaewoni314/Practice02/tsconfig.app.json (1)
2-22: ⚡ Quick win타입 안전성과 번들러 호환성을 위해
strict/isolatedModules를 활성화해 주세요.현재 설정은 타입 검사 강도가 낮고(
strict미사용), 번들러 단일 파일 트랜스파일 제약(isolatedModules)이 빠져 있어 런타임/빌드 불일치 리스크를 키웁니다.제안 패치
{ "compilerOptions": { + "strict": true, + "isolatedModules": true, "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", "target": "es2023", "lib": ["ES2023", "DOM"],🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@Week10/jaewoni314/Practice02/tsconfig.app.json` around lines 2 - 22, The tsconfig.app.json file is missing important compiler options for type safety and bundler compatibility. Add two new boolean options to the compilerOptions object: set `strict` to true to enable stricter type checking (which includes multiple strict flags), and set `isolatedModules` to true to ensure each file can be safely transpiled independently by bundlers. These additions will help prevent runtime and build time mismatches while improving overall type safety.Week10/jaewoni314/Mission01/src/components/Input.tsx (1)
8-10: ⚡ Quick win렌더링 디버그 로그는 제거하는 것이 좋습니다.
Line 9의
console.log는 사용자 기능에는 기여하지 않고 콘솔 노이즈만 늘립니다. PR 목적(최적화/정리)과도 맞지 않아 제거를 권장합니다.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@Week10/jaewoni314/Mission01/src/components/Input.tsx` around lines 8 - 10, Remove the console.log statement from the Input function that logs "Input 렌더링". This debug logging statement serves no functional purpose and contributes only console noise, which should be cleaned up as part of the PR's optimization and cleanup efforts.Week10/jaewoni314/Mission01/src/components/LanguageSelector.tsx (1)
11-13: ⚡ Quick win불필요한 렌더링 로그를 제거해주세요.
Line 12의 로그는 운영 시점에 불필요한 출력만 발생시킵니다. 유지보수성과 디버깅 신뢰도를 위해 제거를 권장합니다.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@Week10/jaewoni314/Mission01/src/components/LanguageSelector.tsx` around lines 11 - 13, The LanguageSelector function contains an unnecessary console.log statement that logs rendering information. This debug logging should be removed as it generates unnecessary console output in production and reduces code maintainability. Delete the console.log("LanguageSelector 렌더링") line from inside the LanguageSelector function body.Week10/jaewoni314/Mission01/src/components/MovieCard.tsx (1)
12-12: ⚡ Quick win렌더 경로의 디버그 로그는 제거해 주세요.
Line 12의
console.log는 카드 개수만큼 반복 실행되어 개발/운영 콘솔 노이즈와 렌더링 오버헤드를 만듭니다. 필요 시 dev 플래그 기반 로깅으로 분리하는 편이 안전합니다.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@Week10/jaewoni314/Mission01/src/components/MovieCard.tsx` at line 12, The console.log statement in the MovieCard component renders repeatedly for each card, creating console noise and unnecessary performance overhead. Remove the console.log("MovieCard 렌더링:", movie.title) statement from line 12 in the MovieCard component's render path. If debugging is necessary in the future, wrap any logging with a development environment flag check instead of leaving it in the production render path.Week10/jaewoni314/Mission01/src/pages/HomePage.tsx (1)
56-59: ⚡ Quick win
useMemo내부 디버그 로그는 정리해 주세요.Line 57 로그는 검색/응답 갱신 때마다 실행되어 콘솔 노이즈를 유발합니다. 현재 로직은
data?.results ?? []라 계산 비용이 낮아, 로그 없이 유지하는 쪽이 더 깔끔합니다.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@Week10/jaewoni314/Mission01/src/pages/HomePage.tsx` around lines 56 - 59, Remove the debug console.log statement from inside the useMemo hook in the movies variable declaration in HomePage.tsx. The console.log("영화 리스트 가공") line is causing unnecessary console noise every time the search or response is refreshed, and since the underlying logic (data?.results ?? []) has low computational cost, the log statement is not needed and should be deleted while keeping the return statement intact.Week10/jaewoni314/Practice01/src/UseCallbackPage.tsx (1)
15-17: ⚡ Quick win
handleTextChange도 메모이제이션하는 편이 이 실습 목적에 더 맞습니다.지금 구조에서는
count만 바뀌어도TextInput의onChange참조가 바뀌어 memo 이점이 줄어듭니다.리팩터 예시
- const handleTextChange = (e: ChangeEvent<HTMLInputElement>) => { + const handleTextChange = useCallback((e: ChangeEvent<HTMLInputElement>) => { setText(e.target.value); - }; + }, []);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@Week10/jaewoni314/Practice01/src/UseCallbackPage.tsx` around lines 15 - 17, The handleTextChange function needs to be memoized using the useCallback hook to preserve referential equality across re-renders. Without memoization, the function is recreated on every render (including when count changes), which causes the TextInput component to lose the benefits of React.memo and re-render unnecessarily. Wrap the handleTextChange function definition with useCallback, ensuring setText is included in the dependency array, so that the function reference remains stable unless the dependencies change.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@Week10/jaewoni314/Mission01/index.html`:
- Line 2: The html element on line 2 has lang="en" but the UI content is in
Korean, which causes issues with assistive devices and language recognition
accuracy. Change the lang attribute in the opening html tag from "en" to "ko" to
correctly match the document's primary language.
In `@Week10/jaewoni314/Mission01/src/components/MovieFilter.tsx`:
- Around line 31-35: The label elements in the MovieFilter component are not
properly associated with their corresponding form controls, breaking
accessibility for assistive devices. Add htmlFor attributes to each label
element (lines 31-33 for the title field and around line 50-52 for the language
field) that reference unique id values. Add id prop support to the Input
component (used in the query field) and the LanguageSelector component (used for
the language field), which may also require adding id prop support to the
internal SelectBox component. Update the TypeScript prop interfaces (InputProps,
LanguageSelectorProps, and SelectBoxProps) to accept an optional id?: string
property so these components can receive and apply the id attribute to their
underlying DOM elements.
In `@Week10/jaewoni314/Mission01/src/components/MovieModal.tsx`:
- Around line 16-25: The onKeyDown handler on the modal container div only works
when the div has focus, which is not guaranteed. To fix this, add a useEffect
hook in the MovieModal component that registers a global keydown event listener
to handle the Escape key press. The listener should call onClose when the Escape
key is detected, ensuring the modal can be closed via keyboard regardless of
which element currently has focus. Don't forget to clean up the event listener
in the useEffect cleanup function to prevent memory leaks.
In `@Week10/jaewoni314/Mission01/src/hooks/useFetch.ts`:
- Around line 17-44: There is a race condition in the fetchMovies function where
the finally block at line 42-43 executes for previous requests and can set
isLoading to false even when a new request is still in progress. To fix this,
store a reference to the current AbortController and before updating the loading
state in the finally block, check whether the current request's controller
signal has been aborted. Only update isLoading to false if the current request's
signal has not been aborted, ensuring that stale requests do not override the
loading state of active requests.
In `@Week10/jaewoni314/Practice01/src/components/TextInput.tsx`:
- Line 5: The onChange property definition on line 5 of TextInput.tsx uses
React.ChangeEvent<HTMLInputElement> but the React namespace is not imported. To
fix this, add ChangeEvent to the import statement from 'react' (alongside the
existing memo import) and then update the onChange property type to use
ChangeEvent<HTMLInputElement> directly without the React prefix.
In `@Week10/jaewoni314/Practice01/src/UseCallbackPage.tsx`:
- Line 15: The handleTextChange function uses the React.ChangeEvent type which
requires React to be imported as a namespace. Add `import React from 'react';`
at the top of the UseCallbackPage.tsx file to provide access to the React
namespace. Apply the same fix to the TextInput.tsx file where the identical
pattern appears at Line 5, ensuring both files have the necessary React
namespace import for TypeScript to resolve the type references correctly.
In `@Week10/jaewoni314/Practice02/src/utils/math.ts`:
- Around line 1-6: The isPrime function does not validate that the input is an
integer, allowing decimal numbers like 2.5 to incorrectly return true. Add input
validation at the beginning of the isPrime function to check if the input is an
integer (you can use Number.isInteger() or check if num % 1 !== 0), and return
false immediately if the input is not an integer. This ensures that only valid
integers are evaluated for primality.
---
Nitpick comments:
In `@Week10/jaewoni314/Mission01/src/components/Input.tsx`:
- Around line 8-10: Remove the console.log statement from the Input function
that logs "Input 렌더링". This debug logging statement serves no functional purpose
and contributes only console noise, which should be cleaned up as part of the
PR's optimization and cleanup efforts.
In `@Week10/jaewoni314/Mission01/src/components/LanguageSelector.tsx`:
- Around line 11-13: The LanguageSelector function contains an unnecessary
console.log statement that logs rendering information. This debug logging should
be removed as it generates unnecessary console output in production and reduces
code maintainability. Delete the console.log("LanguageSelector 렌더링") line from
inside the LanguageSelector function body.
In `@Week10/jaewoni314/Mission01/src/components/MovieCard.tsx`:
- Line 12: The console.log statement in the MovieCard component renders
repeatedly for each card, creating console noise and unnecessary performance
overhead. Remove the console.log("MovieCard 렌더링:", movie.title) statement from
line 12 in the MovieCard component's render path. If debugging is necessary in
the future, wrap any logging with a development environment flag check instead
of leaving it in the production render path.
In `@Week10/jaewoni314/Mission01/src/pages/HomePage.tsx`:
- Around line 56-59: Remove the debug console.log statement from inside the
useMemo hook in the movies variable declaration in HomePage.tsx. The
console.log("영화 리스트 가공") line is causing unnecessary console noise every time
the search or response is refreshed, and since the underlying logic
(data?.results ?? []) has low computational cost, the log statement is not
needed and should be deleted while keeping the return statement intact.
In `@Week10/jaewoni314/Practice01/src/UseCallbackPage.tsx`:
- Around line 15-17: The handleTextChange function needs to be memoized using
the useCallback hook to preserve referential equality across re-renders. Without
memoization, the function is recreated on every render (including when count
changes), which causes the TextInput component to lose the benefits of
React.memo and re-render unnecessarily. Wrap the handleTextChange function
definition with useCallback, ensuring setText is included in the dependency
array, so that the function reference remains stable unless the dependencies
change.
In `@Week10/jaewoni314/Practice02/src/UseMemoPage.tsx`:
- Around line 25-28: The inline onChange handler passed to the memo-wrapped
TextInput component creates a new function reference on every render, defeating
the memo optimization. Extract the onChange handler from the TextInput component
and wrap it with the useCallback hook to maintain a stable function reference
across renders. The handler should remain the same logic that sets the number
state, but now it will be memoized so TextInput will only re-render when its
props actually change, not when the parent component re-renders due to other
state changes like note updates.
In `@Week10/jaewoni314/Practice02/tsconfig.app.json`:
- Around line 2-22: The tsconfig.app.json file is missing important compiler
options for type safety and bundler compatibility. Add two new boolean options
to the compilerOptions object: set `strict` to true to enable stricter type
checking (which includes multiple strict flags), and set `isolatedModules` to
true to ensure each file can be safely transpiled independently by bundlers.
These additions will help prevent runtime and build time mismatches while
improving overall type safety.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: c6e65ff5-5f7a-49ce-961e-d81eaa2c1d4f
⛔ Files ignored due to path filters (18)
Week10/jaewoni314/Mission01/package-lock.jsonis excluded by!**/package-lock.jsonWeek10/jaewoni314/Mission01/pnpm-lock.yamlis excluded by!**/pnpm-lock.yamlWeek10/jaewoni314/Mission01/public/favicon.svgis excluded by!**/*.svgWeek10/jaewoni314/Mission01/public/icons.svgis excluded by!**/*.svgWeek10/jaewoni314/Practice01/package-lock.jsonis excluded by!**/package-lock.jsonWeek10/jaewoni314/Practice01/pnpm-lock.yamlis excluded by!**/pnpm-lock.yamlWeek10/jaewoni314/Practice01/public/favicon.svgis excluded by!**/*.svgWeek10/jaewoni314/Practice01/public/icons.svgis excluded by!**/*.svgWeek10/jaewoni314/Practice01/src/assets/hero.pngis excluded by!**/*.pngWeek10/jaewoni314/Practice01/src/assets/react.svgis excluded by!**/*.svgWeek10/jaewoni314/Practice01/src/assets/vite.svgis excluded by!**/*.svgWeek10/jaewoni314/Practice02/package-lock.jsonis excluded by!**/package-lock.jsonWeek10/jaewoni314/Practice02/pnpm-lock.yamlis excluded by!**/pnpm-lock.yamlWeek10/jaewoni314/Practice02/public/favicon.svgis excluded by!**/*.svgWeek10/jaewoni314/Practice02/public/icons.svgis excluded by!**/*.svgWeek10/jaewoni314/Practice02/src/assets/hero.pngis excluded by!**/*.pngWeek10/jaewoni314/Practice02/src/assets/react.svgis excluded by!**/*.svgWeek10/jaewoni314/Practice02/src/assets/vite.svgis excluded by!**/*.svg
📒 Files selected for processing (58)
Week10/jaewoni314/Mission01/.gitignoreWeek10/jaewoni314/Mission01/README.mdWeek10/jaewoni314/Mission01/eslint.config.jsWeek10/jaewoni314/Mission01/index.htmlWeek10/jaewoni314/Mission01/package.jsonWeek10/jaewoni314/Mission01/postcss.config.jsWeek10/jaewoni314/Mission01/src/App.tsxWeek10/jaewoni314/Mission01/src/api/axiosClient.tsWeek10/jaewoni314/Mission01/src/components/Input.tsxWeek10/jaewoni314/Mission01/src/components/LanguageSelector.tsxWeek10/jaewoni314/Mission01/src/components/MovieCard.tsxWeek10/jaewoni314/Mission01/src/components/MovieFilter.tsxWeek10/jaewoni314/Mission01/src/components/MovieList.tsxWeek10/jaewoni314/Mission01/src/components/MovieModal.tsxWeek10/jaewoni314/Mission01/src/components/SelectBox.tsxWeek10/jaewoni314/Mission01/src/constants/movie.tsWeek10/jaewoni314/Mission01/src/hooks/useFetch.tsWeek10/jaewoni314/Mission01/src/index.cssWeek10/jaewoni314/Mission01/src/main.tsxWeek10/jaewoni314/Mission01/src/pages/HomePage.tsxWeek10/jaewoni314/Mission01/src/types/movie.tsWeek10/jaewoni314/Mission01/src/utils/movie.tsWeek10/jaewoni314/Mission01/tsconfig.app.jsonWeek10/jaewoni314/Mission01/tsconfig.jsonWeek10/jaewoni314/Mission01/tsconfig.node.jsonWeek10/jaewoni314/Mission01/vite.config.tsWeek10/jaewoni314/Practice01/.gitignoreWeek10/jaewoni314/Practice01/README.mdWeek10/jaewoni314/Practice01/eslint.config.jsWeek10/jaewoni314/Practice01/index.htmlWeek10/jaewoni314/Practice01/package.jsonWeek10/jaewoni314/Practice01/src/App.cssWeek10/jaewoni314/Practice01/src/App.tsxWeek10/jaewoni314/Practice01/src/UseCallbackPage.tsxWeek10/jaewoni314/Practice01/src/components/CountButton.tsxWeek10/jaewoni314/Practice01/src/components/TextInput.tsxWeek10/jaewoni314/Practice01/src/index.cssWeek10/jaewoni314/Practice01/src/main.tsxWeek10/jaewoni314/Practice01/tsconfig.app.jsonWeek10/jaewoni314/Practice01/tsconfig.jsonWeek10/jaewoni314/Practice01/tsconfig.node.jsonWeek10/jaewoni314/Practice01/vite.config.tsWeek10/jaewoni314/Practice02/.gitignoreWeek10/jaewoni314/Practice02/README.mdWeek10/jaewoni314/Practice02/eslint.config.jsWeek10/jaewoni314/Practice02/index.htmlWeek10/jaewoni314/Practice02/package.jsonWeek10/jaewoni314/Practice02/src/App.cssWeek10/jaewoni314/Practice02/src/App.tsxWeek10/jaewoni314/Practice02/src/UseMemoPage.tsxWeek10/jaewoni314/Practice02/src/components/TextInput.tsxWeek10/jaewoni314/Practice02/src/index.cssWeek10/jaewoni314/Practice02/src/main.tsxWeek10/jaewoni314/Practice02/src/utils/math.tsWeek10/jaewoni314/Practice02/tsconfig.app.jsonWeek10/jaewoni314/Practice02/tsconfig.jsonWeek10/jaewoni314/Practice02/tsconfig.node.jsonWeek10/jaewoni314/Practice02/vite.config.ts
| @@ -0,0 +1,13 @@ | |||
| <!doctype html> | |||
| <html lang="en"> | |||
There was a problem hiding this comment.
문서 lang 값이 실제 UI 언어와 불일치합니다.
현재 화면 텍스트가 한국어 중심인데 lang="en"으로 선언되어 보조기기 발음과 언어 인식 정확도가 떨어질 수 있습니다. 기본 언어를 ko로 맞춰주세요.
수정 예시
-<html lang="en">
+<html lang="ko">📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <html lang="en"> | |
| <html lang="ko"> |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@Week10/jaewoni314/Mission01/index.html` at line 2, The html element on line 2
has lang="en" but the UI content is in Korean, which causes issues with
assistive devices and language recognition accuracy. Change the lang attribute
in the opening html tag from "en" to "ko" to correctly match the document's
primary language.
| <label className="mb-1 block text-sm font-semibold text-gray-700"> | ||
| 영화 제목 | ||
| </label> | ||
| <Input value={query} onChange={onQueryChange} /> | ||
| </div> |
There was a problem hiding this comment.
라벨과 폼 컨트롤 연결이 빠져 접근성이 깨집니다.
Line 31-33, 50-52 라벨이 실제 입력 요소와 연결되지 않아 보조기기에서 필드 의미 전달이 약해집니다. htmlFor + id를 연결하고, Input/LanguageSelector(내부 SelectBox)까지 id prop 계약을 확장해주세요.
수정 방향 예시
-<label className="mb-1 block text-sm font-semibold text-gray-700">
+<label htmlFor="movie-query" className="mb-1 block text-sm font-semibold text-gray-700">
영화 제목
</label>
-<Input value={query} onChange={onQueryChange} />
+<Input id="movie-query" value={query} onChange={onQueryChange} />
-<label className="mb-1 block text-sm font-semibold text-gray-700">
+<label htmlFor="movie-language" className="mb-1 block text-sm font-semibold text-gray-700">
언어
</label>
-<LanguageSelector language={language} onChange={onLanguageChange} />
+<LanguageSelector id="movie-language" language={language} onChange={onLanguageChange} />(연동을 위해 InputProps, LanguageSelectorProps, SelectBoxProps에 id?: string 추가 필요)
Also applies to: 49-54
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@Week10/jaewoni314/Mission01/src/components/MovieFilter.tsx` around lines 31 -
35, The label elements in the MovieFilter component are not properly associated
with their corresponding form controls, breaking accessibility for assistive
devices. Add htmlFor attributes to each label element (lines 31-33 for the title
field and around line 50-52 for the language field) that reference unique id
values. Add id prop support to the Input component (used in the query field) and
the LanguageSelector component (used for the language field), which may also
require adding id prop support to the internal SelectBox component. Update the
TypeScript prop interfaces (InputProps, LanguageSelectorProps, and
SelectBoxProps) to accept an optional id?: string property so these components
can receive and apply the id attribute to their underlying DOM elements.
| <div | ||
| onClick={onClose} | ||
| onKeyDown={(e) => { | ||
| if (e.key === "Escape") onClose(); | ||
| }} | ||
| role="dialog" | ||
| aria-modal="true" | ||
| aria-label={`${movie.title} 상세 정보`} | ||
| tabIndex={-1} | ||
| className="fixed inset-0 z-50 flex items-center justify-center bg-black/70 p-4" |
There was a problem hiding this comment.
Escape 닫기 동작이 포커스 상태에 따라 실패할 수 있습니다.
Line 18-20은 모달 컨테이너가 포커스를 가져야만 동작합니다. 현재 초기 포커스 보장이 없어 키보드 사용 시 Escape 닫기가 불안정합니다. useEffect로 전역 keydown을 등록하거나, 모달 오픈 시 명시적으로 포커스를 이동시켜 주세요.
수정 예시
+import { useEffect, useRef } from "react";
...
function MovieModal({ movie, onClose }: MovieModalProps) {
+ const dialogRef = useRef<HTMLDivElement>(null);
+
+ useEffect(() => {
+ dialogRef.current?.focus();
+ const onKeyDown = (e: KeyboardEvent) => {
+ if (e.key === "Escape") onClose();
+ };
+ window.addEventListener("keydown", onKeyDown);
+ return () => window.removeEventListener("keydown", onKeyDown);
+ }, [onClose]);
+
return (
<div
+ ref={dialogRef}
onClick={onClose}
- onKeyDown={(e) => {
- if (e.key === "Escape") onClose();
- }}
role="dialog"
aria-modal="true"📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <div | |
| onClick={onClose} | |
| onKeyDown={(e) => { | |
| if (e.key === "Escape") onClose(); | |
| }} | |
| role="dialog" | |
| aria-modal="true" | |
| aria-label={`${movie.title} 상세 정보`} | |
| tabIndex={-1} | |
| className="fixed inset-0 z-50 flex items-center justify-center bg-black/70 p-4" | |
| import { useEffect, useRef } from "react"; | |
| function MovieModal({ movie, onClose }: MovieModalProps) { | |
| const dialogRef = useRef<HTMLDivElement>(null); | |
| useEffect(() => { | |
| dialogRef.current?.focus(); | |
| const onKeyDown = (e: KeyboardEvent) => { | |
| if (e.key === "Escape") onClose(); | |
| }; | |
| window.addEventListener("keydown", onKeyDown); | |
| return () => window.removeEventListener("keydown", onKeyDown); | |
| }, [onClose]); | |
| return ( | |
| <div | |
| ref={dialogRef} | |
| onClick={onClose} | |
| role="dialog" | |
| aria-modal="true" | |
| aria-label={`${movie.title} 상세 정보`} | |
| tabIndex={-1} | |
| className="fixed inset-0 z-50 flex items-center justify-center bg-black/70 p-4" | |
| > | |
| {/* ... rest of component */} | |
| </div> | |
| ); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@Week10/jaewoni314/Mission01/src/components/MovieModal.tsx` around lines 16 -
25, The onKeyDown handler on the modal container div only works when the div has
focus, which is not guaranteed. To fix this, add a useEffect hook in the
MovieModal component that registers a global keydown event listener to handle
the Escape key press. The listener should call onClose when the Escape key is
detected, ensuring the modal can be closed via keyboard regardless of which
element currently has focus. Don't forget to clean up the event listener in the
useEffect cleanup function to prevent memory leaks.
| useEffect(() => { | ||
| if (!filters.query) { | ||
| setData(null); | ||
| setError(null); | ||
| return; | ||
| } | ||
|
|
||
| const controller = new AbortController(); | ||
|
|
||
| const fetchMovies = async () => { | ||
| setIsLoading(true); | ||
| setError(null); | ||
|
|
||
| try { | ||
| const response = await axiosClient.get<MovieResponse>( | ||
| "/search/movie", | ||
| { | ||
| params: filters, | ||
| signal: controller.signal, | ||
| } | ||
| ); | ||
| setData(response.data); | ||
| } catch (err) { | ||
| if (axios.isCancel(err)) return; | ||
| setError("영화 정보를 불러오는데 실패했습니다."); | ||
| } finally { | ||
| setIsLoading(false); | ||
| } |
There was a problem hiding this comment.
로딩 상태 갱신에 경쟁 조건이 있습니다.
Line 42-43의 finally가 이전 요청에서도 실행되어, 새 요청이 진행 중이어도 isLoading이 false로 덮일 수 있습니다. effect 생명주기 기준으로 현재 요청인지 확인한 뒤 상태를 갱신하세요.
수정 예시
useEffect(() => {
+ let isCurrent = true;
if (!filters.query) {
setData(null);
setError(null);
return;
}
const controller = new AbortController();
const fetchMovies = async () => {
setIsLoading(true);
setError(null);
try {
const response = await axiosClient.get<MovieResponse>(
"/search/movie",
{
params: filters,
signal: controller.signal,
}
);
- setData(response.data);
+ if (isCurrent) setData(response.data);
} catch (err) {
if (axios.isCancel(err)) return;
- setError("영화 정보를 불러오는데 실패했습니다.");
+ if (isCurrent) setError("영화 정보를 불러오는데 실패했습니다.");
} finally {
- setIsLoading(false);
+ if (isCurrent) setIsLoading(false);
}
};
fetchMovies();
- return () => controller.abort();
+ return () => {
+ isCurrent = false;
+ controller.abort();
+ };
}, [filters.query, filters.include_adult, filters.language]);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| useEffect(() => { | |
| if (!filters.query) { | |
| setData(null); | |
| setError(null); | |
| return; | |
| } | |
| const controller = new AbortController(); | |
| const fetchMovies = async () => { | |
| setIsLoading(true); | |
| setError(null); | |
| try { | |
| const response = await axiosClient.get<MovieResponse>( | |
| "/search/movie", | |
| { | |
| params: filters, | |
| signal: controller.signal, | |
| } | |
| ); | |
| setData(response.data); | |
| } catch (err) { | |
| if (axios.isCancel(err)) return; | |
| setError("영화 정보를 불러오는데 실패했습니다."); | |
| } finally { | |
| setIsLoading(false); | |
| } | |
| useEffect(() => { | |
| let isCurrent = true; | |
| if (!filters.query) { | |
| setData(null); | |
| setError(null); | |
| return; | |
| } | |
| const controller = new AbortController(); | |
| const fetchMovies = async () => { | |
| setIsLoading(true); | |
| setError(null); | |
| try { | |
| const response = await axiosClient.get<MovieResponse>( | |
| "/search/movie", | |
| { | |
| params: filters, | |
| signal: controller.signal, | |
| } | |
| ); | |
| if (isCurrent) setData(response.data); | |
| } catch (err) { | |
| if (axios.isCancel(err)) return; | |
| if (isCurrent) setError("영화 정보를 불러오는데 실패했습니다."); | |
| } finally { | |
| if (isCurrent) setIsLoading(false); | |
| } | |
| }; | |
| fetchMovies(); | |
| return () => { | |
| isCurrent = false; | |
| controller.abort(); | |
| }; | |
| }, [filters.query, filters.include_adult, filters.language]); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@Week10/jaewoni314/Mission01/src/hooks/useFetch.ts` around lines 17 - 44,
There is a race condition in the fetchMovies function where the finally block at
line 42-43 executes for previous requests and can set isLoading to false even
when a new request is still in progress. To fix this, store a reference to the
current AbortController and before updating the loading state in the finally
block, check whether the current request's controller signal has been aborted.
Only update isLoading to false if the current request's signal has not been
aborted, ensuring that stale requests do not override the loading state of
active requests.
|
|
||
| interface TextInputProps { | ||
| text: string; | ||
| onChange: (e: React.ChangeEvent<HTMLInputElement>) => void; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# React.ChangeEvent 사용 위치와 import 상태를 재확인 (read-only)
rg -n 'React\.ChangeEvent<.*>' Week10/jaewoni314/Practice01/src
rg -n 'import .*ChangeEvent|import .*React' Week10/jaewoni314/Practice01/src/components/TextInput.tsx Week10/jaewoni314/Practice01/src/UseCallbackPage.tsxRepository: HSU-Makeus-Challenge-10th/Web
Length of output: 324
🏁 Script executed:
cat -n Week10/jaewoni314/Practice01/src/components/TextInput.tsxRepository: HSU-Makeus-Challenge-10th/Web
Length of output: 509
React.ChangeEvent 타입 import 누락으로 인한 TypeScript 오류 발생
TextInput.tsx 파일에서 React 네임스페이스가 import되지 않은 상태에서 Line 5에서 React.ChangeEvent<HTMLInputElement>를 사용하고 있습니다. 현재 파일은 memo만 import 중이기 때문에 TypeScript 컴파일 시 오류가 발생합니다.
다음과 같이 ChangeEvent 타입을 직접 import하고 사용하도록 수정하세요:
수정 예시
-import { memo } from "react";
+import { memo, type ChangeEvent } from "react";
@@
- onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
+ onChange: (e: ChangeEvent<HTMLInputElement>) => void;🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@Week10/jaewoni314/Practice01/src/components/TextInput.tsx` at line 5, The
onChange property definition on line 5 of TextInput.tsx uses
React.ChangeEvent<HTMLInputElement> but the React namespace is not imported. To
fix this, add ChangeEvent to the import statement from 'react' (alongside the
existing memo import) and then update the onChange property type to use
ChangeEvent<HTMLInputElement> directly without the React prefix.
| setCount((prev) => prev + 1); | ||
| }, []); | ||
|
|
||
| const handleTextChange = (e: React.ChangeEvent<HTMLInputElement>) => { |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# React 타입 네임스페이스 사용 대비 import 누락 여부 확인 (read-only)
rg -n 'React\.ChangeEvent<.*>' Week10/jaewoni314/Practice01/src
rg -n 'import .*ChangeEvent|import .*React' Week10/jaewoni314/Practice01/src/UseCallbackPage.tsx Week10/jaewoni314/Practice01/src/components/TextInput.tsx
fd -i 'tsconfig*.json' Week10/jaewoni314/Practice01 -x sh -c 'echo "== $1 =="; sed -n "1,160p" "$1"' sh {}Repository: HSU-Makeus-Challenge-10th/Web
Length of output: 492
🏁 Script executed:
head -20 Week10/jaewoni314/Practice01/src/UseCallbackPage.tsxRepository: HSU-Makeus-Challenge-10th/Web
Length of output: 592
🏁 Script executed:
head -15 Week10/jaewoni314/Practice01/src/components/TextInput.tsxRepository: HSU-Makeus-Challenge-10th/Web
Length of output: 411
🏁 Script executed:
cat Week10/jaewoni314/Practice01/tsconfig.app.jsonRepository: HSU-Makeus-Challenge-10th/Web
Length of output: 690
🏁 Script executed:
# TextInput.tsx도 동일한 이슈가 있는지 확인, 그리고 실제 React 타입 사용 사례 더 확인
rg -n 'React\.' Week10/jaewoni314/Practice01/src --type tsx --type tsRepository: HSU-Makeus-Challenge-10th/Web
Length of output: 101
🏁 Script executed:
# package.json 확인하여 빌드/타입 체크 스크립트 있는지 확인
cat Week10/jaewoni314/Practice01/package.jsonRepository: HSU-Makeus-Challenge-10th/Web
Length of output: 785
React.ChangeEvent 타입을 사용하려면 React 네임스페이스 import가 필요합니다.
"jsx": "react-jsx" 설정으로 인해 JSX 변환에는 React import가 불필요하지만, React.ChangeEvent 같은 타입 참조는 React 모듈의 네임스페이스 접근이 필요합니다. 현재 코드는 TypeScript 컴파일 오류를 발생시킵니다.
수정 예시
-import { useCallback, useState } from "react";
+import { useCallback, useState, type ChangeEvent } from "react";
@@
- const handleTextChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+ const handleTextChange = (e: ChangeEvent<HTMLInputElement>) => {참고: Week10/jaewoni314/Practice01/src/components/TextInput.tsx Line 5에서도 동일한 패턴이 사용되고 있습니다.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@Week10/jaewoni314/Practice01/src/UseCallbackPage.tsx` at line 15, The
handleTextChange function uses the React.ChangeEvent type which requires React
to be imported as a namespace. Add `import React from 'react';` at the top of
the UseCallbackPage.tsx file to provide access to the React namespace. Apply the
same fix to the TextInput.tsx file where the identical pattern appears at Line
5, ensuring both files have the necessary React namespace import for TypeScript
to resolve the type references correctly.
| export function isPrime(num: number): boolean { | ||
| if (num < 2) return false; | ||
| for (let i = 2; i * i <= num; i++) { | ||
| if (num % i === 0) return false; | ||
| } | ||
| return true; |
There was a problem hiding this comment.
isPrime가 소수가 아닌 실수를 소수로 판정할 수 있습니다.
현재 구현은 2.5 같은 실수에서 true를 반환합니다. 정수만 소수 판정을 하도록 입력 검증을 추가해 주세요.
수정 예시
export function isPrime(num: number): boolean {
+ if (!Number.isInteger(num)) return false;
if (num < 2) return false;
for (let i = 2; i * i <= num; i++) {
if (num % i === 0) return false;
}
return true;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export function isPrime(num: number): boolean { | |
| if (num < 2) return false; | |
| for (let i = 2; i * i <= num; i++) { | |
| if (num % i === 0) return false; | |
| } | |
| return true; | |
| export function isPrime(num: number): boolean { | |
| if (!Number.isInteger(num)) return false; | |
| if (num < 2) return false; | |
| for (let i = 2; i * i <= num; i++) { | |
| if (num % i === 0) return false; | |
| } | |
| return true; | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@Week10/jaewoni314/Practice02/src/utils/math.ts` around lines 1 - 6, The
isPrime function does not validate that the input is an integer, allowing
decimal numbers like 2.5 to incorrectly return true. Add input validation at the
beginning of the isPrime function to check if the input is an integer (you can
use Number.isInteger() or check if num % 1 !== 0), and return false immediately
if the input is not an integer. This ensures that only valid integers are
evaluated for primality.
📝 미션 번호
10주차 Misson 1,2
📋 구현 사항
📎 스크린샷
https://umc-final-vercel.vercel.app/
✅ 체크리스트
Summary by CodeRabbit
릴리스 노트
새로운 기능
문서
구성