How to set up hot reload in React

Setting up hot reload in React enables instant code updates during development without full page refresh or losing component state. With over 12 years of React experience since 2014 and as the creator of CoreUI, I’ve configured HMR for optimal development workflows. Hot Module Replacement (HMR) automatically updates changed modules in the browser while preserving application state and context. This approach dramatically improves development speed with instant feedback on code changes without manual browser refresh.

Use React Fast Refresh with Webpack or Vite for hot module replacement preserving component state during development.

Vite with Fast Refresh (recommended):

// vite.config.js
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [
    react({
      // Fast Refresh is enabled by default
      fastRefresh: true
    })
  ],
  server: {
    port: 3000,
    open: true,
    hmr: {
      overlay: true // Show error overlay
    }
  }
})

Create React App (built-in):

# Fast Refresh is built into CRA
npx create-react-app my-app
cd my-app
npm start

# Hot reload works automatically
# Edit src/App.js and see instant updates

Webpack 5 with React Fast Refresh:

// webpack.config.js
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin')

module.exports = {
  mode: 'development',
  entry: './src/index.js',
  devServer: {
    hot: true,
    port: 3000,
    open: true
  },
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            plugins: [
              require.resolve('react-refresh/babel')
            ]
          }
        }
      }
    ]
  },
  plugins: [
    new ReactRefreshWebpackPlugin()
  ]
}

Package.json for Webpack:

{
  "scripts": {
    "start": "webpack serve --config webpack.config.js"
  },
  "devDependencies": {
    "@babel/core": "^7.23.0",
    "@babel/preset-env": "^7.23.0",
    "@babel/preset-react": "^7.23.0",
    "@pmmmwh/react-refresh-webpack-plugin": "^0.5.11",
    "babel-loader": "^9.1.3",
    "react-refresh": "^0.14.0",
    "webpack": "^5.89.0",
    "webpack-cli": "^5.1.4",
    "webpack-dev-server": "^4.15.1"
  }
}

Component with preserved state:

// Counter.jsx
import { useState } from 'react'

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

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>
        Increment
      </button>
      {/* Edit this text - count state persists */}
      <p>Hot reload preserves state!</p>
    </div>
  )
}

Error boundary for HMR errors:

// ErrorBoundary.jsx
import { Component } from 'react'

class ErrorBoundary extends Component {
  constructor(props) {
    super(props)
    this.state = { hasError: false, error: null }
  }

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

  componentDidCatch(error, errorInfo) {
    console.error('HMR Error:', error, errorInfo)
  }

  render() {
    if (this.state.hasError) {
      return (
        <div style={{ padding: 20, background: '#fee', color: '#c00' }}>
          <h2>Hot Reload Error</h2>
          <pre>{this.state.error?.toString()}</pre>
          <button onClick={() => window.location.reload()}>
            Reload Page
          </button>
        </div>
      )
    }

    return this.props.children
  }
}

export default ErrorBoundary

Custom HMR handling:

// App.jsx
import { useState } from 'react'
import Header from './Header'
import Content from './Content'

function App() {
  const [data, setData] = useState({ user: 'John' })

  return (
    <div>
      <Header user={data.user} />
      <Content />
    </div>
  )
}

// Accept hot updates for this module
if (import.meta.hot) {
  import.meta.hot.accept()
}

export default App

HMR with Redux:

// store.js
import { configureStore } from '@reduxjs/toolkit'
import rootReducer from './reducers'

export const store = configureStore({
  reducer: rootReducer
})

// Enable HMR for reducers
if (import.meta.hot) {
  import.meta.hot.accept('./reducers', () => {
    const nextRootReducer = require('./reducers').default
    store.replaceReducer(nextRootReducer)
  })
}

Troubleshooting HMR:

// Check HMR status
if (import.meta.hot) {
  console.log('HMR is enabled')

  import.meta.hot.on('vite:beforeUpdate', () => {
    console.log('About to update module')
  })

  import.meta.hot.on('vite:afterUpdate', () => {
    console.log('Module updated')
  })

  import.meta.hot.on('vite:error', (err) => {
    console.error('HMR Error:', err)
  })
}

// Force full reload if needed
if (import.meta.hot) {
  import.meta.hot.decline() // Don't accept updates, full reload
}

Development best practices:

// ✅ Good - Component preserves state
function GoodComponent() {
  const [count, setCount] = useState(0)
  return <button onClick={() => setCount(count + 1)}>{count}</button>
}

// ❌ Bad - State resets on HMR
let moduleCount = 0
function BadComponent() {
  return <button onClick={() => moduleCount++}>{moduleCount}</button>
}

// ✅ Good - Use environment variables
const API_URL = import.meta.env.VITE_API_URL

// ❌ Bad - Direct process.env in component body
function Component() {
  const url = process.env.REACT_APP_API_URL // May not update with HMR
}

Webpack dev server configuration:

// webpack.config.js
module.exports = {
  devServer: {
    hot: true,
    liveReload: false, // Disable live reload in favor of HMR
    client: {
      overlay: {
        errors: true,
        warnings: false
      },
      progress: true
    },
    historyApiFallback: true,
    compress: true,
    port: 3000
  }
}

Best Practice Note

Use React Fast Refresh over legacy hot reloading for better state preservation. Vite provides Fast Refresh out of the box with zero configuration. Webpack requires react-refresh-webpack-plugin and babel plugin. HMR preserves component state between updates. Use error boundaries to catch HMR errors. Avoid module-level state that doesn’t persist across updates. Enable error overlay in development for instant error feedback. This is how we configure HMR in CoreUI React development—React Fast Refresh with Vite for instant updates, zero refresh time, and preserved component state improving development speed significantly.


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