React useFocus Hook Challenge
This challenge asks you to create a custom React hook, useFocus, that tracks whether a given DOM element is currently in focus. This is a fundamental capability for building interactive UI components, enabling features like input validation feedback, conditional styling, or keyboard navigation indicators.
Problem Description
You need to implement a TypeScript React hook named useFocus. This hook will take a React RefObject pointing to a DOM element as an argument and return a boolean value indicating whether that element currently has focus.
Key Requirements:
- Hook Signature: The hook should have the signature
useFocus<T extends HTMLElement>(elementRef: RefObject<T>): boolean. - Focus Tracking: The hook must accurately report
truewhen the referenced element is focused andfalseotherwise. - Event Listeners: You should attach and detach appropriate event listeners to the
windowobject to detect focus and blur events. - Initial State: The hook should correctly initialize its state based on whether the element is already focused when the hook is first mounted.
- Cleanup: Event listeners must be properly removed when the component using the hook unmounts to prevent memory leaks.
Expected Behavior:
- When the referenced element gains focus (e.g., by a user clicking on it or navigating via keyboard), the hook should return
true. - When the referenced element loses focus, the hook should return
false. - If the referenced element is not available (e.g.,
ref.currentis null), the hook should consistently returnfalse.
Edge Cases to Consider:
- What happens if the
elementRefpassed to the hook isnullorundefinedinitially? - How does the hook behave if the element is focused programmatically versus by user interaction?
- Consider scenarios where focus might shift rapidly between elements.
Examples
Example 1:
import React, { useRef } from 'react';
import { useFocus } from './useFocus'; // Assuming your hook is in this file
function InputWithFocusIndicator() {
const inputRef = useRef<HTMLInputElement>(null);
const isFocused = useFocus(inputRef);
return (
<div>
<input
ref={inputRef}
type="text"
placeholder="Type here..."
style={{ border: isFocused ? '2px solid blue' : '1px solid gray' }}
/>
<p>Input is {isFocused ? 'focused' : 'not focused'}</p>
</div>
);
}
Input: The user clicks on the input field.
Output (during interaction): The input field will have a blue border, and the text will display "Input is focused".
Explanation: useFocus detects the focusin event on the input element, updates its internal state to true, and the component re-renders, applying the conditional styling and text. When the user clicks outside the input, useFocus detects focusout and returns false.
Example 2:
import React, { useRef, useEffect } from 'react';
import { useFocus } from './useFocus';
function ButtonWithFocusState() {
const buttonRef = useRef<HTMLButtonElement>(null);
const isFocused = useFocus(buttonRef);
useEffect(() => {
// Programmatically focus the button after 2 seconds
const timer = setTimeout(() => {
buttonRef.current?.focus();
}, 2000);
return () => clearTimeout(timer);
}, []);
return (
<button ref={buttonRef} style={{ backgroundColor: isFocused ? 'yellow' : 'white' }}>
Click Me or I'll Be Focused
</button>
);
}
Input: The ButtonWithFocusState component is rendered. After 2 seconds, the button is programmatically focused.
Output (after 2 seconds): The button's background color changes to yellow.
Explanation: Initially, useFocus returns false. After 2 seconds, buttonRef.current.focus() is called. The useFocus hook detects this change in focus state and updates its return value to true, causing the button to re-render with a yellow background.
Constraints
- The
useFocushook must be implemented using functional components and React Hooks API. - It should be written in TypeScript.
- The hook should not rely on any external libraries for its core functionality.
- Performance is important; the hook should efficiently attach and detach listeners without causing unnecessary re-renders or memory leaks.
Notes
- Consider using
focusinandfocusoutevents on thewindowobject, as they bubble and can capture focus changes even if theref.currentis not directly listening to them. - Remember that
ref.currentcan benullinitially and when the component unmounts. Your hook should handle this gracefully. - Think about how to initialize the hook's state correctly. The element might already be focused when the component mounts.
- The cleanup function returned by
useEffectis crucial for detaching event listeners.