Create Week10 Mission1, 2 - #84
Conversation
📝 WalkthroughWalkthroughReact/Vite 기반 두 신규 프로젝트가 추가됨. Changesmission00: useCallback & useMemo 데모 앱
mission0102: TMDB 영화 검색 앱
Sequence Diagram(s)sequenceDiagram
actor User
participant MovieFilter
participant HomePage
participant useFetch
participant axiosClient
participant TMDB_API
participant MovieList
participant Modal
User->>MovieFilter: 검색어/성인포함/언어 입력 후 검색
MovieFilter->>HomePage: onChange(MovieFilters)
HomePage->>useFetch: useFetch("/search/movie", { params: filters })
useFetch->>axiosClient: axiosClient.get(url, option)
axiosClient->>TMDB_API: GET /search/movie (Bearer token)
TMDB_API-->>axiosClient: MovieResponse JSON
axiosClient-->>useFetch: data
useFetch-->>HomePage: { data, isLoading, error }
HomePage->>MovieList: movies={data.results}
MovieList-->>User: 영화 그리드 렌더링
User->>User: 영화 카드 클릭
HomePage->>Modal: isModalOpen=true, selectedMovie 설정
Modal-->>User: 상세 정보(이미지, 평점, 개봉일, 줄거리, IMDb 링크) 표시
User->>Modal: Escape 키 또는 배경 클릭
Modal->>HomePage: onClose()
HomePage->>HomePage: isModalOpen=false
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ 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: 9
🧹 Nitpick comments (5)
Week10/endl24/mission00/src/useMemo/utils/math.ts (1)
3-4: ⚡ Quick win소수 판별 루프 범위를
sqrt(num)까지 줄이세요.Line 3-4의 전수 검사(
i < num)는 비용이 큽니다.i * i <= num로 줄이면 동일 정확도로 연산량을 크게 줄일 수 있습니다.제안 diff
export const isPrime = (num: number) => { if (num < 2) return false; - for (let i = 2; i < num; i++) { + 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/endl24/mission00/src/useMemo/utils/math.ts` around lines 3 - 4, The prime number checking loop in the function on lines 3-4 currently iterates from 2 up to num, which is computationally expensive. Change the loop condition from `i < num` to `i * i <= num` to optimize the prime checking algorithm. This works because if a number has a divisor greater than its square root, it must also have a corresponding divisor less than its square root, so checking only up to the square root is mathematically sufficient while significantly reducing computation.Week10/endl24/mission00/src/App.css (1)
1-1: ⚡ Quick win
@import "tailwindcss";중복 제거.이미
index.css에서 Tailwind를 import하고 있으므로,App.css에서는 제거하세요. 단일 엔트리포인트에서만 Tailwind를 import하는 것이 표준 패턴입니다.💬 제안된 변경
-@import "tailwindcss";🤖 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/endl24/mission00/src/App.css` at line 1, Remove the `@import` "tailwindcss"; statement from App.css because Tailwind is already imported in the index.css file, which serves as the single entry point for the application. Following the standard pattern, Tailwind CSS should only be imported once from the main CSS file, not duplicated across multiple CSS files. Delete this import line entirely from App.css.Week10/endl24/mission00/src/main.tsx (1)
1-1: 💤 Low value사용하지 않는 StrictMode import 제거.
StrictMode가 import되었으나 렌더링에서 사용되지 않습니다. 필요한 경우 실제로 감싸거나, 불필요하면 제거하세요.💬 제안된 변경
-import { StrictMode } from 'react' import { createRoot } from 'react-dom/client'🤖 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/endl24/mission00/src/main.tsx` at line 1, The StrictMode component is being imported from React but is not actually used anywhere in the rendering code. Remove StrictMode from the import statement at the top of the file where React imports are defined. If StrictMode is needed for development purposes (such as detecting unsafe lifecycle methods), wrap the root component with it during rendering; otherwise, simply delete it from the import to keep the code clean.Week10/endl24/mission0102/src/types/movie.ts (1)
3-7: ⚡ Quick win
MovieFilters.language을 더 강한 타입으로 정의하세요.현재
MovieFilters의language필드가string으로 선언되어 있습니다.MovieLanguage타입으로 제한하면 타입 안정성이 향상됩니다. 또한 LANGUAGE_OPTIONS의 value 타입과 일치해야 합니다.♻️ 제안된 수정
export type MovieFilters = { query: string; include_adult: boolean; - language: string; + language: MovieLanguage; };🤖 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/endl24/mission0102/src/types/movie.ts` around lines 3 - 7, The language field in the MovieFilters type is currently declared as a generic string, which lacks type safety. Change the language field type from string to MovieLanguage to restrict it to valid language values and ensure consistency with LANGUAGE_OPTIONS. This will improve type safety and prevent invalid language values from being passed as filters.Week10/endl24/mission0102/src/App.css (1)
1-1: ⚡ Quick winTailwind 전역 import를 한 곳으로만 유지하세요.
Week10/endl24/mission0102/src/index.css에서 이미 Tailwind를 import하고 있어, 여기까지 중복 import하면 CSS 중복 생성/번들 비대화 위험이 있습니다.App.css에서는 해당 라인을 제거하는 쪽이 안전합니다.제안 diff
-@import "tailwindcss";🤖 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/endl24/mission0102/src/App.css` at line 1, Remove the duplicate Tailwind import statement from App.css since Tailwind CSS is already being imported globally in index.css. Having the same import in multiple files can cause CSS duplication and increase bundle size. Delete the `@import` "tailwindcss"; line from App.css and rely solely on the import in index.css to provide Tailwind styles throughout the application.
🤖 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/endl24/mission00/src/App.tsx`:
- Line 2: The `UseCallbackPage` component is imported in App.tsx but is not
being rendered anywhere in the component. Either remove the unused import if
only `UseMemoPage` is needed for the PR goal, or add rendering logic to display
`UseCallbackPage` (such as through tabs, routing, or sequential page rendering)
if both pages should be demonstrated.
In `@Week10/endl24/mission00/src/useCallback-memo/useCallbackPage.tsx`:
- Around line 9-15: The dependency arrays in both handleText and
handleIncreaseCount callbacks are including state values that cause the callback
references to change frequently, which undermines the memo optimization. For
handleText (lines 9-11), remove the text dependency and use an empty dependency
array to keep the callback reference stable. For handleIncreaseCount (lines
13-15), convert to functional state update by changing setCount(count + number)
to setCount(prevCount => prevCount + number) and use an empty dependency array,
which allows the callback to access the current state without needing to list
count as a dependency.
In `@Week10/endl24/mission0102/src/apis/axiosClient.ts`:
- Around line 3-7: The TMDB Bearer token is being exposed in the client-side
bundle through the VITE_* environment variable in the axiosClient configuration,
creating a security vulnerability where users can see and misuse the token.
Remove the Authorization header from the axiosClient axios.create call and
instead implement a server-side proxy or Backend For Frontend (BFF) pattern
where TMDB API calls are made from the server with the token attached securely
there. Update the baseURL to point to your backend endpoint instead of the TMDB
API directly, and have the server forward requests to TMDB with the token added
server-side.
In `@Week10/endl24/mission0102/src/components/MovieCard.tsx`:
- Line 20: In the MovieCard.tsx file at the img element with the
transition-transform class, there is a typo in the className attribute where
"case-in-out" should be "ease-in-out". Replace the incorrect "case-in-out"
string with "ease-in-out" to properly apply the Tailwind easing function to the
transform transition effect.
In `@Week10/endl24/mission0102/src/components/MovieFilter.tsx`:
- Around line 17-24: The search action in the handleSubmit function is currently
only triggered by button clicks, which prevents Enter key submission. Wrap the
form inputs (query, includeAdult, language fields) in an HTML form element with
an onSubmit handler that calls handleSubmit, and update the button to
type="submit". This ensures both button clicks and Enter key presses trigger the
same submission logic. Apply this same structure change to both the primary form
location and the sibling form location at lines 62-68.
In `@Week10/endl24/mission0102/src/hooks/useFetch.ts`:
- Around line 10-23: The useFetch hook has two issues: race conditions where
previous requests can overwrite newer results, and error state stickiness where
error messages persist across new requests. To fix this, clear the error state
by calling setError with null or empty string at the start of each fetch in the
fetchData function, immediately after setIsLoading(true). Additionally,
implement request cancellation using AbortController to prevent late-arriving
responses from overwriting current results: create an AbortController instance,
pass its signal to the axiosClient.get call via the option parameter, and cancel
the previous request in the useEffect cleanup function if a new request is
initiated. This ensures that only the most recent request updates the component
state.
In `@Week10/endl24/mission0102/src/pages/HomePage.tsx`:
- Around line 18-20: The early return pattern in the error handling block (lines
18-20) prevents the filter UI from rendering when an error occurs, blocking user
retry attempts. Remove the early return that displays only the error message.
Instead, restructure the component to always render the filter UI and display
the error message inline (such as an alert or error banner) above or within the
filter section. This allows users to modify search conditions and retry without
the filter becoming inaccessible during error states.
In `@Week10/endl24/mission0102/src/types/movie.ts`:
- Line 1: The MovieLanguage type definition contains a typo where the Japanese
language code is specified as "ja=JP" instead of "ja-JP". Fix this by replacing
the equals sign with a hyphen in the MovieLanguage union type to ensure
consistency with the constant definitions in src/constants/movie.ts and prevent
runtime type mismatches.
- Around line 9-25: The `poster_path` field in the Movie type is defined as
non-nullable (string), but the TMDB API can return null for this field. Update
the `poster_path` field declaration in the Movie type to make it nullable by
changing it from `string` to `string | null` to accurately reflect the actual
API response structure.
---
Nitpick comments:
In `@Week10/endl24/mission00/src/App.css`:
- Line 1: Remove the `@import` "tailwindcss"; statement from App.css because
Tailwind is already imported in the index.css file, which serves as the single
entry point for the application. Following the standard pattern, Tailwind CSS
should only be imported once from the main CSS file, not duplicated across
multiple CSS files. Delete this import line entirely from App.css.
In `@Week10/endl24/mission00/src/main.tsx`:
- Line 1: The StrictMode component is being imported from React but is not
actually used anywhere in the rendering code. Remove StrictMode from the import
statement at the top of the file where React imports are defined. If StrictMode
is needed for development purposes (such as detecting unsafe lifecycle methods),
wrap the root component with it during rendering; otherwise, simply delete it
from the import to keep the code clean.
In `@Week10/endl24/mission00/src/useMemo/utils/math.ts`:
- Around line 3-4: The prime number checking loop in the function on lines 3-4
currently iterates from 2 up to num, which is computationally expensive. Change
the loop condition from `i < num` to `i * i <= num` to optimize the prime
checking algorithm. This works because if a number has a divisor greater than
its square root, it must also have a corresponding divisor less than its square
root, so checking only up to the square root is mathematically sufficient while
significantly reducing computation.
In `@Week10/endl24/mission0102/src/App.css`:
- Line 1: Remove the duplicate Tailwind import statement from App.css since
Tailwind CSS is already being imported globally in index.css. Having the same
import in multiple files can cause CSS duplication and increase bundle size.
Delete the `@import` "tailwindcss"; line from App.css and rely solely on the
import in index.css to provide Tailwind styles throughout the application.
In `@Week10/endl24/mission0102/src/types/movie.ts`:
- Around line 3-7: The language field in the MovieFilters type is currently
declared as a generic string, which lacks type safety. Change the language field
type from string to MovieLanguage to restrict it to valid language values and
ensure consistency with LANGUAGE_OPTIONS. This will improve type safety and
prevent invalid language values from being passed as filters.
🪄 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: 2e4d9fb2-3e51-4e3e-8959-e76273ff58dc
⛔ Files ignored due to path filters (13)
Week10/endl24/mission00/pnpm-lock.yamlis excluded by!**/pnpm-lock.yamlWeek10/endl24/mission00/public/favicon.svgis excluded by!**/*.svgWeek10/endl24/mission00/public/icons.svgis excluded by!**/*.svgWeek10/endl24/mission00/src/assets/hero.pngis excluded by!**/*.pngWeek10/endl24/mission00/src/assets/react.svgis excluded by!**/*.svgWeek10/endl24/mission00/src/assets/vite.svgis excluded by!**/*.svgWeek10/endl24/mission0102/pnpm-lock.yamlis excluded by!**/pnpm-lock.yamlWeek10/endl24/mission0102/public/favicon.svgis excluded by!**/*.svgWeek10/endl24/mission0102/public/icons.svgis excluded by!**/*.svgWeek10/endl24/mission0102/src/assets/hero.pngis excluded by!**/*.pngWeek10/endl24/mission0102/src/assets/react.svgis excluded by!**/*.svgWeek10/endl24/mission0102/src/assets/vite.svgis excluded by!**/*.svgpnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (43)
Week10/endl24/mission00/.gitignoreWeek10/endl24/mission00/README.mdWeek10/endl24/mission00/eslint.config.jsWeek10/endl24/mission00/index.htmlWeek10/endl24/mission00/package.jsonWeek10/endl24/mission00/src/App.cssWeek10/endl24/mission00/src/App.tsxWeek10/endl24/mission00/src/index.cssWeek10/endl24/mission00/src/main.tsxWeek10/endl24/mission00/src/useCallback-memo/components/CountButton.tsxWeek10/endl24/mission00/src/useCallback-memo/components/TextInput.tsxWeek10/endl24/mission00/src/useCallback-memo/useCallbackPage.tsxWeek10/endl24/mission00/src/useMemo/UseMemoPage.tsxWeek10/endl24/mission00/src/useMemo/components/TextInput.tsxWeek10/endl24/mission00/src/useMemo/utils/math.tsWeek10/endl24/mission00/tsconfig.app.jsonWeek10/endl24/mission00/tsconfig.jsonWeek10/endl24/mission00/tsconfig.node.jsonWeek10/endl24/mission00/vite.config.tsWeek10/endl24/mission0102/.gitignoreWeek10/endl24/mission0102/README.mdWeek10/endl24/mission0102/eslint.config.jsWeek10/endl24/mission0102/index.htmlWeek10/endl24/mission0102/package.jsonWeek10/endl24/mission0102/src/App.cssWeek10/endl24/mission0102/src/App.tsxWeek10/endl24/mission0102/src/apis/axiosClient.tsWeek10/endl24/mission0102/src/components/LanguageSelector.tsxWeek10/endl24/mission0102/src/components/MovieCard.tsxWeek10/endl24/mission0102/src/components/MovieFilter.tsxWeek10/endl24/mission0102/src/components/MovieList.tsxWeek10/endl24/mission0102/src/components/SelectBox.tsxWeek10/endl24/mission0102/src/components/input.tsxWeek10/endl24/mission0102/src/constants/movie.tsWeek10/endl24/mission0102/src/hooks/useFetch.tsWeek10/endl24/mission0102/src/index.cssWeek10/endl24/mission0102/src/main.tsxWeek10/endl24/mission0102/src/pages/HomePage.tsxWeek10/endl24/mission0102/src/types/movie.tsWeek10/endl24/mission0102/tsconfig.app.jsonWeek10/endl24/mission0102/tsconfig.jsonWeek10/endl24/mission0102/tsconfig.node.jsonWeek10/endl24/mission0102/vite.config.ts
| @@ -0,0 +1,14 @@ | |||
| import "./App.css"; | |||
| import UseCallbackPage from "./useCallback-memo/useCallbackPage"; | |||
There was a problem hiding this comment.
사용하지 않는 UseCallbackPage import 제거 또는 렌더링 추가.
UseCallbackPage가 import되었으나 렌더링되지 않습니다. PR 목표에 따라:
- 두 페이지를 모두 데모하려면, 렌더링 구조를 변경하여 추가하세요 (예: 탭, 라우팅, 또는 순차 페이지).
UseMemoPage만 필요하면, 불필요한 import를 제거하세요.
🤖 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/endl24/mission00/src/App.tsx` at line 2, The `UseCallbackPage`
component is imported in App.tsx but is not being rendered anywhere in the
component. Either remove the unused import if only `UseMemoPage` is needed for
the PR goal, or add rendering logic to display `UseCallbackPage` (such as
through tabs, routing, or sequential page rendering) if both pages should be
demonstrated.
| const handleText = useCallback((text: string) => { | ||
| setText(text); | ||
| }, [text]); | ||
|
|
||
| const handleIncreaseCount = useCallback((number: number) => { | ||
| setCount(count + number); | ||
| }, [count]); |
There was a problem hiding this comment.
useCallback 의존성 설정 때문에 데모 의도가 약해집니다.
Line 9-11과 Line 13-15에서 상태값을 직접 의존시키면 콜백 레퍼런스가 자주 바뀌어 memo 자식 최적화 효과가 흐려집니다. setCount는 함수형 업데이트를 사용하고 두 핸들러 모두 고정 deps로 두는 편이 안전합니다.
수정 제안
- const handleText = useCallback((text: string) => {
- setText(text);
- }, [text]);
+ const handleText = useCallback((nextText: string) => {
+ setText(nextText);
+ }, []);
- const handleIncreaseCount = useCallback((number: number) => {
- setCount(count + number);
- }, [count]);
+ const handleIncreaseCount = useCallback((number: number) => {
+ setCount((prev) => prev + number);
+ }, []);🤖 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/endl24/mission00/src/useCallback-memo/useCallbackPage.tsx` around
lines 9 - 15, The dependency arrays in both handleText and handleIncreaseCount
callbacks are including state values that cause the callback references to
change frequently, which undermines the memo optimization. For handleText (lines
9-11), remove the text dependency and use an empty dependency array to keep the
callback reference stable. For handleIncreaseCount (lines 13-15), convert to
functional state update by changing setCount(count + number) to
setCount(prevCount => prevCount + number) and use an empty dependency array,
which allows the callback to access the current state without needing to list
count as a dependency.
| export const axiosClient = axios.create({ | ||
| baseURL: "https://api.themoviedb.org/3", | ||
| headers:{ | ||
| Authorization: `Bearer ${import.meta.env.VITE_TMDB_TOKEN}`, | ||
| } |
There was a problem hiding this comment.
클라이언트 번들에 TMDB Bearer 토큰이 노출됩니다.
VITE_* 환경변수는 클라이언트에 주입되므로, 현재 방식은 토큰이 사용자에게 공개됩니다. 악의적 재사용으로 쿼터 소진/오남용이 가능합니다. TMDB 호출은 서버(또는 BFF)에서 토큰을 붙이는 구조로 분리하는 것이 안전합니다.
🔐 제안 수정안
export const axiosClient = axios.create({
- baseURL: "https://api.themoviedb.org/3",
- headers:{
- Authorization: `Bearer ${import.meta.env.VITE_TMDB_TOKEN}`,
- }
+ // 서버 프록시(/api/tmdb) 뒤로 이동하고,
+ // 서버에서만 TMDB 토큰을 주입하세요.
+ baseURL: "/api/tmdb",
})📝 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 const axiosClient = axios.create({ | |
| baseURL: "https://api.themoviedb.org/3", | |
| headers:{ | |
| Authorization: `Bearer ${import.meta.env.VITE_TMDB_TOKEN}`, | |
| } | |
| export const axiosClient = axios.create({ | |
| // 서버 프록시(/api/tmdb) 뒤로 이동하고, | |
| // 서버에서만 TMDB 토큰을 주입하세요. | |
| baseURL: "/api/tmdb", | |
| }) |
🤖 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/endl24/mission0102/src/apis/axiosClient.ts` around lines 3 - 7, The
TMDB Bearer token is being exposed in the client-side bundle through the VITE_*
environment variable in the axiosClient configuration, creating a security
vulnerability where users can see and misuse the token. Remove the Authorization
header from the axiosClient axios.create call and instead implement a
server-side proxy or Backend For Frontend (BFF) pattern where TMDB API calls are
made from the server with the token attached securely there. Update the baseURL
to point to your backend endpoint instead of the TMDB API directly, and have the
server forward requests to TMDB with the token added server-side.
| const handleSubmit = () => { | ||
| const filters: MovieFilters = { | ||
| query, | ||
| include_adult: includeAdult, | ||
| language, | ||
| }; | ||
| onChange(filters); | ||
| }; |
There was a problem hiding this comment.
검색 액션이 버튼 클릭에만 묶여 있어 Enter 키 제출이 동작하지 않습니다.
검색 UI는 <form onSubmit>로 구성해 키보드 제출(Enter)도 동일 동작 경로를 타도록 맞추는 것이 안전합니다.
⌨️ 제안 수정안
-import { useState } from "react";
+import { useState, type FormEvent } from "react";
@@
- const handleSubmit = () => {
+ const handleSubmit = (e: FormEvent) => {
+ e.preventDefault();
const filters: MovieFilters = {
query,
include_adult: includeAdult,
language,
};
onChange(filters);
};
return (
- <div className="transform rounded-2xl border border-gray-300 bg-white p-6 shadow-xl transition-all hover:shadow-2xl">
+ <form
+ onSubmit={handleSubmit}
+ className="transform rounded-2xl border border-gray-300 bg-white p-6 shadow-xl transition-all hover:shadow-2xl"
+ >
@@
- <button
- onClick={handleSubmit}
+ <button
+ type="submit"
className="rounded-lg bg-blue-500 px-6 py-2 font-semibold text-white hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500"
>
영화 검색
</button>
@@
- </div>
+ </form>
);Also applies to: 62-68
🤖 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/endl24/mission0102/src/components/MovieFilter.tsx` around lines 17 -
24, The search action in the handleSubmit function is currently only triggered
by button clicks, which prevents Enter key submission. Wrap the form inputs
(query, includeAdult, language fields) in an HTML form element with an onSubmit
handler that calls handleSubmit, and update the button to type="submit". This
ensures both button clicks and Enter key presses trigger the same submission
logic. Apply this same structure change to both the primary form location and
the sibling form location at lines 62-68.
| useEffect(() => { | ||
| const fetchData = async () => { | ||
| setIsLoading(true); | ||
| try { | ||
| const { data } = await axiosClient.get(url, { | ||
| ...option, | ||
| }); | ||
|
|
||
| setData(data); | ||
| } catch (err) { | ||
| setError("데이터를 가져오는데 에러가 발생했습니다."); | ||
| } finally { | ||
| setIsLoading(false); | ||
| } |
There was a problem hiding this comment.
요청 경합과 에러 상태 고착으로 결과 화면이 틀어질 수 있습니다.
이전 요청이 늦게 완료되면 최신 검색 결과를 덮어쓸 수 있고, 새 요청 전에 error를 초기화하지 않아 에러 UI가 계속 남을 수 있습니다.
🛠 제안 수정안
useEffect(() => {
+ let cancelled = false;
const fetchData = async () => {
setIsLoading(true);
+ setError(null);
try {
const { data } = await axiosClient.get(url, {
...option,
});
-
- setData(data);
+ if (!cancelled) setData(data);
} catch (err) {
- setError("데이터를 가져오는데 에러가 발생했습니다.");
+ if (!cancelled) {
+ setError("데이터를 가져오는데 에러가 발생했습니다.");
+ }
} finally {
- setIsLoading(false);
+ if (!cancelled) setIsLoading(false);
}
};
fetchData();
+ return () => {
+ cancelled = true;
+ };
}, [url, JSON.stringify(option)]);Also applies to: 28-28
🤖 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/endl24/mission0102/src/hooks/useFetch.ts` around lines 10 - 23, The
useFetch hook has two issues: race conditions where previous requests can
overwrite newer results, and error state stickiness where error messages persist
across new requests. To fix this, clear the error state by calling setError with
null or empty string at the start of each fetch in the fetchData function,
immediately after setIsLoading(true). Additionally, implement request
cancellation using AbortController to prevent late-arriving responses from
overwriting current results: create an AbortController instance, pass its signal
to the axiosClient.get call via the option parameter, and cancel the previous
request in the useEffect cleanup function if a new request is initiated. This
ensures that only the most recent request updates the component state.
| if (error) { | ||
| return <div>{error}</div>; | ||
| } |
There was a problem hiding this comment.
에러 발생 시 필터 UI가 사라져 사용자가 재시도할 수 없습니다.
현재 조기 반환 때문에 에러 상태에서 검색 조건 변경 자체가 막힙니다. 필터는 항상 렌더링하고, 에러는 인라인으로 표시해 재시도 가능 상태를 유지하세요.
🔁 제안 수정안
- if (error) {
- return <div>{error}</div>;
- }
-
return (
<div className="container">
<MovieFilter onChange={setFilters} />
+ {error && (
+ <div className="mt-4 rounded-md bg-red-50 p-3 text-sm text-red-700">
+ {error}
+ </div>
+ )}
{isLoading ? (
<div>로딩 중 입니다...</div>
) : (
<MovieList movies={data?.results || []} />
)}
</div>
);Also applies to: 23-30
🤖 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/endl24/mission0102/src/pages/HomePage.tsx` around lines 18 - 20, The
early return pattern in the error handling block (lines 18-20) prevents the
filter UI from rendering when an error occurs, blocking user retry attempts.
Remove the early return that displays only the error message. Instead,
restructure the component to always render the filter UI and display the error
message inline (such as an alert or error banner) above or within the filter
section. This allows users to modify search conditions and retry without the
filter becoming inaccessible during error states.
| @@ -0,0 +1,32 @@ | |||
| export type MovieLanguage = "ko-KR" | "en-US" | "ja=JP"; | |||
There was a problem hiding this comment.
MovieLanguage 타입의 일본어 코드가 잘못되었습니다.
Line 1의 "ja=JP"는 "ja-JP"로 수정되어야 합니다. 현재 src/constants/movie.ts에서는 올바르게 "ja-JP"로 정의되어 있으나, 타입 정의에서 다르게 되어 있어 런타임 타입 불일치가 발생할 수 있습니다.
🔧 수정 제안
-export type MovieLanguage = "ko-KR" | "en-US" | "ja=JP";
+export type MovieLanguage = "ko-KR" | "en-US" | "ja-JP";📝 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 type MovieLanguage = "ko-KR" | "en-US" | "ja=JP"; | |
| export type MovieLanguage = "ko-KR" | "en-US" | "ja-JP"; |
🤖 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/endl24/mission0102/src/types/movie.ts` at line 1, The MovieLanguage
type definition contains a typo where the Japanese language code is specified as
"ja=JP" instead of "ja-JP". Fix this by replacing the equals sign with a hyphen
in the MovieLanguage union type to ensure consistency with the constant
definitions in src/constants/movie.ts and prevent runtime type mismatches.
| export type Movie = { | ||
| adult: boolean; | ||
| backdrop_path: string | null; | ||
| genre_ids: number[]; | ||
| id: number; | ||
| title: string; | ||
| original_language: string; | ||
| original_title: string; | ||
| overview: string; | ||
| popularity: number; | ||
| poster_path: string; | ||
| release_date: string; | ||
| softcore: boolean; | ||
| video: boolean; | ||
| vote_average: number; | ||
| vote_count: number; | ||
| }; |
There was a problem hiding this comment.
Movie 타입에서 poster_path 필드가 non-nullable로 정의되어 있습니다.
Line 19에서 poster_path: string으로 정의되었으나, TMDB API 응답에서는 이 필드가 null일 수 있습니다. MovieCard 컴포넌트에서 fallback 처리가 있더라도, 타입 정의는 실제 API 응답을 반영해야 합니다.
🔧 수정 제안
export type Movie = {
adult: boolean;
backdrop_path: string | null;
genre_ids: number[];
id: number;
title: string;
original_language: string;
original_title: string;
overview: string;
popularity: number;
- poster_path: string;
+ poster_path: string | null;
release_date: string;
softcore: boolean;
video: boolean;
vote_average: number;
vote_count: number;
};📝 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 type Movie = { | |
| adult: boolean; | |
| backdrop_path: string | null; | |
| genre_ids: number[]; | |
| id: number; | |
| title: string; | |
| original_language: string; | |
| original_title: string; | |
| overview: string; | |
| popularity: number; | |
| poster_path: string; | |
| release_date: string; | |
| softcore: boolean; | |
| video: boolean; | |
| vote_average: number; | |
| vote_count: number; | |
| }; | |
| export type Movie = { | |
| adult: boolean; | |
| backdrop_path: string | null; | |
| genre_ids: number[]; | |
| id: number; | |
| title: string; | |
| original_language: string; | |
| original_title: string; | |
| overview: string; | |
| popularity: number; | |
| poster_path: string | null; | |
| release_date: string; | |
| softcore: boolean; | |
| video: boolean; | |
| vote_average: number; | |
| vote_count: number; | |
| }; |
🤖 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/endl24/mission0102/src/types/movie.ts` around lines 9 - 25, The
`poster_path` field in the Movie type is defined as non-nullable (string), but
the TMDB API can return null for this field. Update the `poster_path` field
declaration in the Movie type to make it nullable by changing it from `string`
to `string | null` to accurately reflect the actual API response structure.
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
Week10/endl24/mission0102/src/App.tsx (1)
8-13: ⚡ Quick win잘못된 경로 진입 시 빈 화면이 나올 수 있어
*폴백 라우트를 추가해 주세요.현재 라우트 테이블에는 미일치 경로 처리(
*)가 없어, 오타 URL 접근 시 아무 콘텐츠도 렌더링되지 않습니다.제안 diff
import { BrowserRouter, Routes, Route } from "react-router-dom"; import HomePage from "./pages/HomePage"; import MovieDetailPage from "./pages/MovieDetailPage"; // 방금 만든 페이지 +import { Navigate } from "react-router-dom"; function App() { return ( <BrowserRouter> <Routes> {/* 메인 페이지 */} <Route path="/" element={<HomePage />} /> {/* 영화 상세 페이지 동적 라우팅 */} <Route path="/movies/:movieId" element={<MovieDetailPage />} /> + {/* 잘못된 경로 폴백 */} + <Route path="*" element={<Navigate to="/" replace />} /> </Routes> </BrowserRouter> ); }🤖 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/endl24/mission0102/src/App.tsx` around lines 8 - 13, The Routes component in App.tsx lacks a fallback route for handling unmatched paths, resulting in a blank screen when users navigate to invalid URLs. Add a new Route with path="*" after the existing routes (the HomePage and MovieDetailPage routes) to catch all undefined paths and render an appropriate component such as a NotFound or 404 page, providing proper user feedback instead of a blank screen.
🤖 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/endl24/mission0102/src/components/Modal.tsx`:
- Around line 21-31: The modal component is missing critical accessibility
attributes that assistive technologies rely on to properly identify and describe
the dialog. Add `role="dialog"` and `aria-modal="true"` to the inner div element
(the one with the className containing "fixed inset-0 z-50 flex items-center
justify-center...") that wraps the children content. Additionally, add either an
`aria-label` attribute with a descriptive text or an `aria-labelledby` attribute
pointing to an element ID that labels the modal, so screen readers can properly
announce the purpose of the dialog to users.
In `@Week10/endl24/mission0102/src/components/MovieCard.tsx`:
- Around line 14-17: The div element in MovieCard component that has the onClick
handler is not keyboard accessible, preventing keyboard users from interacting
with it using Enter or Space keys. Either convert the div element to a button
element to inherit native keyboard accessibility, or if keeping it as a div, add
the accessibility attributes role="button", tabIndex="0", and an onKeyDown
handler that triggers the onClick callback when Enter or Space keys are pressed.
This ensures the card is fully accessible to keyboard users.
In `@Week10/endl24/mission0102/src/pages/MovieDetailPage.tsx`:
- Around line 3-11: The MovieDetailPage is unreachable because the onMovieClick
handler in HomePage.tsx only opens a modal and does not perform any navigation.
Modify the onMovieClick handler to navigate to the detail page route using the
pattern `/movies/${movie.id}` (using React Router's useNavigate hook), or
alternatively wrap the MovieCard component with a Link element that points to
this route. This will connect the user interaction flow to the detail page so
the movieId parameter extracted from the URL can be properly utilized.
---
Nitpick comments:
In `@Week10/endl24/mission0102/src/App.tsx`:
- Around line 8-13: The Routes component in App.tsx lacks a fallback route for
handling unmatched paths, resulting in a blank screen when users navigate to
invalid URLs. Add a new Route with path="*" after the existing routes (the
HomePage and MovieDetailPage routes) to catch all undefined paths and render an
appropriate component such as a NotFound or 404 page, providing proper user
feedback instead of a blank screen.
🪄 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: 01c6ae8a-9d31-40e8-a48d-898ec0f84d80
⛔ Files ignored due to path filters (1)
Week10/endl24/mission0102/pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (13)
Week10/endl24/mission0102/.gitignoreWeek10/endl24/mission0102/package.jsonWeek10/endl24/mission0102/src/App.tsxWeek10/endl24/mission0102/src/components/LanguageSelector.tsxWeek10/endl24/mission0102/src/components/Modal.tsxWeek10/endl24/mission0102/src/components/MovieCard.tsxWeek10/endl24/mission0102/src/components/MovieFilter.tsxWeek10/endl24/mission0102/src/components/MovieList.tsxWeek10/endl24/mission0102/src/components/SelectBox.tsxWeek10/endl24/mission0102/src/components/input.tsxWeek10/endl24/mission0102/src/pages/HomePage.tsxWeek10/endl24/mission0102/src/pages/MovieDetailPage.tsxWeek10/endl24/mission0102/vercel.json
✅ Files skipped from review due to trivial changes (2)
- Week10/endl24/mission0102/vercel.json
- Week10/endl24/mission0102/.gitignore
🚧 Files skipped from review as they are similar to previous changes (4)
- Week10/endl24/mission0102/src/components/LanguageSelector.tsx
- Week10/endl24/mission0102/src/components/SelectBox.tsx
- Week10/endl24/mission0102/package.json
- Week10/endl24/mission0102/src/components/MovieFilter.tsx
| <div | ||
| className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 p-4 backdrop-blur-sm" | ||
| onClick={onClose} | ||
| > | ||
| <div | ||
| className="w-full max-w-md transform overflow-hidden rounded-3xl bg-white shadow-2xl transition-all" | ||
| onClick={(e) => e.stopPropagation()} | ||
| > | ||
| {children} | ||
| </div> | ||
| </div> |
There was a problem hiding this comment.
모달 접근성 시맨틱이 빠져 있습니다.
현재 모달 컨테이너에 role="dialog"/aria-modal="true"가 없어 보조기기에서 대화상자로 인식되지 않습니다. 최소한 해당 속성과 라벨(aria-label 또는 aria-labelledby)을 추가해 주세요.
🤖 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/endl24/mission0102/src/components/Modal.tsx` around lines 21 - 31, The
modal component is missing critical accessibility attributes that assistive
technologies rely on to properly identify and describe the dialog. Add
`role="dialog"` and `aria-modal="true"` to the inner div element (the one with
the className containing "fixed inset-0 z-50 flex items-center
justify-center...") that wraps the children content. Additionally, add either an
`aria-label` attribute with a descriptive text or an `aria-labelledby` attribute
pointing to an element ID that labels the modal, so screen readers can properly
announce the purpose of the dialog to users.
| <div | ||
| onClick={onClick} | ||
| className="group flex cursor-pointer flex-col overflow-hidden rounded-3xl border-2 border-zinc-200 bg-white transition-all duration-300 hover:-translate-y-2 hover:border-violet-400 hover:shadow-[0_20px_40px_-15px_rgba(139,92,246,0.25)]" | ||
| > |
There was a problem hiding this comment.
키보드 접근이 불가능한 클릭 카드입니다.
Line 14의 div 클릭 처리만으로는 키보드 사용자(Enter/Space) 경로가 막힙니다. 카드 루트를 button으로 바꾸거나 role="button", tabIndex, onKeyDown을 추가해 접근 경로를 보장해 주세요.
제안 diff
- <div
- onClick={onClick}
+ <button
+ type="button"
+ onClick={onClick}
className="group flex cursor-pointer flex-col overflow-hidden rounded-3xl border-2 border-zinc-200 bg-white transition-all duration-300 hover:-translate-y-2 hover:border-violet-400 hover:shadow-[0_20px_40px_-15px_rgba(139,92,246,0.25)]"
>
@@
- </div>
+ </button>📝 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={onClick} | |
| className="group flex cursor-pointer flex-col overflow-hidden rounded-3xl border-2 border-zinc-200 bg-white transition-all duration-300 hover:-translate-y-2 hover:border-violet-400 hover:shadow-[0_20px_40px_-15px_rgba(139,92,246,0.25)]" | |
| > | |
| <button | |
| type="button" | |
| onClick={onClick} | |
| className="group flex cursor-pointer flex-col overflow-hidden rounded-3xl border-2 border-zinc-200 bg-white transition-all duration-300 hover:-translate-y-2 hover:border-violet-400 hover:shadow-[0_20px_40px_-15px_rgba(139,92,246,0.25)]" | |
| > | |
| {/* card content */} | |
| </button> |
🤖 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/endl24/mission0102/src/components/MovieCard.tsx` around lines 14 - 17,
The div element in MovieCard component that has the onClick handler is not
keyboard accessible, preventing keyboard users from interacting with it using
Enter or Space keys. Either convert the div element to a button element to
inherit native keyboard accessibility, or if keeping it as a div, add the
accessibility attributes role="button", tabIndex="0", and an onKeyDown handler
that triggers the onClick callback when Enter or Space keys are pressed. This
ensures the card is fully accessible to keyboard users.
| export default function MovieDetailPage() { | ||
| // URL에서 :movieId 파라미터를 뽑아옵니다. | ||
| const { movieId } = useParams(); | ||
|
|
||
| return ( | ||
| <div className="flex h-screen flex-col items-center justify-center gap-4"> | ||
| <h1 className="text-3xl font-extrabold text-zinc-800">🎬 영화 상세 페이지</h1> | ||
| <p className="text-xl font-bold text-violet-600">선택된 영화 ID: {movieId}</p> | ||
| <p className="text-zinc-500">이곳에 향후 영화 상세 정보가 렌더링됩니다.</p> |
There was a problem hiding this comment.
MovieDetailPage 진입 경로가 현재 UI 플로우에 연결되지 않아 사실상 도달 불가능합니다.
Week10/endl24/mission0102/src/pages/HomePage.tsx의 클릭 동작은 모달만 열고 라우팅을 하지 않아서, Line 5의 movieId 기반 상세 페이지가 사용자 플로우에서 사용되지 않습니다. onMovieClick에서 /movies/${movie.id}로 이동(또는 카드에 Link)하도록 연결이 필요합니다.
🤖 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/endl24/mission0102/src/pages/MovieDetailPage.tsx` around lines 3 - 11,
The MovieDetailPage is unreachable because the onMovieClick handler in
HomePage.tsx only opens a modal and does not perform any navigation. Modify the
onMovieClick handler to navigate to the detail page route using the pattern
`/movies/${movie.id}` (using React Router's useNavigate hook), or alternatively
wrap the MovieCard component with a Link element that points to this route. This
will connect the user interaction flow to the detail page so the movieId
parameter extracted from the URL can be properly utilized.
📝 미션 번호
10주차 Misson 1
📋 구현 사항
배포 주소
📎 스크린샷
✅ 체크리스트
🤔 질문 사항
Summary by CodeRabbit
릴리스 노트