Skip to main content

Debugging Guide

This guide covers debugging strategies, tools, and techniques for the Nounspace codebase to help developers identify and resolve issues effectively.

Debugging Philosophy​

1. Debugging Principles​

  • Reproduce First - Always reproduce the issue before debugging
  • Isolate the Problem - Narrow down the scope of the issue
  • Use the Right Tools - Choose appropriate debugging tools for the situation
  • Document Findings - Keep track of what you discover during debugging

2. Debugging Process​

  1. Reproduce - Reproduce the issue consistently
  2. Isolate - Isolate the problematic code
  3. Analyze - Analyze the root cause
  4. Fix - Implement the fix
  5. Verify - Verify the fix works
  6. Test - Test to ensure no regressions

Browser Debugging​

1. Chrome DevTools​

// Console debugging
console.log('Debug info:', { user, data, state });
console.table(data); // Display data in table format
console.group('User Actions'); // Group related logs
console.log('Action 1');
console.log('Action 2');
console.groupEnd();

// Performance debugging
console.time('API Call');
await fetchUserData();
console.timeEnd('API Call');

// Memory debugging
console.memory; // Check memory usage

2. React DevTools​

// Component debugging
import { useDebugValue } from 'react';

const useUser = (userId: string) => {
const [user, setUser] = useState(null);

// Debug value for React DevTools
useDebugValue(user, user => user ? `User: ${user.name}` : 'No user');

return { user, setUser };
};

// Profiler debugging
import { Profiler } from 'react';

const onRenderCallback = (id, phase, actualDuration) => {
console.log('Component render:', { id, phase, actualDuration });
};

<Profiler id="UserProfile" onRender={onRenderCallback}>
<UserProfile />
</Profiler>

3. Network Debugging​

// API debugging
const fetchUser = async (userId: string) => {
try {
console.log('Fetching user:', userId);
const response = await fetch(`/api/users/${userId}`);
console.log('Response status:', response.status);

if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}

const data = await response.json();
console.log('User data:', data);
return data;
} catch (error) {
console.error('Fetch error:', error);
throw error;
}
};

State Debugging​

1. Zustand Store Debugging​

// Store debugging
import { create } from 'zustand';
import { devtools } from 'zustand/middleware';

const useUserStore = create(
devtools(
(set, get) => ({
user: null,
loading: false,
error: null,

setUser: (user) => {
console.log('Setting user:', user);
set({ user }, false, 'setUser');
},

setLoading: (loading) => {
console.log('Setting loading:', loading);
set({ loading }, false, 'setLoading');
},

setError: (error) => {
console.log('Setting error:', error);
set({ error }, false, 'setError');
},
}),
{
name: 'user-store', // Unique name for DevTools
}
)
);

// Debug store state
const debugStore = () => {
const state = useUserStore.getState();
console.log('Current store state:', state);
};

2. State Change Tracking​

// Track state changes
const useUserStore = create((set, get) => ({
user: null,
setUser: (user) => {
const prevState = get();
console.log('Previous state:', prevState);

set({ user });

const newState = get();
console.log('New state:', newState);
},
}));

// Subscribe to state changes
const unsubscribe = useUserStore.subscribe(
(state) => console.log('State changed:', state),
(state) => state.user // Only log when user changes
);

Component Debugging​

1. Component Lifecycle Debugging​

// Component debugging
import { useEffect, useRef } from 'react';

const UserProfile = ({ userId }) => {
const renderCount = useRef(0);
renderCount.current++;

console.log(`UserProfile rendered ${renderCount.current} times`);

useEffect(() => {
console.log('UserProfile mounted');
return () => console.log('UserProfile unmounted');
}, []);

useEffect(() => {
console.log('UserProfile userId changed:', userId);
}, [userId]);

return <div>User Profile</div>;
};

2. Props Debugging​

// Props debugging
const UserProfile = (props) => {
console.log('UserProfile props:', props);

// Debug specific prop changes
const prevProps = useRef();
useEffect(() => {
if (prevProps.current) {
const changedProps = Object.keys(props).filter(
key => prevProps.current[key] !== props[key]
);
if (changedProps.length > 0) {
console.log('Changed props:', changedProps);
}
}
prevProps.current = props;
});

return <div>User Profile</div>;
};

Error Debugging​

1. Error Boundaries​

// Error boundary for debugging
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null, errorInfo: null };
}

static getDerivedStateFromError(error) {
return { hasError: true, error };
}

componentDidCatch(error, errorInfo) {
console.error('Error caught by boundary:', error);
console.error('Error info:', errorInfo);

// Log to error reporting service
this.logErrorToService(error, errorInfo);

this.setState({ errorInfo });
}

logErrorToService = (error, errorInfo) => {
// Send error to logging service
console.log('Logging error to service:', { error, errorInfo });
};

render() {
if (this.state.hasError) {
return (
<div>
<h2>Something went wrong</h2>
<details>
<summary>Error details</summary>
<pre>{this.state.error && this.state.error.toString()}</pre>
<pre>{this.state.errorInfo.componentStack}</pre>
</details>
</div>
);
}

return this.props.children;
}
}

2. Error Handling​

// Error handling with debugging
const fetchUser = async (userId: string) => {
try {
const response = await api.get(`/users/${userId}`);
return response.data;
} catch (error) {
console.error('Fetch user error:', {
userId,
error: error.message,
stack: error.stack,
timestamp: new Date().toISOString()
});

// Re-throw with additional context
throw new Error(`Failed to fetch user ${userId}: ${error.message}`);
}
};

Performance Debugging​

1. Performance Monitoring​

// Performance monitoring
import { Profiler } from 'react';

const PerformanceProfiler = ({ children, id }) => {
const onRenderCallback = (id, phase, actualDuration, baseDuration, startTime, commitTime) => {
console.log('Performance metrics:', {
id,
phase,
actualDuration,
baseDuration,
startTime,
commitTime
});

// Log slow renders
if (actualDuration > 100) {
console.warn(`Slow render detected: ${id} took ${actualDuration}ms`);
}
};

return (
<Profiler id={id} onRender={onRenderCallback}>
{children}
</Profiler>
);
};

2. Memory Debugging​

// Memory debugging
const debugMemory = () => {
if (performance.memory) {
console.log('Memory usage:', {
used: Math.round(performance.memory.usedJSHeapSize / 1024 / 1024) + ' MB',
total: Math.round(performance.memory.totalJSHeapSize / 1024 / 1024) + ' MB',
limit: Math.round(performance.memory.jsHeapSizeLimit / 1024 / 1024) + ' MB'
});
}
};

// Monitor memory leaks
const useMemoryMonitor = () => {
useEffect(() => {
const interval = setInterval(debugMemory, 5000);
return () => clearInterval(interval);
}, []);
};

Network Debugging​

1. API Debugging​

// API debugging
const apiClient = axios.create({
baseURL: '/api',
timeout: 10000
});

// Request interceptor
apiClient.interceptors.request.use(
(config) => {
console.log('API Request:', {
url: config.url,
method: config.method,
data: config.data,
headers: config.headers
});
return config;
},
(error) => {
console.error('API Request Error:', error);
return Promise.reject(error);
}
);

// Response interceptor
apiClient.interceptors.response.use(
(response) => {
console.log('API Response:', {
url: response.config.url,
status: response.status,
data: response.data
});
return response;
},
(error) => {
console.error('API Response Error:', {
url: error.config?.url,
status: error.response?.status,
data: error.response?.data,
message: error.message
});
return Promise.reject(error);
}
);

2. WebSocket Debugging​

// WebSocket debugging
const useWebSocket = (url: string) => {
const [socket, setSocket] = useState<WebSocket | null>(null);
const [connectionState, setConnectionState] = useState('Connecting');

useEffect(() => {
const ws = new WebSocket(url);

ws.onopen = () => {
console.log('WebSocket connected:', url);
setConnectionState('Connected');
};

ws.onmessage = (event) => {
console.log('WebSocket message received:', event.data);
};

ws.onclose = () => {
console.log('WebSocket disconnected');
setConnectionState('Disconnected');
};

ws.onerror = (error) => {
console.error('WebSocket error:', error);
setConnectionState('Error');
};

setSocket(ws);

return () => {
ws.close();
};
}, [url]);

return { socket, connectionState };
};

Mobile Debugging​

1. Mobile-Specific Debugging​

// Mobile debugging
const useMobileDebug = () => {
const [isMobile, setIsMobile] = useState(false);

useEffect(() => {
const checkMobile = () => {
const mobile = window.innerWidth < 768;
setIsMobile(mobile);
console.log('Mobile detection:', { isMobile: mobile, width: window.innerWidth });
};

checkMobile();
window.addEventListener('resize', checkMobile);

return () => window.removeEventListener('resize', checkMobile);
}, []);

return { isMobile };
};

2. Touch Event Debugging​

// Touch event debugging
const useTouchDebug = () => {
useEffect(() => {
const handleTouchStart = (e: TouchEvent) => {
console.log('Touch start:', {
touches: e.touches.length,
target: e.target,
clientX: e.touches[0]?.clientX,
clientY: e.touches[0]?.clientY
});
};

const handleTouchMove = (e: TouchEvent) => {
console.log('Touch move:', {
touches: e.touches.length,
clientX: e.touches[0]?.clientX,
clientY: e.touches[0]?.clientY
});
};

const handleTouchEnd = (e: TouchEvent) => {
console.log('Touch end:', {
touches: e.touches.length,
changedTouches: e.changedTouches.length
});
};

document.addEventListener('touchstart', handleTouchStart);
document.addEventListener('touchmove', handleTouchMove);
document.addEventListener('touchend', handleTouchEnd);

return () => {
document.removeEventListener('touchstart', handleTouchStart);
document.removeEventListener('touchmove', handleTouchMove);
document.removeEventListener('touchend', handleTouchEnd);
};
}, []);
};

Debugging Tools​

1. Custom Debug Hooks​

// Custom debug hook
const useDebug = (value: any, label: string) => {
useEffect(() => {
console.log(`${label}:`, value);
}, [value, label]);
};

// Usage
const UserProfile = ({ user }) => {
useDebug(user, 'User Profile User');
useDebug(user?.name, 'User Name');

return <div>{user?.name}</div>;
};

2. Debug Utilities​

// Debug utilities
export const debug = {
log: (message: string, data?: any) => {
if (process.env.NODE_ENV === 'development') {
console.log(`[DEBUG] ${message}`, data);
}
},

warn: (message: string, data?: any) => {
if (process.env.NODE_ENV === 'development') {
console.warn(`[DEBUG] ${message}`, data);
}
},

error: (message: string, data?: any) => {
if (process.env.NODE_ENV === 'development') {
console.error(`[DEBUG] ${message}`, data);
}
},

group: (label: string, fn: () => void) => {
if (process.env.NODE_ENV === 'development') {
console.group(label);
fn();
console.groupEnd();
}
}
};

Debugging Best Practices​

1. Debugging Strategy​

  • Start Simple - Begin with basic console.log statements
  • Use Breakpoints - Set breakpoints in critical code paths
  • Isolate Issues - Narrow down the problem scope
  • Document Findings - Keep track of what you discover

2. Debugging Tools​

  • Browser DevTools - Use Chrome DevTools for debugging
  • React DevTools - Use React DevTools for component debugging
  • Network Tab - Monitor network requests and responses
  • Performance Tab - Analyze performance bottlenecks

3. Debugging Techniques​

  • Console Debugging - Use console.log, console.error, etc.
  • Breakpoint Debugging - Set breakpoints in code
  • Step-through Debugging - Step through code line by line
  • Variable Inspection - Inspect variable values and types

4. Debugging Maintenance​

  • Remove Debug Code - Clean up debug code before committing
  • Use Environment Variables - Control debug output with env vars
  • Document Debug Process - Keep track of debugging steps
  • Share Debug Findings - Share debugging insights with team

Common Debugging Scenarios​

1. State Issues​

// Debug state issues
const useUser = (userId: string) => {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);

useEffect(() => {
console.log('User effect triggered:', { userId, user, loading, error });

const fetchUser = async () => {
setLoading(true);
setError(null);

try {
const userData = await api.getUser(userId);
console.log('User fetched:', userData);
setUser(userData);
} catch (err) {
console.error('User fetch error:', err);
setError(err.message);
} finally {
setLoading(false);
}
};

if (userId) {
fetchUser();
}
}, [userId]);

return { user, loading, error };
};

2. Component Issues​

// Debug component issues
const UserProfile = ({ userId }) => {
console.log('UserProfile render:', { userId });

const { user, loading, error } = useUser(userId);

console.log('UserProfile state:', { user, loading, error });

if (loading) {
console.log('UserProfile loading');
return <div>Loading...</div>;
}

if (error) {
console.log('UserProfile error:', error);
return <div>Error: {error}</div>;
}

if (!user) {
console.log('UserProfile no user');
return <div>No user found</div>;
}

console.log('UserProfile rendering user:', user);
return <div>{user.name}</div>;
};

3. Performance Issues​

// Debug performance issues
const ExpensiveComponent = ({ data }) => {
console.log('ExpensiveComponent render:', { dataLength: data?.length });

const processedData = useMemo(() => {
console.log('Processing data:', data);
return data.map(item => ({
...item,
processed: expensiveCalculation(item)
}));
}, [data]);

console.log('Processed data:', processedData);

return (
<div>
{processedData.map(item => (
<Item key={item.id} data={item} />
))}
</div>
);
};