2024Libraries
toolbench
Back

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:

tsx
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:

tsx
useWhyDidYouRender('MyComponent', { props, state });

When the com...

Built With

ReactTypeScriptSCSSVite

Impact

Framework

React 18+

Platform

npm

Under the Hood

A peek at the implementation — the kind of code that powers toolbench.

devtools.tsxtypescript
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}

Discussion

💬 Be nice, or be funny. Preferably both.