Hone logo
Hone
Problems

Recreate React's useDebugValue Hook

React provides a built-in hook called useDebugValue that allows you to display a custom label for your custom hooks in the React DevTools. This challenge asks you to implement your own version of useDebugValue to understand how it works under the hood and to reinforce your knowledge of React's internal mechanisms.

Problem Description

Your task is to create a custom hook named useDebugValue in TypeScript that mirrors the functionality of React's built-in useDebugValue. This hook should accept a value and optionally a formatting function. When the custom hook using useDebugValue is rendered, the provided value (or its formatted representation) should be visible in the React DevTools.

Key Requirements:

  • useDebugValue Function Signature:
    • It should accept two arguments:
      • value: The value to be displayed. This can be of any type.
      • formatFn (optional): A function that takes the value and returns a string representation. If not provided, the value itself will be displayed (or its default string representation).
  • Integration with React DevTools: The primary goal is for the hook to make the provided value visible in the React DevTools.
  • Custom Hook Context: Your useDebugValue should be designed to be used within other custom React hooks.

Expected Behavior:

When you use your useDebugValue within a custom hook, and that custom hook is rendered in your React application, you should be able to inspect the component in React DevTools and see the debug value associated with your custom hook.

Edge Cases:

  • No formatFn provided: The hook should correctly display the raw value.
  • formatFn returns different types: While ideally it should return a string, consider how to handle cases where formatFn might return non-string values (though for this challenge, we'll assume it returns a string or a value that can be coerced to a string).

Examples

Since this hook's output is observed in the React DevTools, we'll describe the expected behavior in DevTools.

Example 1: Basic Usage

Consider a custom hook useMyCounter that simply returns a count.

// Hypothetical custom hook using your useDebugValue
function useMyCounter(initialValue: number) {
  const [count, setCount] = React.useState(initialValue);
  // --- Using your custom useDebugValue ---
  // Assume your useDebugValue is imported as 'debug'
  debug(count); // This should display 'count' in DevTools
  // -------------------------------------

  return { count, setCount };
}

// In your component:
function CounterComponent() {
  const { count, setCount } = useMyCounter(0);
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

Expected Behavior in React DevTools:

When inspecting CounterComponent in React DevTools, you would see useMyCounter listed. Associated with useMyCounter would be the label 0 (the initial count value). When you click "Increment", the label should update to 1, then 2, and so on.

Example 2: With a Formatting Function

Consider a custom hook useUserStatus that returns a user object.

interface User {
  name: string;
  id: number;
  isActive: boolean;
}

// Hypothetical custom hook using your useDebugValue
function useUserStatus(user: User) {
  // --- Using your custom useDebugValue ---
  // Assume your useDebugValue is imported as 'debug'
  debug(user, (u: User) => `User: ${u.name} (${u.isActive ? 'Active' : 'Inactive'})`);
  // -------------------------------------

  return user;
}

// In your component:
function UserProfileComponent() {
  const mockUser: User = { name: "Alice", id: 1, isActive: true };
  const user = useUserStatus(mockUser);
  return (
    <div>
      <h2>{user.name}</h2>
      <p>Status: {user.isActive ? 'Active' : 'Inactive'}</p>
    </div>
  );
}

Expected Behavior in React DevTools:

When inspecting UserProfileComponent in React DevTools, you would see useUserStatus listed. The associated label should be User: Alice (Active).

Example 3: Handling Null/Undefined

Consider a hook that might conditionally return a value.

// Hypothetical custom hook using your useDebugValue
function useOptionalValue() {
  const [value, setValue] = React.useState<string | null>(null);

  // --- Using your custom useDebugValue ---
  // Assume your useDebugValue is imported as 'debug'
  debug(value, (v: string | null) => `Current: ${v ?? 'Not Set'}`);
  // -------------------------------------

  const setOptional = (val: string) => setValue(val);
  const clearOptional = () => setValue(null);

  return { setOptional, clearOptional };
}

// In your component:
function OptionalComponent() {
  const { setOptional, clearOptional } = useOptionalValue();
  return (
    <div>
      <button onClick={() => setOptional("Some Data")}>Set Value</button>
      <button onClick={clearOptional}>Clear Value</button>
    </div>
  );
}

Expected Behavior in React DevTools:

Initially, the label for useOptionalValue should be Current: Not Set. After clicking "Set Value", it should change to Current: Some Data. After clicking "Clear Value", it should revert to Current: Not Set.

Constraints

  • You must implement useDebugValue using only standard JavaScript and React hooks (e.g., useState, useEffect, useRef). You are not allowed to use any third-party libraries for this specific implementation.
  • Your useDebugValue function should not cause any rendering cycles on its own. It's a passive display mechanism.
  • The implementation should be efficient and not introduce significant overhead.

Notes

  • Think about how React DevTools hooks into custom hooks. The mechanism for this is typically through a special global API or a similar internal bridge. For this exercise, you'll need to simulate this bridge.
  • Consider using useRef to store the formatting function if it changes over time, to avoid re-running the formatting logic unnecessarily if the function reference itself hasn't changed.
  • The key is to understand how custom hooks can communicate information to the React development environment. Your implementation should mimic this communication.
Loading editor...
typescript