Skip to content

Conversation

kimgho
Copy link
Contributor

@kimgho kimgho commented Aug 23, 2025

✅ Linked Issue

🔍 What I did

  • 필드 선택 시 다음 필드를 자동으로 포커스 및 클릭하도록 수정
    • SelectField.tsx에 ref props 추가
    • requestAnimationFramesetTimeout으로 다음 필드를 자동으로 포커스 및 클릭하도록 추가

Summary by CodeRabbit

  • New Features
    • Streamlined application form flow: after choosing Area, the Exam Date field auto-focuses and opens; after selecting Exam Date, School auto-opens; after selecting School, Lunch auto-opens.
    • Auto-scroll keeps the active field in view within horizontally scrollable sections, improving touch and keyboard navigation.
    • Faster sequential selection reduces taps/clicks and prevents missed fields during form completion.

Copy link

vercel bot commented Aug 23, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
mosu-client Ready Ready Preview Comment Aug 23, 2025 10:20am

Copy link

coderabbitai bot commented Aug 23, 2025

Walkthrough

Implements ref-forwarding in SelectField and adds scroll-aware focus progression across examDate → schoolName → lunch fields in the Apply form. On each selection, downstream fields reset and the next field is focused/opened, ensuring visibility within horizontally scrollable containers.

Changes

Cohort / File(s) Summary
Apply form flow updates
mosu-app/src/features/apply/ui/SelectExamField.tsx
Adds refs for examDate/schoolName/lunch, introduces scrollAfterSelect to focus/scroll/click next SelectField within horizontal scroll containers, and wires cascading resets and next-field opening on each selection.
Shared SelectField ref-forwarding
mosu-app/src/shared/components/SelectField/SelectField.tsx
Updates SelectFieldProps to accept an optional ref and forwards it to SelectTrigger; minor formatting tweaks; no change to value handling or error logic.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant U as User
  participant AF as ApplyForm (SelectExamField)
  participant SF as SelectField
  participant SA as scrollAfterSelect
  participant DOM as Scroll Container (.overflow-x-auto)

  U->>SF: Select Area
  SF-->>AF: onValueChange(area)
  AF->>AF: Reset examDate, schoolName, lunch
  AF->>SA: scrollAfterSelect(examDateRef)
  SA->>DOM: Ensure visibility (scroll if needed)
  SA-->>SF: Focus + click examDate

  U->>SF: Select examDate
  SF-->>AF: onValueChange(examDate)
  AF->>AF: Reset schoolName, lunch
  AF->>SA: scrollAfterSelect(schoolNameRef)
  SA->>DOM: Ensure visibility (scroll if needed)
  SA-->>SF: Focus + click schoolName

  U->>SF: Select schoolName
  SF-->>AF: onValueChange(schoolName)
  AF->>AF: Reset lunch
  AF->>SA: scrollAfterSelect(lunchRef)
  SA->>DOM: Ensure visibility (scroll if needed)
  SA-->>SF: Focus + click lunch
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Assessment against linked issues

Objective Addressed Explanation
Move focus to next field after selection and handle mobile small-width horizontal scroll (#358)

Suggested labels

우선순위 (상)

Suggested reviewers

  • toothlessdev

Poem

A hop, a skip, a focused leap—
From date to school, then lunch we keep.
I nudge the fields with gentle scroll,
So narrow screens can see the whole.
Tap-tap! The dropdowns bloom in line—
A bunny’s flow, precise and fine. 🐇✨

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix#358

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.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

Messages
📖 ✅ PR 제목에 이슈 번호가 포함되어 있습니다.
📖 ✅ PR에 Reviewers가 지정되어 있습니다.
📖 ✅ PR에 라벨이 지정되어 있습니다.
📖 ✅ PR에 Assignees가 지정되어 있습니다.
📖 ✅ package.json에 변경사항이 없습니다.
📖 ✅ 브랜치 이름 'fix#358'이 컨벤션을 따릅니다.
📖 ✅ TypeScript 컴파일이 성공적으로 완료되었습니다.
📖 ✅ ESLint 검사 결과 문제가 없습니다.

📝 추가 및 변경된 파일

총 2개 파일 변경

└── 📂 mosu-app/
    └── 📂 src/
        ├── 📂 features/
        │   └── 📂 apply/
        │       └── 📂 ui/
        │           └── ⚛️ SelectExamField.tsx
        └── 📂 shared/
            └── 📂 components/
                └── 📂 SelectField/
                    └── ⚛️ SelectField.tsx

Generated by 🚫 dangerJS against abd0491

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
mosu-app/src/shared/components/SelectField/SelectField.tsx (1)

15-27: Refs are not forwarded — current implementation will not receive caller refs

React strips the ref prop from props and passes it as a second argument only when the component is wrapped with React.forwardRef. As written:

  • Adding ref?: … to SelectFieldProps (Line 26) does nothing at runtime.
  • Destructuring ref from props (Line 40) will always yield undefined.
  • Passing that to <SelectTrigger ref={ref} …> (Line 71) therefore never connects the caller’s ref to the trigger.

Net effect: all uses like <SelectField ref={examDateRef} …/> in SelectExamField won’t work; focus/click chaining won’t fire. Fix by converting SelectField to a forwardRef and removing ref from the props interface.

Apply this diff:

@@
-export interface SelectFieldProps<T extends FieldValues> extends React.ComponentProps<typeof SelectPrimitive.Root> {
+export interface SelectFieldProps<T extends FieldValues> extends React.ComponentProps<typeof SelectPrimitive.Root> {
   id: string;
   placeholder: string;
   triggerClassName?: string;
   options?: Array<Option>;
   name?: FieldPath<T>;
   register?: UseFormRegister<T>;
   watch?: UseFormWatch<T>;
   error?: string | boolean;
   shouldDisplayErrorMessage?: boolean;
   onValueChange?: (value: string, metadata?: unknown) => void;
-  ref?: React.Ref<React.ComponentRef<typeof SelectTrigger>>;
 }
@@
-export const SelectField = <T extends FieldValues>({
-    id,
-    triggerClassName,
-    placeholder,
-    options,
-    name,
-    register,
-    watch: externalWatch,
-    error,
-    shouldDisplayErrorMessage = false,
-    onValueChange,
-    ref,
-    ...props
-}: SelectFieldProps<T>) => {
+type TriggerEl = React.ComponentRef<typeof SelectTrigger>;
+
+const SelectFieldInner = <T extends FieldValues>(
+    {
+        id,
+        triggerClassName,
+        placeholder,
+        options,
+        name,
+        register,
+        watch: externalWatch,
+        error,
+        shouldDisplayErrorMessage = false,
+        onValueChange,
+        ...props
+    }: SelectFieldProps<T>,
+    ref: React.Ref<TriggerEl>
+) => {
@@
-    return (
-        <Select {...props} onValueChange={onValueChangeHandler} name={registerProps.name} value={currentValue}>
-            <SelectTrigger ref={ref} className={cn(error ? "border-red-400" : "", triggerClassName)}>
+    return (
+        <Select {...props} onValueChange={onValueChangeHandler} name={registerProps.name} value={currentValue}>
+            <SelectTrigger ref={ref} className={cn(error ? "border-red-400" : "", triggerClassName)}>
                 <SelectValue placeholder={placeholder} />
             </SelectTrigger>
             <SelectContent id={id}>
@@
-        </Select>
-    );
-};
+        </Select>
+    );
+};
+
+export const SelectField = forwardRef(SelectFieldInner) as <
+    T extends FieldValues
+>(
+    p: SelectFieldProps<T> & { ref?: React.Ref<TriggerEl> }
+) => JSX.Element;

Notes:

  • This preserves the generic T for form typing while enabling true ref forwarding to the internal SelectTrigger.
  • No external API change for callers; they can keep using ref={...}.

Also applies to: 40-42, 71-71

♻️ Duplicate comments (1)
mosu-app/src/features/apply/ui/SelectExamField.tsx (1)

27-29: Downstream refs won’t be populated until SelectField forwards the ref

The new refs here are correct, but they currently receive null because SelectField doesn’t forward refs (see prior comment). After applying the forwardRef fix, these lines will work as intended.

Also applies to: 108-108, 125-125, 143-143

🧹 Nitpick comments (5)
mosu-app/src/shared/components/SelectField/SelectField.tsx (1)

3-3: Import forwardRef for proper ref forwarding

You’ll need forwardRef here once we fix the ref wiring below.

Apply this diff:

-import { useCallback, useMemo } from "react";
+import { useCallback, useMemo, forwardRef } from "react";
mosu-app/src/features/apply/ui/SelectExamField.tsx (4)

37-71: Simplify sequencing; avoid nested rAF + setTimeout(0) and make scroll delay configurable

The double-scheduling (rAF + setTimeout 0) is redundant and the hard-coded 200ms can be brittle across devices. Recommend:

  • Use a single rAF for layout stability after state updates.
  • Hoist the click-delay into a constant for easier tuning.
  • Guard early, and reuse locals to reduce repeated ref.current lookups.

Apply this diff:

-    const scrollAfterSelect = (ref: React.RefObject<HTMLButtonElement>) => {
-        requestAnimationFrame(() => {
-            setTimeout(() => {
-                if (ref.current) {
-                    ref.current.focus({ preventScroll: true });
-
-                    const container = ref.current.closest(".overflow-x-auto");
-                    const rect = ref.current.getBoundingClientRect();
-                    const containerRect = container?.getBoundingClientRect();
-
-                    let needsScroll = false;
-                    if (containerRect) {
-                        needsScroll = rect.left < containerRect.left || rect.right > containerRect.right;
-                    } else {
-                        needsScroll = rect.left < 0 || rect.right > window.innerWidth;
-                    }
-
-                    if (needsScroll) {
-                        ref.current.scrollIntoView({
-                            behavior: "smooth",
-                            block: "nearest",
-                            inline: "center",
-                        });
-
-                        setTimeout(() => {
-                            ref.current?.click();
-                        }, 200);
-                    } else {
-                        ref.current.click();
-                    }
-                }
-            }, 0);
-        });
-    };
+    const SCROLL_CLICK_DELAY_MS = 160;
+    const scrollAfterSelect = (ref: React.RefObject<HTMLButtonElement>) => {
+        requestAnimationFrame(() => {
+            const el = ref.current;
+            if (!el) return;
+
+            el.focus({ preventScroll: true });
+
+            const container = el.closest(".overflow-x-auto") as HTMLElement | null;
+            const rect = el.getBoundingClientRect();
+            const containerRect = container?.getBoundingClientRect();
+
+            const needsScroll = containerRect
+                ? rect.left < containerRect.left || rect.right > containerRect.right
+                : rect.left < 0 || rect.right > window.innerWidth;
+
+            if (needsScroll) {
+                el.scrollIntoView({ behavior: "smooth", block: "nearest", inline: "center" });
+                window.setTimeout(() => el?.click(), SCROLL_CLICK_DELAY_MS);
+            } else {
+                el.click();
+            }
+        });
+    };

97-103: Option loading race: consider deferring open until options are ready

After changing area, you immediately try to focus/open examDate. If useGetExamByExamArea hasn’t populated options yet, the dropdown will open empty. Consider:

  • Opening only when !isLoading && !isPlaceholderData, or
  • Opening immediately but re-opening once data arrives.

If you choose to defer open:

                 onValueChange={() => {
                     resetField(`examApplication.${index}.examDate`);
                     resetField(`examApplication.${index}.schoolName`);
                     resetField(`examApplication.${index}.lunch`);
-                    scrollAfterSelect(examDateRef as React.RefObject<HTMLButtonElement>);
+                    if (!isLoading && !isPlaceholderData) {
+                        scrollAfterSelect(examDateRef as React.RefObject<HTMLButtonElement>);
+                    }
                 }}

And add an effect to open when data flips ready (optional).


132-137: Guard against invalid metadata before setting examId

metadata is typed as unknown. Number(metadata) can yield NaN, resulting in an invalid examId being written to the form. Add a simple guard.

Apply this diff:

-                onValueChange={(_, metadata) => {
-                    const examId = Number(metadata);
-                    setValue(`examApplication.${index}.examId`, examId);
+                onValueChange={(_, metadata) => {
+                    const examId = typeof metadata === "number" ? metadata : Number(metadata);
+                    if (Number.isFinite(examId)) {
+                        setValue(`examApplication.${index}.examId`, examId);
+                    }
                     resetField(`examApplication.${index}.lunch`);
                     scrollAfterSelect(lunchRef as React.RefObject<HTMLButtonElement>);
                 }}

88-90: Minor UX: keep RemoveButton tabbable but out of the auto-advance path

With focus auto-advancing across fields, it’s easy to skip the remove control. If this fieldset is keyboard-heavy, consider:

  • Giving the RemoveButton a consistent placement and a tooltip/label, and
  • Ensuring it’s reachable via Shift+Tab from the first select.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between bfff144 and abd0491.

📒 Files selected for processing (2)
  • mosu-app/src/features/apply/ui/SelectExamField.tsx (5 hunks)
  • mosu-app/src/shared/components/SelectField/SelectField.tsx (5 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
mosu-app/src/features/apply/ui/SelectExamField.tsx (1)
mosu-app/src/features/apply/services/getExamByExamArea.ts (1)
  • useGetExamByExamArea (34-41)
mosu-app/src/shared/components/SelectField/SelectField.tsx (1)
mosu-app/src/shared/components/SelectField/index.ts (1)
  • SelectFieldProps (2-2)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Run Unit & Integration Tests
  • GitHub Check: Run DangerJS
🔇 Additional comments (1)
mosu-app/src/shared/components/SelectField/SelectField.tsx (1)

70-70: Controlled vs. uncontrolled value — confirm currentValue is always defined or intentionally undefined

value={currentValue} will make the Select controlled when currentValue is a string and controlled-with-undefined otherwise. Verify that externalWatch(name) returns undefined initially and does not flip between undefined and a value in the same render pass; otherwise you may see React warnings about switching between controlled/uncontrolled inputs. If needed, coalesce to "" or explicitly pass defaultValue.

Copy link

📚 Storybook이 Chromatic에 배포되었습니다!

@kimgho kimgho merged commit 11a834a into main Aug 23, 2025
10 checks passed
@kimgho kimgho deleted the fix#358 branch August 23, 2025 10:28
@github-project-automation github-project-automation bot moved this to 완료 in mosu-client Aug 23, 2025
@toothlessdev
Copy link
Collaborator

25/8/23

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: 완료
Development

Successfully merging this pull request may close these issues.

[✍️ 기능 변경 요청] 모의수능 신청 - 시험장 선택에서 필드 선택시 다음 필드로 포커스 이동하기
2 participants