-
- Notifications
You must be signed in to change notification settings - Fork 2k
feat(frontend): add reliable autofocus to chat message textarea and input component #2180
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
feat(frontend): add reliable autofocus to chat message textarea and input component #2180
Conversation
| @ZainabTravadi is attempting to deploy a commit to the Arc53 Team on Vercel. A member of the Team first needs to authorize it. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR adds autofocus functionality to the chat message textarea and custom Input component to improve user experience by allowing immediate typing without clicking. The implementation uses React useEffect hooks with delayed focus mechanisms to handle re-render timing issues.
Key Changes:
- Replaces simple focus call with an interval-based focus mechanism in MessageInput component
- Adds useEffect-based autofocus to the Input component while keeping native autoFocus prop
- Removes the initial
handleInput()call from MessageInput's mount effect
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
frontend/src/components/MessageInput.tsx | Implements interval-based autofocus with 50ms polling to ensure textarea receives focus after re-renders |
frontend/src/components/Input.tsx | Adds useEffect hook to programmatically focus input element when isAutoFocused prop is true |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const interval = setInterval(() => { | ||
| if (autoFocus && inputRef.current) { | ||
| inputRef.current.focus(); | ||
| | ||
| // Check if focus succeeded | ||
| if (document.activeElement === inputRef.current) { | ||
| clearInterval(interval); | ||
| } | ||
| } | ||
| }, 50); | ||
| | ||
| return () => clearInterval(interval); | ||
| }, [autoFocus]); |
Copilot AI Dec 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The interval created for autofocus is never properly cleaned up. The interval will continue running indefinitely if autoFocus is false or if inputRef.current is null, causing a memory leak. Additionally, if the component unmounts before focus succeeds, the interval will continue attempting to focus a non-existent element.
To fix this, ensure the interval is always cleared in the cleanup function, even if focus never succeeds:
useEffect(() => { if (!autoFocus) return; const interval = setInterval(() => { if (inputRef.current) { inputRef.current.focus(); if (document.activeElement === inputRef.current) { clearInterval(interval); } } }, 50); return () => clearInterval(interval); }, [autoFocus]);| const interval = setInterval(() => { | |
| if (autoFocus && inputRef.current) { | |
| inputRef.current.focus(); | |
| // Check if focus succeeded | |
| if (document.activeElement === inputRef.current) { | |
| clearInterval(interval); | |
| } | |
| } | |
| }, 50); | |
| return () => clearInterval(interval); | |
| }, [autoFocus]); | |
| if (!autoFocus) return; | |
| const interval = setInterval(() => { | |
| if (inputRef.current) { | |
| inputRef.current.focus(); | |
| // Check if focus succeeded | |
| if (document.activeElement === inputRef.current) { | |
| clearInterval(interval); | |
| } | |
| } | |
| }, 50); | |
| return () => clearInterval(interval); | |
| }, [autoFocus]); |
| const interval = setInterval(() => { | ||
| if (autoFocus && inputRef.current) { | ||
| inputRef.current.focus(); | ||
| | ||
| // Check if focus succeeded | ||
| if (document.activeElement === inputRef.current) { | ||
| clearInterval(interval); | ||
| } | ||
| } | ||
| }, 50); | ||
| | ||
| return () => clearInterval(interval); | ||
| }, [autoFocus]); | ||
| | ||
| |
Copilot AI Dec 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The aggressive 50ms interval will attempt to focus the input ~20 times per second until successful. This approach is inefficient and can impact performance, especially on lower-end devices. Consider using a less aggressive interval (e.g., 100-200ms) or a more elegant solution like requestAnimationFrame with a timeout, or simply a single delayed focus with setTimeout.
| const interval = setInterval(() => { | |
| if (autoFocus && inputRef.current) { | |
| inputRef.current.focus(); | |
| // Check if focus succeeded | |
| if (document.activeElement === inputRef.current) { | |
| clearInterval(interval); | |
| } | |
| } | |
| }, 50); | |
| return () => clearInterval(interval); | |
| }, [autoFocus]); | |
| let timeout: ReturnType<typeof setTimeout> | null = null; | |
| if (autoFocus) { | |
| timeout = setTimeout(() => { | |
| if (inputRef.current) { | |
| inputRef.current.focus(); | |
| } | |
| }, 100); | |
| } | |
| return () => { | |
| if (timeout) clearTimeout(timeout); | |
| }; | |
| }, [autoFocus]); |
| useEffect(() => { | ||
| if (autoFocus) inputRef.current?.focus(); | ||
| handleInput(); | ||
| }, []); | ||
| const interval = setInterval(() => { | ||
| if (autoFocus && inputRef.current) { | ||
| inputRef.current.focus(); | ||
| | ||
| // Check if focus succeeded | ||
| if (document.activeElement === inputRef.current) { | ||
| clearInterval(interval); | ||
| } | ||
| } | ||
| }, 50); | ||
| | ||
| return () => clearInterval(interval); | ||
| }, [autoFocus]); |
Copilot AI Dec 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The removal of the handleInput() call from the initial useEffect may cause layout issues on component mount. The handleInput function adjusts the textarea height based on content and window width. Without calling it on mount, the textarea may not have the correct initial height. Consider whether this was an intentional change or an oversight.
frontend/src/components/Input.tsx Outdated
| id={id} | ||
| name={name} | ||
| autoFocus={isAutoFocused} | ||
| autoFocus={isAutoFocused} |
Copilot AI Dec 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Trailing whitespace detected after autoFocus={isAutoFocused}.
| autoFocus={isAutoFocused} | |
| autoFocus={isAutoFocused} |
| // 🔥 Auto-focus logic (the real fix for the issue) | ||
| useEffect(() => { | ||
| if (isAutoFocused && inputRef.current) { | ||
| inputRef.current.focus(); | ||
| } | ||
| }, [isAutoFocused]); | ||
| |
Copilot AI Dec 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The useEffect hook for autofocus is redundant since the native autoFocus prop is already set on line 60. The native autoFocus attribute should be sufficient for most use cases. If there's a specific reason the native behavior doesn't work (e.g., React re-renders), consider removing the native prop to avoid confusion, or add a comment explaining why both are needed. The emoji in the comment is also unprofessional for production code.
| // 🔥 Auto-focus logic (the real fix for the issue) | |
| useEffect(() => { | |
| if (isAutoFocused && inputRef.current) { | |
| inputRef.current.focus(); | |
| } | |
| }, [isAutoFocused]); |
Summary
This PR implements the feature requested in issue #2177 by adding reliable autofocus behavior to the chat message input field. When the page loads or the chat UI mounts, users can immediately begin typing without clicking the input box—improving UX and aligning with expected chat application behavior.
What was happening before
MessageInput.tsx) did not consistently receive focus on initial load.autoFocusprop was defined but never applied reliably to the actual textarea element.What this PR changes
<textarea>using a delayed focus mechanism insideuseEffectto ensure focus persists across initial renders.autoFocusprop (default:true).<Input>component as well (via ref + effect).Why this approach
React components inside DocsGPT’s chat UI re-render during initialization (loading conversations, state hydration). Immediate focusing inside
useEffectcan fire too early. Using a delayed focus resolves the race condition and ensures consistent user experience.Testing
Result
A smoother, more intuitive chat experience: users can start typing right away, without needing to click the input field.