toolbench
Reusable development tools library for React applications with debugging and console utilities.
React DevTools is great. But it's in a separate panel. I have to switch tabs. Sometimes I forget to close it and ship it to production (okay, that only happened once).
I wanted dev tools IN my app. Right there. Visible while I'm looking at the thing I'm debugging.
The Floating Dev Panel
Toolbench adds a little floating panel to your app (in development only, obviously). It shows:
- •State: All useState values, updating in real-time
- •Props: What's being passed to the current component
- •Renders: How many times the component has rendered
- •Performance: Last render time in milliseconds
The panel is draggable, collapsible, and disappears completely in production builds.
The Console Enhancements
I added some utilities that make console.log actually useful:
import { devLog, devTable, devGroup } from 'toolbench';
// Pretty-printed with timestamp and component name
devLog('User clicked button', { userId: 123 });
// Table format for arrays of objects
devTable(users);
// Grouped logs that can be collapsed
devGroup('API Response', () => {
devLog('Status', response.status);
devLog('Data', response.data);
});All of these NO-OP in production. You can leave them in your code without worrying about console spam in prod.
The "Why Did This Render?" Feature
React re-renders are tricky. Sometimes you're not sure WHY something re-rendered. Toolbench tracks this:
useWhyDidYouRender('MyComponent', { props, state });When the com...
Built With
Impact
Framework
React 18+
Platform
npm
Under the Hood
A peek at the implementation — the kind of code that powers toolbench.
1// The floating dev panel that I can't live without
2// Shows state, props, render count, and performance in-app
3
4import { useState, useEffect, useRef } from 'react';
5
6interface DevToolsProps {
7 position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
8 initiallyExpanded?: boolean;
9}
10
11export function DevTools({
12 position = 'bottom-right',
13 initiallyExpanded = false
14}: DevToolsProps) {
15 const [expanded, setExpanded] = useState(initiallyExpanded);
16 const [activePanel, setActivePanel] = useState<'state' | 'perf' | 'renders'>('state');
17 const [isDragging, setIsDragging] = useState(false);
18 const [pos, setPos] = useState(getInitialPosition(position));
19
20 // Don't render in production
21 if (process.env.NODE_ENV === 'production') return null;
22
23 return (
24 <Draggable position={pos} onDrag={setPos}>
25 <div className="toolbench-panel">
26 <header className="toolbench-header" onClick={() => setExpanded(e => !e)}>
27 <span>🔧 DevTools</span>
28 <span>{expanded ? '−' : '+'}</span>
29 </header>
30
31 {expanded && (
32 <>
33 <nav className="toolbench-tabs">
34 <Tab active={activePanel === 'state'} onClick={() => setActivePanel('state')}>
35 State
36 </Tab>
37 <Tab active={activePanel === 'perf'} onClick={() => setActivePanel('perf')}>
38 Perf
39 </Tab>
40 <Tab active={activePanel === 'renders'} onClick={() => setActivePanel('renders')}>
41 Renders
42 </Tab>
43 </nav>
44
45 <div className="toolbench-content">
46 {activePanel === 'state' && <StatePanel />}
47 {activePanel === 'perf' && <PerfPanel />}
48 {activePanel === 'renders' && <RenderPanel />}
49 </div>
50 </>
51 )}
52 </div>
53 </Draggable>
54 );
55}
56
57// The hook that tracks why components re-render
58export function useWhyDidYouRender(componentName: string, props: Record<string, unknown>) {
59 const prevProps = useRef(props);
60
61 useEffect(() => {
62 if (process.env.NODE_ENV === 'production') return;
63
64 const changes: string[] = [];
65
66 for (const key of Object.keys(props)) {
67 if (prevProps.current[key] !== props[key]) {
68 changes.push(`${key} changed`);
69 }
70 }
71
72 if (changes.length > 0) {
73 console.log(
74 `%c[${componentName}] Re-rendered because:`,
75 'color: #f59e0b; font-weight: bold',
76 changes.join(', ')
77 );
78 }
79
80 prevProps.current = props;
81 });
82}
83
84// Production-safe logging that no-ops in prod
85export function devLog(label: string, ...args: unknown[]) {
86 if (process.env.NODE_ENV === 'production') return;
87 console.log(
88 `%c[${new Date().toLocaleTimeString()}] ${label}`,
89 'color: #3b82f6',
90 ...args
91 );
92}