How to debug React with console.log

Console logging is the quickest way to debug React components, inspect props and state, and track re-renders. As the creator of CoreUI with 25 years of debugging experience, I use strategic console.log placement to diagnose issues in React applications serving millions of users.

The most effective approach combines descriptive labels, grouped logs, and render tracking to quickly identify issues.

Log Props and State

function UserProfile({ user }) {
  const [count, setCount] = useState(0)

  console.log('UserProfile render:', { user, count })

  return (
    <div>
      <h1>{user.name}</h1>
      <button onClick={() => setCount(count + 1)}>
        Count: {count}
      </button>
    </div>
  )
}

Track Re-renders

import { useEffect, useRef } from 'react'

function ExpensiveComponent({ data }) {
  const renderCount = useRef(0)

  useEffect(() => {
    renderCount.current++
    console.log(`ExpensiveComponent rendered ${renderCount.current} times`)
  })

  return <div>{data.map(item => <p key={item.id}>{item.name}</p>)}</div>
}

Log Hook Dependencies

function DataFetcher({ userId }) {
  const [data, setData] = useState(null)

  useEffect(() => {
    console.log('useEffect triggered, userId:', userId)

    fetch(`/api/users/${userId}`)
      .then(res => res.json())
      .then(result => {
        console.log('Data fetched:', result)
        setData(result)
      })
  }, [userId])

  return <div>{data?.name}</div>
}
function ComplexForm({ initialData }) {
  const [formData, setFormData] = useState(initialData)

  const handleSubmit = async (e) => {
    e.preventDefault()

    console.group('Form Submission')
    console.log('Form data:', formData)
    console.log('Timestamp:', new Date().toISOString())

    try {
      const response = await fetch('/api/submit', {
        method: 'POST',
        body: JSON.stringify(formData)
      })
      console.log('Response:', response)
    } catch (error) {
      console.error('Submission failed:', error)
    } finally {
      console.groupEnd()
    }
  }

  return <form onSubmit={handleSubmit}>...</form>
}

Log Before and After State Updates

function Counter() {
  const [count, setCount] = useState(0)

  const increment = () => {
    console.log('Before:', count)
    setCount(count + 1)
    console.log('After setState (still old):', count)
  }

  useEffect(() => {
    console.log('After render (updated):', count)
  }, [count])

  return <button onClick={increment}>Count: {count}</button>
}

Log Component Lifecycle

function LifecycleLogger({ name }) {
  console.log(`${name} render`)

  useEffect(() => {
    console.log(`${name} mounted`)

    return () => {
      console.log(`${name} unmounted`)
    }
  }, [name])

  useEffect(() => {
    console.log(`${name} updated`)
  })

  return <div>{name}</div>
}

Conditional Logging

function DataList({ items }) {
  const debugMode = process.env.NODE_ENV === 'development'

  if (debugMode && items.length === 0) {
    console.warn('DataList rendered with empty items array')
  }

  if (debugMode && items.length > 100) {
    console.warn('DataList has', items.length, 'items - consider pagination')
  }

  return <ul>{items.map(item => <li key={item.id}>{item.name}</li>)}</ul>
}

Table Logging

function UserTable({ users }) {
  console.table(users.map(u => ({
    ID: u.id,
    Name: u.name,
    Email: u.email,
    Active: u.isActive
  })))

  return <table>...</table>
}

Time Logging

function ExpensiveOperation({ data }) {
  console.time('Data Processing')

  const processed = data.map(item => {
    return heavyCalculation(item)
  })

  console.timeEnd('Data Processing')

  return <div>{processed.length} items processed</div>
}

Custom Logger Hook

const useLogger = (componentName, props) => {
  const renderCount = useRef(0)

  useEffect(() => {
    renderCount.current++
  })

  useEffect(() => {
    console.log(`[${componentName}] Mount`)
    return () => console.log(`[${componentName}] Unmount`)
  }, [componentName])

  useEffect(() => {
    console.log(
      `[${componentName}] Render #${renderCount.current}`,
      { props }
    )
  })
}

function MyComponent(props) {
  useLogger('MyComponent', props)
  return <div>...</div>
}

Best Practice Note

This is the same console debugging strategy we use when developing CoreUI components. Strategic console.log placement reveals component behavior, re-render patterns, and state issues quickly. Always remove console logs before production or use conditional logging with environment variables.

For advanced debugging, consider using React DevTools for visual component inspection.

For performance analysis, you might also want to learn how to measure performance in React for detailed profiling.


Speed up your responsive apps and websites with fully-featured, ready-to-use open-source admin panel templates—free to use and built for efficiency.


About the Author