Next.js starter your AI actually understands. Ship internal tools in days not weeks. Pre-order $199 $499 → [Get it now]

How to debug React with console.log

Debugging React with console.log() is one of the quickest ways to understand how state, props, and events flow through your components. As the creator of CoreUI and a developer with over 25 years of experience, I reach for console.log() constantly during local development to trace exactly when and why a component updates. The key is knowing where to place your logs — inside useEffect() for state changes, inside event handlers for user interactions, and never directly in the render body for production code.

Log state changes with useEffect()

Use useEffect() to log a value every time it changes. This runs after React commits the update, so the logged value is always current.

import { useEffect, useState } from 'react'

export const SearchBox = () => {
  const [query, setQuery] = useState('')

  useEffect(() => {
    console.log('Query changed:', query)
  }, [query])

  return (
    <input
      value={query}
      onChange={(event) => setQuery(event.target.value)}
      placeholder="Search..."
    />
  )
}

Because useEffect() fires after the render, you see the new value of query each time it changes. This is the most reliable way to observe state transitions during development.

Strict Mode caveat: In development, React Strict Mode runs effects twice on mount. If you see duplicate logs on the initial render, that is expected behavior — it does not happen in production builds.

Log inside event handlers

To inspect values at the moment a user interacts with your component, log directly inside the handler:

const handleChange = (event) => {
  console.log('Input value:', event.target.value)
  setQuery(event.target.value)
}

This is useful when you need to see the raw event data before state updates. Keep in mind that logging query here would show the previous value because setQuery() is asynchronous — the state update has not been applied yet.

Log props to trace data flow

When debugging parent-child communication, log incoming props at the top of a component:

const UserCard = ({ name, role }) => {
  useEffect(() => {
    console.log('UserCard props:', { name, role })
  }, [name, role])

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

This helps you confirm whether a parent is passing the expected data and when re-renders happen.

Clean up before shipping

Remove or guard debugging logs before deploying to production. A simple environment check keeps logs available during development without cluttering production output:

if (process.env.NODE_ENV === 'development') {
  console.log('Debug:', value)
}

This is the same kind of practical, low-complexity approach we use in CoreUI components to keep debugging easy without affecting production performance.


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

Subscribe to our newsletter
Get early information about new products, product updates and blog posts.
Understanding the Difference Between NPX and NPM
Understanding the Difference Between NPX and NPM

CSS Selector for Parent Element
CSS Selector for Parent Element

How to force a React component to re-render
How to force a React component to re-render

What is the Difference Between Null and Undefined in JavaScript
What is the Difference Between Null and Undefined in JavaScript

Answers by CoreUI Core Team