Home
React
Advanced React APIs: Focus Management
June 18, 2025
2 min

Table Of Contents

01
🚦 Why Focus Management Matters
02
✅ The Fix: flushSync
03
✏️ Real Example: Editable Text with Focus Transitions
04
🛠️ Implementation
05
🦽 Accessibility Matters
06
📌 Takeaways
07
📚 Resources

🎯 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

Related Posts

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

Social Media

githublinkedinyoutube