> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/preactjs/preact/llms.txt
> Use this file to discover all available pages before exploring further.

# useDebugValue

> Displays a custom label for custom hooks in browser devtools

`useDebugValue` displays a custom label for custom hooks in the Preact DevTools panel. This helps with debugging by providing meaningful information about custom hook state.

## Signature

```typescript theme={null}
function useDebugValue<T>(
  value: T,
  formatter?: (value: T) => any
): void
```

## Parameters

<ParamField path="value" type="T" required>
  The value to display in the devtools. This can be any type: string, number, object, etc.
</ParamField>

<ParamField path="formatter" type="(value: T) => any" optional>
  An optional function to format the value before displaying it in devtools. This is useful for expensive formatting operations that should only run when the devtools are open and inspecting the component.
</ParamField>

## Returns

`void`

## Basic Usage

```jsx theme={null}
import { useState } from 'preact/hooks';
import { useDebugValue } from 'preact/hooks';

function useOnlineStatus() {
  const [isOnline, setIsOnline] = useState(navigator.onLine);

  useEffect(() => {
    const handleOnline = () => setIsOnline(true);
    const handleOffline = () => setIsOnline(false);

    window.addEventListener('online', handleOnline);
    window.addEventListener('offline', handleOffline);

    return () => {
      window.removeEventListener('online', handleOnline);
      window.removeEventListener('offline', handleOffline);
    };
  }, []);

  // Display in devtools
  useDebugValue(isOnline ? 'Online' : 'Offline');

  return isOnline;
}

function App() {
  const isOnline = useOnlineStatus();
  return <div>{isOnline ? 'Connected' : 'Disconnected'}</div>;
}
```

## With Formatter

```jsx theme={null}
function useUserStatus(userId) {
  const [user, setUser] = useState(null);

  useEffect(() => {
    fetchUser(userId).then(setUser);
  }, [userId]);

  // Formatter only runs when devtools inspect the component
  useDebugValue(user, (u) => {
    if (!u) return 'Loading...';
    return `${u.name} (${u.status})`;
  });

  return user;
}
```

## Custom Hook Debugging

```jsx theme={null}
function useLocalStorage(key, initialValue) {
  const [storedValue, setStoredValue] = useState(() => {
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      console.error(error);
      return initialValue;
    }
  });

  const setValue = (value) => {
    try {
      const valueToStore =
        value instanceof Function ? value(storedValue) : value;
      setStoredValue(valueToStore);
      window.localStorage.setItem(key, JSON.stringify(valueToStore));
    } catch (error) {
      console.error(error);
    }
  };

  // Show the storage key and current value in devtools
  useDebugValue({ key, value: storedValue });

  return [storedValue, setValue];
}

function Component() {
  const [name, setName] = useLocalStorage('name', 'John');
  return <input value={name} onChange={e => setName(e.target.value)} />;
}
```

## Async State Debugging

```jsx theme={null}
function useFetch(url) {
  const [state, setState] = useState({
    data: null,
    loading: true,
    error: null
  });

  useEffect(() => {
    let cancelled = false;

    setState({ data: null, loading: true, error: null });

    fetch(url)
      .then(res => res.json())
      .then(data => {
        if (!cancelled) {
          setState({ data, loading: false, error: null });
        }
      })
      .catch(error => {
        if (!cancelled) {
          setState({ data: null, loading: false, error });
        }
      });

    return () => {
      cancelled = true;
    };
  }, [url]);

  // Display fetch status in devtools
  useDebugValue(
    state,
    ({ loading, error, data }) => {
      if (loading) return 'Loading...';
      if (error) return `Error: ${error.message}`;
      if (data) return `Loaded ${Object.keys(data).length} keys`;
      return 'Idle';
    }
  );

  return state;
}
```

## Complex State Debugging

```jsx theme={null}
function useForm(initialValues, validate) {
  const [values, setValues] = useState(initialValues);
  const [errors, setErrors] = useState({});
  const [touched, setTouched] = useState({});
  const [isSubmitting, setIsSubmitting] = useState(false);

  // Format complex state for devtools
  useDebugValue(
    { values, errors, touched, isSubmitting },
    (state) => {
      const errorCount = Object.keys(state.errors).length;
      const touchedCount = Object.keys(state.touched).length;
      const status = state.isSubmitting ? 'submitting' : 'idle';

      return `Form: ${status}, ${errorCount} errors, ${touchedCount} touched`;
    }
  );

  return {
    values,
    errors,
    touched,
    isSubmitting,
    setValues,
    setErrors,
    setTouched,
    setIsSubmitting
  };
}
```

## Timer Debugging

```jsx theme={null}
function useTimer(initialTime = 0) {
  const [time, setTime] = useState(initialTime);
  const [isRunning, setIsRunning] = useState(false);

  useEffect(() => {
    if (!isRunning) return;

    const interval = setInterval(() => {
      setTime(t => t + 1);
    }, 1000);

    return () => clearInterval(interval);
  }, [isRunning]);

  // Format time as MM:SS in devtools
  useDebugValue(time, (t) => {
    const minutes = Math.floor(t / 60);
    const seconds = t % 60;
    return `${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
  });

  return {
    time,
    isRunning,
    start: () => setIsRunning(true),
    stop: () => setIsRunning(false),
    reset: () => {
      setTime(initialTime);
      setIsRunning(false);
    }
  };
}
```

## API State Machine

```jsx theme={null}
function useApiState(initialState = 'idle') {
  const [state, setState] = useState(initialState);
  const [data, setData] = useState(null);
  const [error, setError] = useState(null);

  // Show state machine status
  useDebugValue(
    { state, hasData: !!data, hasError: !!error },
    ({ state, hasData, hasError }) => {
      const flags = [];
      if (hasData) flags.push('data');
      if (hasError) flags.push('error');
      return `${state}${flags.length ? ` [${flags.join(', ')}]` : ''}`;
    }
  );

  return {
    state,
    data,
    error,
    setState,
    setData,
    setError
  };
}
```

## Without Formatter

```jsx theme={null}
function useCounter(initialCount = 0) {
  const [count, setCount] = useState(initialCount);

  // Simple value display
  useDebugValue(count);

  return [
    count,
    () => setCount(c => c + 1),
    () => setCount(c => c - 1)
  ];
}
```

<Note>
  `useDebugValue` only affects the display in Preact DevTools. It has no effect on production code behavior and minimal performance impact when devtools are closed.
</Note>

<Note>
  Use the optional `formatter` parameter for expensive formatting operations. The formatter function will only be called when devtools are open and inspecting the component, avoiding unnecessary computation.
</Note>

<Note>
  `useDebugValue` is most useful in custom hooks that are used in multiple places. For component-local state, the regular devtools inspection is usually sufficient.
</Note>
