DEV Community

Alexandru Ghiura
Alexandru Ghiura

Posted on

Best Practices: React Logging and Error Handling

React Logging and Error Handling Best Practices

In this guide, we'll explore battle-tested patterns for implementing production-ready logging and error handling in React applications using logzai-js library, but you can use any library.

Whether you're building a small side project or maintaining a large-scale application, these practices will help you catch bugs faster, understand user behavior better, and ship more reliable software.

Understanding the Foundation

Why Logging Matters in React

Client-side logging presents unique challenges compared to server-side logging:

  • Distributed Environments: Your app runs in thousands of different browsers, devices, and network conditions
  • Limited Visibility: Without proper instrumentation, you only see errors that get reported—most issues go unnoticed
  • User Context: Understanding what the user was doing when an error occurred is critical for debugging
  • Performance Impact: Excessive logging can slow down your app and frustrate users

The key is finding the right balance: log enough to debug effectively, but not so much that you impact performance or create noise.

The Cost of Poor Error Handling

When error handling is an afterthought, the consequences ripple through your entire organization:

  • Lost Revenue: Users who encounter errors often abandon their workflows—shopping carts left behind, forms never submitted
  • Support Burden: Your support team spends hours trying to reproduce issues with incomplete information
  • Development Time: Engineers waste days tracking down bugs that could have been caught in minutes with proper logging
  • User Trust: Frequent errors erode confidence in your product, making users less likely to return

What Makes Good Logging

Good logging isn't about quantity—it's about quality and structure:

  • Structured Data: Logs should be machine-readable with consistent fields (JSON, not strings)
  • Appropriate Levels: Use debug, info, warn, and error levels correctly
  • Rich Context: Include user IDs, session IDs, routes, and relevant state
  • Actionable Information: Every log should help you understand what happened and why
  • Performance-Aware: Logging should never block your UI thread

With these principles in mind, let's dive into implementation.

Setting Up LogzAI in Your React Application

Getting started with logzai-js is straightforward. First, install the package:

npm install logzai-js
# or
pnpm add logzai-js
# or
yarn add logzai-js
Enter fullscreen mode Exit fullscreen mode

Next, initialize logzai in your application entry point (typically main.tsx or index.tsx):

import logzai from 'logzai-js'
import { browserPlugin } from 'logzai-js/browser'

// Initialize logzai before rendering your app
logzai.init({
  ingestToken: 'your-ingest-token',
  ingestEndpoint: 'https://ingest.logzai.com',
  serviceName: 'my-react-app',
  environment: process.env.NODE_ENV, // 'development' or 'production'
  mirrorToConsole: process.env.NODE_ENV === 'development', // See logs in console during dev
})

// Enable the browser plugin for automatic error handling
logzai.plugin('browser', browserPlugin)
Enter fullscreen mode Exit fullscreen mode

What the Browser Plugin Does Automatically

The browser plugin is a powerful addition that transforms your error handling with zero additional code. Once enabled, it automatically:

  • Captures all JavaScript errors: Hooks into window.onerror to catch unhandled exceptions
  • Captures unhandled promise rejections: Monitors window.onunhandledrejection to catch async errors that slip through
  • Logs errors with full context: Automatically includes stack traces, error messages, and browser information
  • Non-blocking operation: Sends logs asynchronously without impacting your app's performance

This means even errors you didn't explicitly catch will be logged to logzai, giving you complete visibility into production issues.

Customizing the Browser Plugin

The browser plugin accepts configuration options to enhance your logs with application-specific context:

import logzai from 'logzai-js'
import { browserPlugin } from 'logzai-js/browser'
import { store } from './store'
import { selectCurrentUser, selectSelectedOrg } from './store/selectors'

// Initialize logzai
logzai.init({
  ingestToken: 'your-ingest-token',
  ingestEndpoint: 'https://ingest.logzai.com',
  serviceName: 'my-react-app',
  environment: process.env.NODE_ENV,
  mirrorToConsole: process.env.NODE_ENV === 'development',
})

// Configure browser plugin with custom options
logzai.plugin('browser', browserPlugin, {
  // Custom message formatter for errors
  messageFormatter: (error: any) => {
    return `Exception: ${error.message}`
  },

  // Context injector - automatically adds context to every log and error
  contextInjector: () => {
    const state = store.getState()
    const currentUser = selectCurrentUser(state)
    const selectedOrg = selectSelectedOrg(state)

    return {
      userId: currentUser?.id,
      userEmail: currentUser?.email,
      orgId: selectedOrg?.id,
      orgName: selectedOrg?.name,
      currentRoute: window.location.pathname,
      userAgent: navigator.userAgent,
      viewport: `${window.innerWidth}x${window.innerHeight}`,
    }
  },

  // Optional: Filter out errors you don't want to log
  errorFilter: (error: Error) => {
    // Return false to skip logging this error
    if (error.message.includes('ResizeObserver')) {
      return false // Don't log benign ResizeObserver errors
    }
    return true // Log all other errors
  },
})
Enter fullscreen mode Exit fullscreen mode

Key Options:

  • messageFormatter: Transform how error messages appear in logs
  • contextInjector: Inject application state (user, org, route) into every log automatically
  • errorFilter: Skip logging specific errors that aren't actionable

With this setup, every error—even those you didn't anticipate—will be automatically logged with rich context about the user, their session, and the state of your application.

That's it! You're now ready to start logging with comprehensive automatic error tracking.

Logging Best Practices

1. Use Appropriate Log Levels

Understanding when to use each log level is crucial for creating a signal-to-noise ratio that actually helps you debug:

DEBUG: Use for detailed information useful during development. These logs are typically verbose and not needed in production.

// Good use of debug
const handleSearch = (query: string) => {
  logzai.debug('Search initiated', {
    query,
    timestamp: Date.now(),
    resultsCount: results.length,
  })
}
Enter fullscreen mode Exit fullscreen mode

INFO: Use for important business events and normal operations you want to track.

// Good use of info
const handleCheckout = async (cartItems: CartItem[]) => {
  logzai.info('Checkout started', {
    itemCount: cartItems.length,
    totalValue: calculateTotal(cartItems),
    userId: currentUser.id,
  })

  // ... checkout logic
}
Enter fullscreen mode Exit fullscreen mode

WARN: Use for recoverable issues or deprecated features that should be addressed but don't break functionality.

// Good use of warn
const fetchUserData = async () => {
  const cachedData = getFromCache('userData')

  if (!cachedData) {
    logzai.warn('Cache miss for user data', {
      userId: currentUser.id,
      cacheExpiry: getCacheExpiry(),
    })
    return fetchFromAPI()
  }

  return cachedData
}
Enter fullscreen mode Exit fullscreen mode

ERROR: Use for unrecoverable errors that impact functionality.

// Good use of error
const saveUserSettings = async (settings: Settings) => {
  try {
    await api.updateSettings(settings)
  } catch (error) {
    logzai.error('Failed to save user settings', {
      error: error.message,
      userId: currentUser.id,
      settings,
    })
    throw error // Re-throw to let UI handle it
  }
}
Enter fullscreen mode Exit fullscreen mode

2. Structure Your Logs with Context

Context transforms logs from cryptic messages into actionable insights. Compare these two approaches:

// ❌ Bad: Minimal context
logzai.info('User logged in')

// ✅ Good: Rich context
logzai.info('User logged in', {
  userId: user.id,
  email: user.email,
  loginMethod: 'oauth',
  provider: 'google',
  timestamp: Date.now(),
  previousLoginAt: user.lastLoginAt,
  daysSinceLastLogin: calculateDaysSince(user.lastLoginAt),
})
Enter fullscreen mode Exit fullscreen mode

The second example gives you everything you need to understand user behavior patterns, detect anomalies, and debug issues.

Key context fields to include:

  • User identifiers: userId, email, sessionId
  • Business context: orderId, transactionId, itemId
  • Technical context: route, component name, action type
  • Temporal context: timestamps, durations, retry counts
  • Environmental context: browser, device, network status

3. Context Injection Pattern

As we saw in the setup section, the browser plugin's contextInjector automatically enriches all logs and errors with application context. This pattern ensures you never have to manually add context to individual log statements.

When you configure the browser plugin with a contextInjector, every log call automatically includes that context:

// Simple log call
logzai.info('Feature flag toggled', {
  featureName: 'dark-mode',
  enabled: true
})

// Automatically enriched with context from the browser plugin:
// - userId, userEmail (from Redux state)
// - orgId, orgName (from Redux state)
// - currentRoute (from window.location)
// - userAgent, viewport (from browser)
Enter fullscreen mode Exit fullscreen mode

This pattern has several benefits:

  • Consistency: Every log has the same baseline context
  • DRY Principle: Write context logic once, not in every log statement
  • Error Context: Even automatically caught errors include this context
  • Zero Overhead: Context is injected at log time, not computed unnecessarily

The contextInjector is called for every log, so you can include dynamic information like the current route or selected organization that changes during the user's session.

4. Log User Actions and State Changes

Logging user interactions creates a breadcrumb trail that's invaluable for debugging:

// Component with action logging
const ProductPage = () => {
  const handleAddToCart = (product: Product) => {
    logzai.info('Product added to cart', {
      productId: product.id,
      productName: product.name,
      price: product.price,
      quantity: 1,
      source: 'product-page',
    })

    dispatch(addToCart(product))
  }

  const handleQuickView = (product: Product) => {
    logzai.debug('Quick view opened', {
      productId: product.id,
      trigger: 'hover',
    })

    setQuickViewProduct(product)
  }

  return (
    // ... component JSX
  )
}
Enter fullscreen mode Exit fullscreen mode

For forms, log both submission and validation failures:

const ContactForm = () => {
  const handleSubmit = async (values: FormValues) => {
    // Validate
    const errors = validateForm(values)

    if (Object.keys(errors).length > 0) {
      logzai.warn('Form validation failed', {
        formName: 'contact',
        errors: Object.keys(errors),
        attemptNumber: submitAttempts + 1,
      })
      return
    }

    try {
      await api.submitContactForm(values)

      logzai.info('Contact form submitted successfully', {
        formName: 'contact',
        fieldsFilled: Object.keys(values),
      })
    } catch (error) {
      logzai.error('Contact form submission failed', {
        formName: 'contact',
        error: error.message,
      })
    }
  }

  return (
    // ... form JSX
  )
}
Enter fullscreen mode Exit fullscreen mode

5. Performance Considerations

Logging should never slow down your app. Follow these guidelines:

Async by Default: logzai sends logs asynchronously, but avoid doing heavy computation in log statements:

// ❌ Bad: Expensive operation in log statement
logzai.debug('Component rendered', {
  largeArray: expensiveComputation(data), // Blocks UI
})

// ✅ Good: Only log what's necessary
logzai.debug('Component rendered', {
  dataSize: data.length,
})
Enter fullscreen mode Exit fullscreen mode

Sampling in Production: For high-frequency events, use sampling to reduce volume:

const logScrollEvent = () => {
  // Only log 1% of scroll events
  if (Math.random() < 0.01) {
    logzai.debug('Page scrolled', {
      scrollPosition: window.scrollY,
      scrollDepth: calculateScrollDepth(),
    })
  }
}
Enter fullscreen mode Exit fullscreen mode

Environment-Specific Verbosity: Use different log levels for development vs. production:

const isDevelopment = process.env.NODE_ENV === 'development'

const logComponentMount = (componentName: string) => {
  if (isDevelopment) {
    logzai.debug(`Component mounted: ${componentName}`)
  }
}
Enter fullscreen mode Exit fullscreen mode

Error Handling Best Practices

1. Implement Error Boundaries

React Error Boundaries are your first line of defense against unhandled errors crashing your entire app. They catch errors in the component tree and allow you to log them and show fallback UI:

import React, { Component, ErrorInfo, ReactNode } from 'react'
import logzai from 'logzai-js/browser'

interface Props {
  children: ReactNode
  fallback?: ReactNode
}

interface State {
  hasError: boolean
  error?: Error
}

class ErrorBoundary extends Component<Props, State> {
  constructor(props: Props) {
    super(props)
    this.state = { hasError: false }
  }

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

  componentDidCatch(error: Error, errorInfo: ErrorInfo) {
    // Log to logzai with full context
    logzai.exception('React error boundary caught error', error, {
      errorType: 'react-error',
      componentStack: errorInfo.componentStack,
      errorMessage: error.message,
      errorStack: error.stack,
      pathname: window.location.pathname,
      timestamp: Date.now(),
    })
  }

  render() {
    if (this.state.hasError) {
      return this.props.fallback || (
        <div style={{ padding: '20px', textAlign: 'center' }}>
          <h2>Something went wrong</h2>
          <p>We've been notified and are looking into it.</p>
          <button onClick={() => window.location.reload()}>
            Reload Page
          </button>
        </div>
      )
    }

    return this.props.children
  }
}

export default ErrorBoundary
Enter fullscreen mode Exit fullscreen mode

Where to Place Error Boundaries: Wrap strategic parts of your app to isolate failures:

// App-level boundary
const App = () => (
  <ErrorBoundary>
    <Router>
      <Routes />
    </Router>
  </ErrorBoundary>
)

// Feature-level boundaries
const Dashboard = () => (
  <div>
    <ErrorBoundary fallback={<WidgetError />}>
      <RevenueWidget />
    </ErrorBoundary>

    <ErrorBoundary fallback={<WidgetError />}>
      <UsersWidget />
    </ErrorBoundary>

    <ErrorBoundary fallback={<WidgetError />}>
      <ActivityWidget />
    </ErrorBoundary>
  </div>
)
Enter fullscreen mode Exit fullscreen mode

This approach ensures one broken widget doesn't take down the entire dashboard.

Note on Browser Plugin Integration: While the browser plugin automatically catches unhandled JavaScript errors and promise rejections, Error Boundaries are still essential. The browser plugin catches errors that escape React's component tree, while Error Boundaries catch errors within React components and allow you to show fallback UI. Together, they provide comprehensive error coverage:

  • Error Boundaries: Catch React component errors + show fallback UI + log with logzai.exception()
  • Browser Plugin: Catch all other JavaScript errors + unhandled promise rejections automatically

Both layers working together ensure nothing slips through the cracks.

2. Log Exceptions with Full Context

When logging exceptions, include everything needed to reproduce and debug the issue:

const fetchUserProfile = async (userId: string) => {
  try {
    const response = await api.get(`/users/${userId}`)
    return response.data
  } catch (error) {
    logzai.exception('Failed to fetch user profile', error, {
      // Error details
      errorMessage: error.message,
      errorCode: error.response?.status,

      // Request context
      userId,
      endpoint: `/users/${userId}`,

      // User context (automatically added by contextInjector)
      // - currentUserId, email
      // - orgId, orgName
      // - pathname

      // Additional debugging info
      retryCount: 0,
      timestamp: Date.now(),
    })

    throw error // Re-throw to let calling code handle
  }
}
Enter fullscreen mode Exit fullscreen mode

The logzai.exception() method is specifically designed for errors and automatically extracts the stack trace and error details.

3. Handle Async Errors (Promise Rejections)

Good news: The browser plugin automatically captures unhandled promise rejections! Once you've enabled the browser plugin with logzai.plugin('browser', browserPlugin), all unhandled rejections are automatically logged with full context.

However, you should still handle promise rejections explicitly where possible for better control over error messages and recovery strategies:

const loadUserData = async () => {
  try {
    const [profile, settings, preferences] = await Promise.all([
      fetchProfile(),
      fetchSettings(),
      fetchPreferences(),
    ])

    return { profile, settings, preferences }
  } catch (error) {
    logzai.exception('Failed to load user data', error, {
      failedOperation: 'loadUserData',
      retryable: true,
    })

    // Show user-friendly error
    throw new Error('Unable to load your profile. Please try again.')
  }
}
Enter fullscreen mode Exit fullscreen mode

4. API Error Handling Pattern

Centralize API error logging using interceptors. For axios:

import axios from 'axios'
import logzai from 'logzai-js/browser'

const apiClient = axios.create({
  baseURL: import.meta.env.VITE_API_URL,
})

// Request interceptor (log outgoing requests in debug mode)
apiClient.interceptors.request.use(
  (config) => {
    if (import.meta.env.DEV) {
      logzai.debug('API request', {
        method: config.method?.toUpperCase(),
        url: config.url,
        params: config.params,
      })
    }
    return config
  },
  (error) => {
    logzai.error('API request setup failed', {
      error: error.message,
    })
    return Promise.reject(error)
  }
)

// Response interceptor (log errors)
apiClient.interceptors.response.use(
  (response) => response,
  (error) => {
    const request = error.config

    logzai.error('API request failed', {
      method: request?.method?.toUpperCase(),
      url: request?.url,
      statusCode: error.response?.status,
      statusText: error.response?.statusText,
      errorMessage: error.message,
      responseData: error.response?.data,
      requestDuration: Date.now() - request?.metadata?.startTime,
    })

    return Promise.reject(error)
  }
)

export default apiClient
Enter fullscreen mode Exit fullscreen mode

For fetch API:

const fetchWithLogging = async (url: string, options?: RequestInit) => {
  const startTime = Date.now()

  try {
    const response = await fetch(url, options)

    if (!response.ok) {
      const errorData = await response.text()

      logzai.error('Fetch request failed', {
        url,
        method: options?.method || 'GET',
        statusCode: response.status,
        statusText: response.statusText,
        errorData,
        duration: Date.now() - startTime,
      })

      throw new Error(`HTTP ${response.status}: ${response.statusText}`)
    }

    return response
  } catch (error) {
    logzai.exception('Fetch request error', error, {
      url,
      method: options?.method || 'GET',
      duration: Date.now() - startTime,
    })

    throw error
  }
}
Enter fullscreen mode Exit fullscreen mode

5. User-Friendly Error Messages

Always separate what you log from what you show users:

class ErrorBoundary extends Component<Props, State> {
  // ... previous code

  componentDidCatch(error: Error, errorInfo: ErrorInfo) {
    // Log technical details
    logzai.exception('React error boundary caught error', error, {
      errorType: 'react-error',
      componentStack: errorInfo.componentStack,
      errorMessage: error.message,
      errorStack: error.stack,
    })
  }

  render() {
    if (this.state.hasError) {
      // Show friendly message to users
      return (
        <div className="error-container">
          <h2>Oops! Something went wrong</h2>
          <p>
            We've been notified and are working on a fix.
            In the meantime, try refreshing the page.
          </p>
          <button onClick={() => window.location.reload()}>
            Refresh Page
          </button>
        </div>
      )
    }

    return this.props.children
  }
}
Enter fullscreen mode Exit fullscreen mode

Never expose stack traces, error codes, or technical jargon to end users—those belong in your logs, not your UI.

Advanced Patterns

1. Redux/State Management Integration

If you're using Redux, you can log state changes and actions with middleware:

import { Middleware } from '@reduxjs/toolkit'
import logzai from 'logzai-js/browser'

const logzaiMiddleware: Middleware = () => (next) => (action) => {
  // Only log in development or for specific action types
  if (
    import.meta.env.DEV ||
    ['auth/login', 'auth/logout', 'user/update'].includes(action.type)
  ) {
    logzai.debug('Redux action dispatched', {
      kind: 'redux',
      type: action.type,
      payload: action.payload,
      timestamp: Date.now(),
    })
  }

  return next(action)
}

// Add to your store configuration
export const store = configureStore({
  reducer: rootReducer,
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware().concat(logzaiMiddleware),
})
Enter fullscreen mode Exit fullscreen mode

When to Use: Enable this in production only for critical actions to avoid log spam. Use it freely in development for debugging.

2. Route Change Logging

Track user navigation to understand user journeys:

import { useEffect } from 'react'
import { useLocation } from 'react-router-dom'
import logzai from 'logzai-js/browser'

const RouteLogger = () => {
  const location = useLocation()

  useEffect(() => {
    logzai.info('Route changed', {
      pathname: location.pathname,
      search: location.search,
      hash: location.hash,
      timestamp: Date.now(),
    })
  }, [location])

  return null
}

// Use in your app
const App = () => (
  <Router>
    <RouteLogger />
    <Routes>
      {/* your routes */}
    </Routes>
  </Router>
)
Enter fullscreen mode Exit fullscreen mode

3. Custom Hooks for Logging

Create reusable hooks to standardize logging patterns:

import { useCallback } from 'react'
import { useLocation } from 'react-router-dom'
import logzai from 'logzai-js/browser'

// Hook for logging user actions
export const useLogAction = () => {
  const location = useLocation()

  return useCallback((action: string, context?: Record<string, any>) => {
    logzai.info(action, {
      ...context,
      pathname: location.pathname,
      timestamp: Date.now(),
    })
  }, [location])
}

// Usage in components
const ShoppingCart = () => {
  const logAction = useLogAction()

  const handleCheckout = () => {
    logAction('Checkout initiated', {
      itemCount: cartItems.length,
      totalValue: calculateTotal(cartItems),
    })

    // ... checkout logic
  }

  return (
    // ... component JSX
  )
}
Enter fullscreen mode Exit fullscreen mode

4. Error Recovery Strategies

Build error boundaries that can recover from errors:

interface Props {
  children: ReactNode
  maxRetries?: number
}

interface State {
  hasError: boolean
  error?: Error
  retryCount: number
}

class RecoverableErrorBoundary extends Component<Props, State> {
  constructor(props: Props) {
    super(props)
    this.state = { hasError: false, retryCount: 0 }
  }

  static getDerivedStateFromError(error: Error): Partial<State> {
    return { hasError: true, error }
  }

  componentDidCatch(error: Error, errorInfo: ErrorInfo) {
    logzai.exception('Recoverable error boundary caught error', error, {
      errorType: 'react-error',
      componentStack: errorInfo.componentStack,
      retryCount: this.state.retryCount,
      maxRetries: this.props.maxRetries || 3,
    })
  }

  handleRetry = () => {
    const { maxRetries = 3 } = this.props
    const newRetryCount = this.state.retryCount + 1

    if (newRetryCount <= maxRetries) {
      logzai.info('Error boundary retry attempt', {
        retryCount: newRetryCount,
        maxRetries,
      })

      this.setState({
        hasError: false,
        error: undefined,
        retryCount: newRetryCount
      })
    } else {
      logzai.warn('Error boundary max retries reached', {
        retryCount: newRetryCount,
        maxRetries,
      })
    }
  }

  render() {
    const { hasError, retryCount } = this.state
    const { maxRetries = 3 } = this.props

    if (hasError) {
      return (
        <div className="error-container">
          <h2>Something went wrong</h2>
          {retryCount < maxRetries ? (
            <>
              <p>Would you like to try again?</p>
              <button onClick={this.handleRetry}>
                Try Again ({maxRetries - retryCount} attempts remaining)
              </button>
            </>
          ) : (
            <>
              <p>We've exhausted retry attempts. Please refresh the page.</p>
              <button onClick={() => window.location.reload()}>
                Refresh Page
              </button>
            </>
          )}
        </div>
      )
    }

    return this.props.children
  }
}
Enter fullscreen mode Exit fullscreen mode

This pattern is useful for components that might fail due to temporary issues (network glitches, race conditions, etc.).

Production Checklist

Before deploying your logging and error handling to production, verify these items:

Configuration

  • [ ] Use environment-specific ingest tokens (different for dev/staging/prod)
  • [ ] Set environment field correctly based on NODE_ENV
  • [ ] Configure appropriate log levels (debug in dev, info/warn/error in prod)
  • [ ] Set mirrorToConsole to false in production
  • [ ] Enable browser plugin with logzai.plugin('browser', browserPlugin)
  • [ ] Configure contextInjector to include user, org, and route context
  • [ ] Set up errorFilter to exclude benign errors (ResizeObserver, etc.)

Testing

  • [ ] Test error boundaries with intentionally thrown errors
  • [ ] Verify browser plugin captures unhandled JavaScript errors automatically
  • [ ] Test that unhandled promise rejections are logged by the browser plugin
  • [ ] Verify logs appear in logzai dashboard with correct context
  • [ ] Verify contextInjector is working (check logs include userId, orgId, route)
  • [ ] Test errorFilter excludes errors you want to skip
  • [ ] Check that sensitive data is NOT being logged

Monitoring

  • [ ] Set up alerts for high error rates
  • [ ] Create dashboards for key metrics (error rates by route, user actions)
  • [ ] Configure notification channels (email, Slack, PagerDuty)
  • [ ] Review logs regularly to identify patterns

Privacy & Security

  • [ ] Never log passwords, tokens, or API keys
  • [ ] Sanitize PII (email, phone, address) before logging
  • [ ] Review data retention policies
  • [ ] Ensure compliance with GDPR/CCPA if applicable

Performance

  • [ ] Verify bundle size impact (logzai-js is ~15KB gzipped)
  • [ ] Use log sampling for high-frequency events
  • [ ] Avoid logging large objects or arrays
  • [ ] Profile your app to ensure logging doesn't impact performance

Common Pitfalls to Avoid

Over-Logging

Logging every single action creates noise that obscures real issues:

// ❌ Bad: Too much noise
const MyComponent = () => {
  logzai.debug('MyComponent rendering')
  logzai.debug('Props received', props)
  logzai.debug('State initialized')

  const handleClick = () => {
    logzai.debug('Button clicked')
    logzai.debug('State before update', state)
    setState(newState)
    logzai.debug('State after update', newState)
  }

  logzai.debug('MyComponent render complete')
  return <button onClick={handleClick}>Click me</button>
}
Enter fullscreen mode Exit fullscreen mode

Instead, log meaningful events:

// ✅ Good: Signal over noise
const MyComponent = () => {
  const handleClick = () => {
    logzai.info('Important action triggered', {
      actionType: 'submit-form',
      formData: sanitizedData,
    })
    setState(newState)
  }

  return <button onClick={handleClick}>Click me</button>
}
Enter fullscreen mode Exit fullscreen mode

Under-Logging

The opposite problem: not logging enough context to be useful:

// ❌ Bad: Not enough context
logzai.error('API call failed')
Enter fullscreen mode Exit fullscreen mode
// ✅ Good: Actionable context
logzai.error('API call failed', {
  endpoint: '/api/users',
  method: 'POST',
  statusCode: 500,
  errorMessage: error.message,
  requestId: response.headers['x-request-id'],
})
Enter fullscreen mode Exit fullscreen mode

Synchronous Logging in Hot Paths

Don't perform expensive operations in frequently-called code:

// ❌ Bad: Expensive operation in render
const ProductList = ({ products }) => {
  logzai.debug('Rendering products', {
    products: products.map(p => ({ // Expensive!
      id: p.id,
      name: p.name,
      fullDetails: JSON.stringify(p), // Very expensive!
    }))
  })

  return <div>{/* render products */}</div>
}
Enter fullscreen mode Exit fullscreen mode
// ✅ Good: Log only what's necessary
const ProductList = ({ products }) => {
  logzai.debug('Rendering products', {
    productCount: products.length,
    firstProductId: products[0]?.id,
  })

  return <div>{/* render products */}</div>
}
Enter fullscreen mode Exit fullscreen mode

Logging Sensitive Data

Never log passwords, tokens, credit cards, or other sensitive information:

// ❌ Bad: Logging sensitive data
logzai.info('User logged in', {
  email: user.email,
  password: user.password, // NEVER DO THIS!
  creditCard: user.paymentMethod.cardNumber, // NEVER DO THIS!
})
Enter fullscreen mode Exit fullscreen mode
// ✅ Good: Sanitized logging
logzai.info('User logged in', {
  userId: user.id,
  email: user.email,
  hasPaymentMethod: !!user.paymentMethod,
  loginMethod: 'password',
})
Enter fullscreen mode Exit fullscreen mode

Ignoring Error Boundaries

Don't let errors crash your entire app:

// ❌ Bad: No error boundary
const App = () => (
  <Router>
    <Routes>
      <Route path="/" element={<Dashboard />} />
      {/* One error here crashes everything */}
    </Routes>
  </Router>
)
Enter fullscreen mode Exit fullscreen mode
// ✅ Good: Strategic error boundaries
const App = () => (
  <ErrorBoundary>
    <Router>
      <Routes>
        <Route path="/" element={
          <ErrorBoundary fallback={<DashboardError />}>
            <Dashboard />
          </ErrorBoundary>
        } />
      </Routes>
    </Router>
  </ErrorBoundary>
)
Enter fullscreen mode Exit fullscreen mode

Generic Error Messages

Don't make debugging harder with vague errors:

// ❌ Bad: Generic error
throw new Error('Something went wrong')
Enter fullscreen mode Exit fullscreen mode
// ✅ Good: Specific error
throw new Error(
  `Failed to load user profile: ${error.message} (User ID: ${userId})`
)
Enter fullscreen mode Exit fullscreen mode

Conclusion

Implementing robust logging and error handling in React applications isn't optional—it's essential for building reliable, maintainable software. Let's recap the key takeaways:

  • Enable the browser plugin for automatic error capture—it catches JavaScript errors and promise rejections with zero extra code
  • Use structured logging with rich context via the contextInjector to make logs searchable and actionable
  • Implement error boundaries at strategic points to prevent cascading failures and show fallback UI
  • Log at appropriate levels to maintain signal-to-noise ratio
  • Handle specific errors explicitly for better control, while the browser plugin catches everything else
  • Separate technical logs from user-facing messages to maintain good UX
  • Test your error handling to ensure it works when things go wrong
  • Monitor and alert on error patterns to catch issues proactively

Top comments (0)