HomeAbout Me

Advanced React APIs: Focus Management

By Daniel Nguyen
Published in React JS
June 18, 2025
2 min read
Advanced React APIs: Focus Management

🎯 Mastering Focus Management in React: flushSync, Inline Editing, and Accessibility

When building interactive UIs, especially with forms and dynamic elements, managing focus properly becomes critical. It’s not just about making things “work”—it’s about accessibility, keyboard navigation, and creating a smooth experience for everyone.

Let’s walk through common focus pitfalls and learn how to solve them using flushSync, ref, and smart event handling with a practical real-world example.


🚦 Why Focus Management Matters

Good focus management ensures that:

  • Keyboard users can navigate efficiently
  • Screen reader users stay oriented
  • Visual users have a seamless experience

Example: Conditional Rendering and Focus

Consider this component:

function MyComponent() {
const [show, setShow] = useState(false)
return (
<div>
<button onClick={() => setShow(true)}>Show</button>
{show ? <input /> : null}
</div>
)
}

When the user clicks the Show button, the input appears. Naturally, you’d expect the input to be focused so the user can type immediately. But…

❌ The Naive Approach

const inputRef = useRef<HTMLInputElement>(null)
<button
onClick={() => {
setShow(true)
inputRef.current?.focus() // ❌ Doesn't work
}}
>
Show
</button>

This doesn’t work because React batches state updates. The DOM isn’t updated when setShow(true) is called, so the input doesn’t yet exist.


✅ The Fix: flushSync

To handle this properly, we can use flushSync from react-dom to flush the state update synchronously, ensuring the DOM updates before we call focus().

import { flushSync } from 'react-dom'
function MyComponent() {
const inputRef = useRef<HTMLInputElement>(null)
const [show, setShow] = useState(false)
return (
<div>
<button
onClick={() => {
flushSync(() => {
setShow(true)
})
inputRef.current?.focus()
}}
>
Show
</button>
{show ? <input ref={inputRef} /> : null}
</div>
)
}

💡 Use flushSync sparingly. It’s a performance de-optimization, but it’s perfect for situations like managing focus where user experience matters most.


✏️ Real Example: Editable Text with Focus Transitions

Let’s now build an <EditableText /> component that:

  • Renders a button with text
  • On click, replaces it with an input
  • On blur, Enter, or Escape, exits edit mode and goes back to the button
  • Selects all text when entering edit mode
  • Properly manages tab order and focus

🧠 Why This Is Tricky

When the user clicks the button, it disappears and is replaced with the input. If we don’t manage focus, the user is left staring at a blank screen with no cursor in sight.


🛠️ Implementation

import { flushSync } from 'react-dom'
import React, { useRef, useState } from 'react'
function EditableText() {
const [isEditing, setIsEditing] = useState(false)
const [value, setValue] = useState('Click to edit me')
const inputRef = useRef<HTMLInputElement>(null)
const startEditing = () => {
flushSync(() => setIsEditing(true))
requestAnimationFrame(() => {
inputRef.current?.focus()
inputRef.current?.select()
})
}
const stopEditing = (submit: boolean) => {
setIsEditing(false)
if (submit && inputRef.current) {
setValue(inputRef.current.value)
}
}
return (
<div style={{ display: 'flex', gap: '8px', alignItems: 'center' }}>
<button>Before</button>
{isEditing ? (
<input
ref={inputRef}
defaultValue={value}
onBlur={() => stopEditing(true)}
onKeyDown={(e) => {
if (e.key === 'Enter') stopEditing(true)
if (e.key === 'Escape') stopEditing(false)
}}
/>
) : (
<button onClick={startEditing}>{value}</button>
)}
<button>After</button>
</div>
)
}

🧪 Try This:

  1. Tab through the buttons and into the editable text.
  2. Press Enter → input should focus and select all text.
  3. Type, then press Enter, Escape, or click away.

✅ Key Behaviors Covered:

  • Focus & Selection on click (with flushSync + requestAnimationFrame)
  • Keyboard support: Enter submits, Escape cancels
  • Blur fallback: auto-submit when leaving the field

🦽 Accessibility Matters

This solution improves usability and accessibility:

  • Screen readers know what to read (because focus is on the right element)
  • Keyboard users can navigate smoothly
  • All interactions are clear and responsive

📌 Takeaways

  • Use ref + focus() to control where focus goes
  • React state updates are batched, so newly rendered elements aren’t available immediately
  • Use flushSync() when you need DOM to update synchronously
  • Always manage focus in complex UIs (especially dynamic inputs)
  • Keyboard interactions (Enter, Escape, Tab) should be handled explicitly

📚 Resources

  • React Docs – flushSync
  • React A11y Guide
  • Example inspiration: Remix Trellix

By thoughtfully managing focus, you’re not just fixing bugs—you’re creating a smooth, delightful, and accessible experience for every user.

Happy coding! 🚀



Tags

#AdvancedReactAPIs

Share

Previous Article
Advanced React APIs: Imperative Handles

Table Of Contents

1
🚦 Why Focus Management Matters
2
✅ The Fix: flushSync
3
✏️ Real Example: Editable Text with Focus Transitions
4
🛠️ Implementation
5
🦽 Accessibility Matters
6
📌 Takeaways
7
📚 Resources

Related Posts

Advanced React APIs: Sync Exernal Store
June 19, 2025
2 min
© 2025, All Rights Reserved.
Powered By

Quick Links

About Me

Legal Stuff

Social Media