Hone logo
Hone
Problems

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:

  1. Hook Signature: The hook should have the signature useFocus<T extends HTMLElement>(elementRef: RefObject<T>): boolean.
  2. Focus Tracking: The hook must accurately report true when the referenced element is focused and false otherwise.
  3. Event Listeners: You should attach and detach appropriate event listeners to the window object to detect focus and blur events.
  4. Initial State: The hook should correctly initialize its state based on whether the element is already focused when the hook is first mounted.
  5. 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.current is null), the hook should consistently return false.

Edge Cases to Consider:

  • What happens if the elementRef passed to the hook is null or undefined initially?
  • 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 useFocus hook 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 focusin and focusout events on the window object, as they bubble and can capture focus changes even if the ref.current is not directly listening to them.
  • Remember that ref.current can be null initially 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 useEffect is crucial for detaching event listeners.
Loading editor...
typescript