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
function useDebugValue<T>(
value: T,
formatter?: (value: T) => any
): void
Parameters
The value to display in the devtools. This can be any type: string, number, object, etc.
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.
Returns
void
Basic Usage
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>;
}
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
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
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
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
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
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
};
}
function useCounter(initialCount = 0) {
const [count, setCount] = useState(initialCount);
// Simple value display
useDebugValue(count);
return [
count,
() => setCount(c => c + 1),
() => setCount(c => c - 1)
];
}
useDebugValue only affects the display in Preact DevTools. It has no effect on production code behavior and minimal performance impact when devtools are closed.
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.
useDebugValue is most useful in custom hooks that are used in multiple places. For component-local state, the regular devtools inspection is usually sufficient.