Conversation
📝 WalkthroughWalkthroughAdded a set of new React Native UI components (Button, Input, Dropdown, Loading, Modal, Skeleton), form context and hooks for state management (useForm, useToggle, useDropdown, useModal, useInfiniteScroll), and validation utilities; removed placeholder comments from two common files. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related issues
Poem
🚥 Pre-merge checks | ✅ 3✅ 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: 5
🧹 Nitpick comments (6)
src/components/Dropdown/Dropdown.tsx (1)
23-24: Use a stable key for dropdown options.Line 24 uses
idxas the key, which can cause item identity issues when options are reordered or filtered. Prefer a stable, item-derived key.🔧 Proposed change
-export const Dropdown = ({ options, onSelect }: any) => { +export const Dropdown = ({ options, onSelect }: any) => { return ( <View> {options.map((item: any, idx: number) => ( - <Pressable key={idx} onPress={() => onSelect(item)}> + <Pressable key={String(item)} onPress={() => onSelect(item)}> <Text>{item}</Text> </Pressable> ))} </View> ); };🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/Dropdown/Dropdown.tsx` around lines 23 - 24, The dropdown options map uses the array index (idx) as the React key in the Pressable created inside options.map, which can break identity when items reorder or filter; change the key to a stable, item-derived identifier (e.g., item.id or another unique property) by updating the key on the Pressable in the options.map callback and ensure the data passed to onSelect remains unchanged (use item.id if available, otherwise derive a stable unique value from the item).src/hooks/useForm.ts (1)
18-20: Consider memoizing handlers to prevent unnecessary re-renders.
handleChangeandhandleSubmitare recreated on every render. If these are passed to child components (viaFormProvider), it may trigger unnecessary re-renders. UsinguseCallbackwould stabilize the references.♻️ Suggested memoization
+import { useState, useCallback } from 'react'; -import { useState } from 'react'; ... - const handleChange = (name: string, value: any) => { - setValues((prev: any) => ({ ...prev, [name]: value })); - }; + const handleChange = useCallback((name: string, value: any) => { + setValues((prev: any) => ({ ...prev, [name]: value })); + }, []); - const handleSubmit = () => { + const handleSubmit = useCallback(() => { const newErrors = validate ? validate(values) : {}; setErrors(newErrors); if (Object.keys(newErrors).length === 0) { - onSubmit(values); + onSubmit?.(values); } - }; + }, [values, validate, onSubmit]);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/hooks/useForm.ts` around lines 18 - 20, handleChange and handleSubmit are recreated each render causing potential child re-renders; wrap them in React.useCallback to stabilize references. Update handleChange to useCallback((name, value) => setValues(prev => ({ ...prev, [name]: value })), [setValues]) and memoize handleSubmit similarly with useCallback, including its dependencies (e.g., values, onSubmit or any validation functions) so the returned handlers from the hook remain stable when passed through FormProvider.src/hooks/useModal.ts (1)
5-15: Documentation does not match implementation.The JSDoc mentions "ESC로 닫기" (ESC to close) and "outside click 감지" (outside click detection), but:
- ESC key handling is not applicable in React Native
- Outside click detection is handled in the
Modal.tsxcomponent, not in this hookConsider updating the documentation to accurately describe the hook's functionality: hardware back button handling on Android.
📝 Suggested documentation fix
/** * Modal 상태 관리 Hook * * 기능: * - open / close 상태 관리 - * - ESC로 닫기 - * - outside click 감지 + * - Android 하드웨어 백버튼으로 닫기 * * `@example` * const { isOpen, open, close } = useModal(); */🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/hooks/useModal.ts` around lines 5 - 15, Update the JSDoc for the useModal hook to accurately reflect its behavior: remove references to "ESC로 닫기" and "outside click 감지", and instead document that it manages open/close state and handles Android hardware back button behavior (with outside click handled in Modal.tsx); reference the hook name useModal and the Modal.tsx component so maintainers know where outside-click logic lives.src/contexts/FormContext.tsx (1)
15-19: Consider adding type definitions for type safety.Both
FormContextandFormProvideruseany, losing type safety. Consider defining an interface for the form context value.📝 Suggested type definitions
+interface FormContextValue { + values: Record<string, any>; + errors: Record<string, string>; + handleChange: (name: string, value: any) => void; + handleSubmit: () => void; +} + +interface FormProviderProps { + children: React.ReactNode; + value: FormContextValue; +} + -const FormContext = createContext<any>(null); +const FormContext = createContext<FormContextValue | null>(null); -export const FormProvider = ({ children, value }: any) => { +export const FormProvider = ({ children, value }: FormProviderProps) => {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/contexts/FormContext.tsx` around lines 15 - 19, FormContext and FormProvider use any which removes type safety; define a specific interface (e.g., FormContextValue) describing the shape of the context (fields, methods like submit/reset, loading flags, etc.), change createContext to createContext<FormContextValue | null>(null), and update FormProvider props to accept children: React.ReactNode and value: FormContextValue (or Partial/FormContextValue | null if appropriate). Also export the interface/type so consumers can import it and avoid using any in useContext(FormContext) calls.src/components/Modal/Modal.tsx (2)
21-21: Replaceanywith explicit prop types.Using
anyloses type safety and IDE support. Define proper prop types for better maintainability and developer experience.📝 Suggested type definition
+interface ModalProps { + isOpen: boolean; + onClose: () => void; + children: React.ReactNode; +} + -export const Modal = ({ isOpen, onClose, children }: any) => { +export const Modal = ({ isOpen, onClose, children }: ModalProps) => {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/Modal/Modal.tsx` at line 21, The Modal component currently types its props as any; replace this with a concrete prop type (e.g., define an interface or type named ModalProps) and annotate the Modal signature accordingly to restore type safety: include isOpen: boolean, onClose: () => void, and children: React.ReactNode (or ReactElement) as required/optional per usage, and export/apply the ModalProps type (or use React.FC<ModalProps>) so IDEs and the compiler enforce correct prop usage for Modal, isOpen, onClose and children.
24-30: Backdrop lacks visual styling — users won't see a dimmed overlay.The outer
Viewwithflex: 1has no background color, so the backdrop will be invisible. Typically, modals use a semi-transparent dark background to indicate the overlay area and improve UX.🎨 Suggested backdrop styling
<TouchableWithoutFeedback onPress={onClose}> - <View style={{ flex: 1 }}> + <View style={{ flex: 1, backgroundColor: 'rgba(0, 0, 0, 0.5)', justifyContent: 'center', alignItems: 'center' }}> <TouchableWithoutFeedback> <View>{children}</View> </TouchableWithoutFeedback> </View> </TouchableWithoutFeedback>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/Modal/Modal.tsx` around lines 24 - 30, The outer backdrop View inside the Modal component (wrapped by TouchableWithoutFeedback with onClose) has only { flex: 1 } so it's invisible; update the outer View's style (the View wrapping the inner TouchableWithoutFeedback/children) to include a semi-transparent dark background (e.g., backgroundColor: 'rgba(0,0,0,0.5)') and proper positioning (center the inner content) so the overlay is visible; keep the inner TouchableWithoutFeedback/children layout intact so clicks on the backdrop still call onClose while taps inside the inner View do not.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/components/Button/Button.tsx`:
- Line 24: The Button component currently forces non-text children into a <Text>
(see the JSX using loading ? <ActivityIndicator /> : <Text>{children}</Text>),
which breaks when children are icons or other React elements; update the render
so that when loading you show <ActivityIndicator />, otherwise render children
as-is, but still wrap only primitive text/number children in <Text> (use a
runtime check like typeof children === 'string' || typeof children === 'number'
or React.Children.toArray(children).every(c => typeof c === 'string' || typeof c
=== 'number') to decide whether to wrap), and ensure the Button's prop type for
children is React.ReactNode (or preserves the existing contract) so icons and
layout nodes are supported.
In `@src/contexts/FormContext.tsx`:
- Around line 21-23: useFormContext currently returns the raw FormContext which
can be null if called outside a FormProvider; update useFormContext to check for
a null context and throw a clear error (e.g., "useFormContext must be used
within a FormProvider") so consumers like Input.tsx won't crash with cryptic
runtime errors when accessing values[name]; modify the function that returns
useContext(FormContext) to perform this defensive null guard and throw a
descriptive exception if context is missing.
In `@src/hooks/useForm.ts`:
- Around line 22-29: The handleSubmit function calls onSubmit(values) unguarded
which throws if onSubmit is missing; update handleSubmit (in useForm.ts) to
check that onSubmit is a function before invoking (e.g., if
(Object.keys(newErrors).length === 0 && typeof onSubmit === "function")
onSubmit(values)) and otherwise no-op or log a warning so setErrors/validate
behavior stays the same and no runtime TypeError occurs.
In `@src/hooks/useInfiniteScroll.ts`:
- Around line 19-21: In useInfiniteScroll, ensure isFetching is always reset by
wrapping the fetchMore() call in a try/finally: call setIsFetching(true) before
invoking fetchMore(), await fetchMore() inside a try block, and call
setIsFetching(false) in the finally block so that setIsFetching(false) runs even
if fetchMore() rejects; update the logic around setIsFetching and fetchMore in
the hook to use this try/finally pattern.
In `@src/utils/validation.ts`:
- Around line 11-13: The required validator (function required) currently treats
whitespace-only strings as valid; update required to trim the input before
checking so that values like " " are considered empty—i.e., call value =
value?.trim() (or equivalent) then if (!value) return '필수 입력' else return ''.
Ensure you only modify the required function and preserve its return messages
and type signature.
---
Nitpick comments:
In `@src/components/Dropdown/Dropdown.tsx`:
- Around line 23-24: The dropdown options map uses the array index (idx) as the
React key in the Pressable created inside options.map, which can break identity
when items reorder or filter; change the key to a stable, item-derived
identifier (e.g., item.id or another unique property) by updating the key on the
Pressable in the options.map callback and ensure the data passed to onSelect
remains unchanged (use item.id if available, otherwise derive a stable unique
value from the item).
In `@src/components/Modal/Modal.tsx`:
- Line 21: The Modal component currently types its props as any; replace this
with a concrete prop type (e.g., define an interface or type named ModalProps)
and annotate the Modal signature accordingly to restore type safety: include
isOpen: boolean, onClose: () => void, and children: React.ReactNode (or
ReactElement) as required/optional per usage, and export/apply the ModalProps
type (or use React.FC<ModalProps>) so IDEs and the compiler enforce correct prop
usage for Modal, isOpen, onClose and children.
- Around line 24-30: The outer backdrop View inside the Modal component (wrapped
by TouchableWithoutFeedback with onClose) has only { flex: 1 } so it's
invisible; update the outer View's style (the View wrapping the inner
TouchableWithoutFeedback/children) to include a semi-transparent dark background
(e.g., backgroundColor: 'rgba(0,0,0,0.5)') and proper positioning (center the
inner content) so the overlay is visible; keep the inner
TouchableWithoutFeedback/children layout intact so clicks on the backdrop still
call onClose while taps inside the inner View do not.
In `@src/contexts/FormContext.tsx`:
- Around line 15-19: FormContext and FormProvider use any which removes type
safety; define a specific interface (e.g., FormContextValue) describing the
shape of the context (fields, methods like submit/reset, loading flags, etc.),
change createContext to createContext<FormContextValue | null>(null), and update
FormProvider props to accept children: React.ReactNode and value:
FormContextValue (or Partial/FormContextValue | null if appropriate). Also
export the interface/type so consumers can import it and avoid using any in
useContext(FormContext) calls.
In `@src/hooks/useForm.ts`:
- Around line 18-20: handleChange and handleSubmit are recreated each render
causing potential child re-renders; wrap them in React.useCallback to stabilize
references. Update handleChange to useCallback((name, value) => setValues(prev
=> ({ ...prev, [name]: value })), [setValues]) and memoize handleSubmit
similarly with useCallback, including its dependencies (e.g., values, onSubmit
or any validation functions) so the returned handlers from the hook remain
stable when passed through FormProvider.
In `@src/hooks/useModal.ts`:
- Around line 5-15: Update the JSDoc for the useModal hook to accurately reflect
its behavior: remove references to "ESC로 닫기" and "outside click 감지", and instead
document that it manages open/close state and handles Android hardware back
button behavior (with outside click handled in Modal.tsx); reference the hook
name useModal and the Modal.tsx component so maintainers know where
outside-click logic lives.
🪄 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: defaults
Review profile: CHILL
Plan: Pro
Run ID: b643a98d-5ccc-42e2-b1aa-1b0dd5c2df03
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (15)
src/components/Button/Button.tsxsrc/components/Dropdown/Dropdown.tsxsrc/components/Input/Input.tsxsrc/components/Loading/Loading.tsxsrc/components/Modal/Modal.tsxsrc/components/Skeleton/Skeleton.tsxsrc/components/common/Button.tsxsrc/components/common/Input.tsxsrc/contexts/FormContext.tsxsrc/hooks/useDropdown.tssrc/hooks/useForm.tssrc/hooks/useInfiniteScroll.tssrc/hooks/useModal.tssrc/hooks/useToggle.tssrc/utils/validation.ts
💤 Files with no reviewable changes (2)
- src/components/common/Button.tsx
- src/components/common/Input.tsx
There was a problem hiding this comment.
♻️ Duplicate comments (1)
src/contexts/FormContext.tsx (1)
32-33:⚠️ Potential issue | 🟡 MinorGuard
undefinedcontext too, not onlynull.At Line 32,
=== nullmisses cases whereFormProviderpassesundefinedasvalue; consumers (e.g.,Input) can then crash during destructuring. Use a nullish check.💡 Proposed fix
export const useFormContext = () => { const context = useContext(FormContext); - if (context === null) { + if (context == null) { throw new Error('useFormContext must be used within a FormProvider'); } return context; };🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/contexts/FormContext.tsx` around lines 32 - 33, The null-check in useFormContext only tests for === null and misses undefined values; update the guard in useFormContext to use a nullish check (e.g., context == null or context === null || context === undefined) so it throws when the context is null or undefined, ensuring consumers like Input that destructure the context won't crash; locate the useFormContext function and the FormProvider value usage and replace the strict null check with a nullish check to cover both cases.
🧹 Nitpick comments (1)
src/contexts/FormContext.tsx (1)
18-21: Replaceanywith an explicit FormContext contract.At lines 18 and 20, using
anyremoves compile-time guarantees for the{ values, errors, handleChange }shape consumed insrc/components/Input/Input.tsx(line 20). The Input component destructures and uses these properties:
values[name]— string value accesserrors[name]— string or falsy error accesshandleChange(name, text)— called with string name and string valueStrong typing here will prevent silent shape drift and catch mismatches at compile time.
♻️ Proposed typed refactor
-import React, { createContext, useContext } from 'react'; +import React, { createContext, useContext } from 'react'; + +type FormContextValue = { + values: Record<string, string>; + errors: Record<string, string | undefined>; + handleChange: (name: string, value: string) => void; +}; + +type FormProviderProps = { + children: React.ReactNode; + value: FormContextValue; +}; -const FormContext = createContext<any>(null); +const FormContext = createContext<FormContextValue | null>(null); -export const FormProvider = ({ children, value }: any) => { +export const FormProvider = ({ children, value }: FormProviderProps) => { return <FormContext.Provider value={value}>{children}</FormContext.Provider>; }; -export const useFormContext = () => { +export const useFormContext = (): FormContextValue => { const context = useContext(FormContext); - if (context === null) { + if (context == null) { throw new Error('useFormContext must be used within a FormProvider'); } return context; };🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/contexts/FormContext.tsx` around lines 18 - 21, Replace the loose any types on FormContext and FormProvider with a concrete interface describing the form contract used by Input: declare a FormContextType with values: Record<string,string>, errors: Record<string,string|undefined|false>, and handleChange: (name: string, text: string) => void, then use createContext<FormContextType | null>(null) (or provide a safe default) and type the FormProvider props as { children: React.ReactNode; value: FormContextType } so consumers like Input (which reads values[name], errors[name], and calls handleChange(name, text)) get compile-time guarantees.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@src/contexts/FormContext.tsx`:
- Around line 32-33: The null-check in useFormContext only tests for === null
and misses undefined values; update the guard in useFormContext to use a nullish
check (e.g., context == null or context === null || context === undefined) so it
throws when the context is null or undefined, ensuring consumers like Input that
destructure the context won't crash; locate the useFormContext function and the
FormProvider value usage and replace the strict null check with a nullish check
to cover both cases.
---
Nitpick comments:
In `@src/contexts/FormContext.tsx`:
- Around line 18-21: Replace the loose any types on FormContext and FormProvider
with a concrete interface describing the form contract used by Input: declare a
FormContextType with values: Record<string,string>, errors:
Record<string,string|undefined|false>, and handleChange: (name: string, text:
string) => void, then use createContext<FormContextType | null>(null) (or
provide a safe default) and type the FormProvider props as { children:
React.ReactNode; value: FormContextType } so consumers like Input (which reads
values[name], errors[name], and calls handleChange(name, text)) get compile-time
guarantees.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 1de04049-d580-4e3e-82b3-768cfe7506b8
📒 Files selected for processing (5)
src/components/Button/Button.tsxsrc/contexts/FormContext.tsxsrc/hooks/useForm.tssrc/hooks/useInfiniteScroll.tssrc/utils/validation.ts
✅ Files skipped from review due to trivial changes (2)
- src/utils/validation.ts
- src/hooks/useInfiniteScroll.ts
🚧 Files skipped from review as they are similar to previous changes (2)
- src/components/Button/Button.tsx
- src/hooks/useForm.ts
연관된 이슈
체크리스트
기획 및 구현한 내용
Headless Hooks와 공통 UI 컴포넌트를 통합하여 재사용 가능한 상태 관리 및 UI 구조를 구현했습니다.
주요 구현 사항
1. Hooks (로직)
useToggle: boolean 상태 관리 및 toggle 함수 구현useModal: open/close 상태 관리, 하드웨어 뒤로가기 및 outside click 감지useDropdown: open/close 상태, 선택 값 관리useInfiniteScroll: 스크롤 위치 감지, threshold 설정, fetch trigger 함수 연결useForm: values/errors 상태 관리, handleChange, handleSubmit 구현, validation 연동2. Headless Components
Button: onPress, disabled, loading 처리Input: value, onChange, error, placeholder 지원FormProvider: FormContext 생성 및 상태 공유Modal: isOpen, onClose, children 렌더링 및 outside click 처리Dropdown: options, selected, onSelect 이벤트 처리3. Validation Logic
required검사email검사4. Loading / Skeleton
Loading: boolean 기반 표시 처리Skeleton: 재사용 가능한 기본 박스 UI 구조 구현Summary by CodeRabbit
New Features
Chores