How to Use React Hooks Inside Class Components

When I work on older React dashboards, I often find a solid class component that handles an employee directory, customer table, or reporting screen. It may already contain useful lifecycle logic, local state, and tested business rules. Then the team needs useState, useEffect, or a custom hook for a new requirement.

The first instinct is usually to import a hook and call it inside the class. That looks logical, but React does not allow it. The good news is that you do not need to rewrite an entire class component just to use a hook.

This guide shows the practical pattern I use: put the hook inside a small functional wrapper, then pass its result into the existing class component through props.

Can You Use React Hooks Inside Class Components?

No. You cannot call React Hooks directly inside a class component.

A hook is a React function, such as useState or useEffect, that adds features like state, side effects, and shared logic to functional components. A class component is a JavaScript class that extends React.Component and uses this.props, this.state, and lifecycle methods such as componentDidMount().

React Hooks only work inside functional components or inside other custom hooks. This code will cause an error:

import React, { Component, useState } from "react";

class EmployeeDirectory extends Component {
const [searchTerm, setSearchTerm] = useState("");

render() {
return <h1>Employee Directory</h1>;
}
}

export default EmployeeDirectory;

Sample output:

Error: Invalid hook call. Hooks can only be called inside the body of a function component.

The problem is not the useState syntax. The problem is where you call it. React tracks hooks in the order that a functional component runs, and a class does not follow that hook execution model.

If you are new to the difference, it also helps to understand React class-based components and React component lifecycle phases.

How to Use React Hooks Inside Class Components

The best approach is to create a small functional component that uses the hook and renders your class component. The wrapper passes hook values and functions as props.

A prop is data that a parent component gives to a child component. Your existing class component receives those values through this.props.

For this tutorial, we will build an employee directory for a company in Austin, Texas. The directory lets a manager search employees by name or office location.

Step 1: Create the class component

Start with the class component that displays employees. This component does not use hooks. It receives the search value and filtered employee list through props.

EmployeeDirectory.jsx

import React, { Component } from "react";

class EmployeeDirectory extends Component {
render() {
const { employees, searchTerm, onSearchChange } = this.props;

return (
<main className="directory">
<h1>Northstar Employee Directory</h1>
<p>Find employees across our USA offices.</p>

<label htmlFor="employee-search">Search employees</label>
<input
id="employee-search"
type="text"
value={searchTerm}
onChange={onSearchChange}
placeholder="Search by name or location"
/>

<p className="count">
Showing {employees.length} employee{employees.length === 1 ? "" : "s"}
</p>

<ul className="employee-list">
{employees.map((employee) => (
<li key={employee.id} className="employee-card">
<h2>{employee.name}</h2>
<p>{employee.role}</p>
<p>{employee.location}</p>
</li>
))}
</ul>
</main>
);
}
}

export default EmployeeDirectory;

This class component receives three props:

  • employees contains the already filtered employee records
  • searchTerm contains the current input value
  • onSearchChange contains the event handler that updates the search value

An event handler is a function that runs after a user action. Here, React runs onSearchChange when someone types in the search box. You can also review how to handle events in React for more practical event examples.

Sample output:

Northstar Employee Directory
Find employees across our USA offices.

Search employees
[ Search by name or location ]

Showing 4 employees

John Miller
Sales Manager
Austin, Texas

Emily Carter
Customer Success Specialist
Seattle, Washington

Michael Brooks
Software Engineer
Denver, Colorado

Olivia Davis
HR Coordinator
Chicago, Illinois

You can refer to the screenshot below to see the output.

Use React Hooks Inside Class Components

At this stage, the input cannot change anything because the component needs a parent to manage the search state.

Step 2: Create a functional hook wrapper

Now create a functional component named EmployeeDirectoryWithHooks. This component owns the hook logic.

A functional component is a reusable JavaScript function that returns UI through JSX. JSX lets you write HTML-like markup inside JavaScript.

EmployeeDirectoryWithHooks.jsx

import React, { useMemo, useState } from "react";
import EmployeeDirectory from "./EmployeeDirectory";

function EmployeeDirectoryWithHooks() {
const [searchTerm, setSearchTerm] = useState("");

const employees = [
{
id: 1,
name: "John Miller",
role: "Sales Manager",
location: "Austin, Texas",
},
{
id: 2,
name: "Emily Carter",
role: "Customer Success Specialist",
location: "Seattle, Washington",
},
{
id: 3,
name: "Michael Brooks",
role: "Software Engineer",
location: "Denver, Colorado",
},
{
id: 4,
name: "Olivia Davis",
role: "HR Coordinator",
location: "Chicago, Illinois",
},
];

const filteredEmployees = useMemo(() => {
const normalizedSearchTerm = searchTerm.toLowerCase().trim();

return employees.filter((employee) => {
const searchableText =
`${employee.name} ${employee.role} ${employee.location}`.toLowerCase();

return searchableText.includes(normalizedSearchTerm);
});
}, [searchTerm]);

function handleSearchChange(event) {
setSearchTerm(event.target.value);
}

return (
<EmployeeDirectory
employees={filteredEmployees}
searchTerm={searchTerm}
onSearchChange={handleSearchChange}
/>
);
}

export default EmployeeDirectoryWithHooks;

The wrapper uses useState to store text entered by the user. State stores data that can change while a user interacts with a component.

This line creates the state value and its update function:

const [searchTerm, setSearchTerm] = useState("");

searchTerm starts as an empty string. setSearchTerm updates it when the user types. React then runs the wrapper again and gives the class component fresh props.

The example also uses useMemo. This hook remembers a calculated value until one of its dependencies changes. In this case, it recalculates filteredEmployees only after searchTerm changes.

Do not use useMemo everywhere by default. It helps most when filtering a larger list or performing an expensive calculation. For a small list, a normal variable works perfectly.

Sample output before searching:

Search employees
[ Search by name or location ]

Showing 4 employees

John Miller — Sales Manager — Austin, Texas
Emily Carter — Customer Success Specialist — Seattle, Washington
Michael Brooks — Software Engineer — Denver, Colorado
Olivia Davis — HR Coordinator — Chicago, Illinois

You can refer to the screenshot below to see the output.

How to Use React Hooks Inside Class Components

Sample output after entering Seattle:

Search employees
[ Seattle ]

Showing 1 employee

Emily Carter
Customer Success Specialist
Seattle, Washington

The class component stays focused on displaying the user interface. The functional wrapper handles hook-based state and filtering logic.

Pro Tip: I have found that a thin hook wrapper works best during gradual modernization. Keep the class component responsible for its existing UI, then move only new hook-based behavior into the wrapper. This lowers regression risk in established React applications.

Step 3: Render the hook wrapper

Your application must render the functional wrapper, not the original class component. The wrapper uses hooks and then renders the class component internally.

App.jsx

import React from "react";
import EmployeeDirectoryWithHooks from "./EmployeeDirectoryWithHooks";
import "./App.css";

function App() {
return <EmployeeDirectoryWithHooks />;
}

export default App;

App.css

* {
box-sizing: border-box;
}

body {
margin: 0;
background: #f4f7fb;
color: #1f2937;
font-family: Arial, sans-serif;
}

.directory {
width: min(700px, calc(100% - 32px));
margin: 40px auto;
padding: 28px;
background: #ffffff;
border-radius: 12px;
box-shadow: 0 8px 24px rgba(31, 41, 55, 0.1);
}

h1 {
margin-top: 0;
color: #123d6a;
}

label {
display: block;
margin-top: 24px;
margin-bottom: 8px;
font-weight: 700;
}

input {
width: 100%;
padding: 12px;
border: 1px solid #9ca3af;
border-radius: 6px;
font-size: 16px;
}

.count {
margin: 20px 0 12px;
color: #4b5563;
}

.employee-list {
display: grid;
gap: 12px;
padding: 0;
list-style: none;
}

.employee-card {
padding: 16px;
border: 1px solid #dbe3ee;
border-radius: 8px;
background: #f9fbfd;
}

.employee-card h2 {
margin: 0 0 8px;
font-size: 19px;
}

.employee-card p {
margin: 4px 0;
}

Sample output:

A white employee directory panel appears on a light gray background.

The panel shows:
- A “Northstar Employee Directory” heading
- A search field
- Four employee cards

When a manager types “Austin,” only John Miller from Austin, Texas remains visible.

You can use this structure in a Vite or Create React App project. If you need a local setup first, follow this guide to set up a React environment and create your first app.

Use React Hooks Inside Class Components With useEffect

The same wrapper pattern works for useEffect.

useEffect runs code after React updates the browser UI. Developers commonly use it for an API call, browser event listener, timer, or document title update. In an older class component, you might use componentDidMount() for similar work. A functional wrapper lets you introduce useEffect without rewriting that class.

The next example loads employee records from a simulated API request. It includes loading, success, and error states because production dashboards need feedback while data loads.

Step 4: Load data with useEffect

EmployeeDirectory.jsx

import React, { Component } from "react";

class EmployeeDirectory extends Component {
render() {
const { employees, loading, error } = this.props;

if (loading) {
return (
<main className="directory">
<h1>Northstar Employee Directory</h1>
<p role="status">Loading employee records...</p>
</main>
);
}

if (error) {
return (
<main className="directory">
<h1>Northstar Employee Directory</h1>
<p role="alert" className="error-message">
{error}
</p>
</main>
);
}

return (
<main className="directory">
<h1>Northstar Employee Directory</h1>
<p>Employee records loaded successfully.</p>

<ul className="employee-list">
{employees.map((employee) => (
<li key={employee.id} className="employee-card">
<h2>{employee.name}</h2>
<p>{employee.role}</p>
<p>{employee.location}</p>
</li>
))}
</ul>
</main>
);
}
}

export default EmployeeDirectory;

EmployeeDirectoryWithHooks.jsx

import React, { useEffect, useState } from "react";
import EmployeeDirectory from "./EmployeeDirectory";

function EmployeeDirectoryWithHooks() {
const [employees, setEmployees] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState("");

useEffect(() => {
let ignoreResponse = false;

async function loadEmployees() {
try {
setLoading(true);
setError("");

await new Promise((resolve) => {
setTimeout(resolve, 900);
});

const employeeData = [
{
id: 1,
name: "John Miller",
role: "Sales Manager",
location: "Austin, Texas",
},
{
id: 2,
name: "Emily Carter",
role: "Customer Success Specialist",
location: "Seattle, Washington",
},
{
id: 3,
name: "Michael Brooks",
role: "Software Engineer",
location: "Denver, Colorado",
},
];

if (!ignoreResponse) {
setEmployees(employeeData);
}
} catch (error) {
if (!ignoreResponse) {
setError("We could not load employee records. Please try again.");
}
} finally {
if (!ignoreResponse) {
setLoading(false);
}
}
}

loadEmployees();

return () => {
ignoreResponse = true;
};
}, []);

return (
<EmployeeDirectory
employees={employees}
loading={loading}
error={error}
/>
);
}

export default EmployeeDirectoryWithHooks;

The empty dependency array, [], tells React to run this useEffect after the wrapper first appears. The cleanup function sets ignoreResponse to true if the wrapper disappears before the delayed request finishes.

In a real app, replace the simulated delay and hard-coded array with a fetch() request to your secured backend API. Always show clear loading and error handling states so users do not see an empty dashboard and assume there are no employees.

Sample output while loading:

Northstar Employee Directory

Loading employee records...

Sample output after the request completes:

Northstar Employee Directory
Employee records loaded successfully.

John Miller
Sales Manager
Austin, Texas

Emily Carter
Customer Success Specialist
Seattle, Washington

Michael Brooks
Software Engineer
Denver, Colorado

If you need to diagnose a blank screen after fetching data, this guide on React API data not displaying in a component can help.

Create a Reusable Hook Wrapper

A wrapper becomes even more useful when several old class components need the same logic. Instead of copying hook code into multiple wrappers, create a custom hook.

A custom hook is a JavaScript function whose name begins with use. It combines reusable hook logic, then returns values that functional components can use.

This example creates useEmployeeSearch. The hook handles the search state and filter logic, while the class component continues to display the employee directory.

Step 5: Move shared logic into a custom hook

useEmployeeSearch.js

import { useMemo, useState } from "react";

function useEmployeeSearch(employees) {
const [searchTerm, setSearchTerm] = useState("");

const filteredEmployees = useMemo(() => {
const query = searchTerm.toLowerCase().trim();

return employees.filter((employee) => {
const employeeText =
`${employee.name} ${employee.role} ${employee.location}`.toLowerCase();

return employeeText.includes(query);
});
}, [employees, searchTerm]);

function handleSearchChange(event) {
setSearchTerm(event.target.value);
}

return {
filteredEmployees,
searchTerm,
handleSearchChange,
};
}

export default useEmployeeSearch;

EmployeeDirectoryWithHooks.jsx

import React from "react";
import EmployeeDirectory from "./EmployeeDirectory";
import useEmployeeSearch from "./useEmployeeSearch";

function EmployeeDirectoryWithHooks() {
const employees = [
{
id: 1,
name: "John Miller",
role: "Sales Manager",
location: "Austin, Texas",
},
{
id: 2,
name: "Emily Carter",
role: "Customer Success Specialist",
location: "Seattle, Washington",
},
{
id: 3,
name: "Michael Brooks",
role: "Software Engineer",
location: "Denver, Colorado",
},
{
id: 4,
name: "Olivia Davis",
role: "HR Coordinator",
location: "Chicago, Illinois",
},
];

const { filteredEmployees, searchTerm, handleSearchChange } =
useEmployeeSearch(employees);

return (
<EmployeeDirectory
employees={filteredEmployees}
searchTerm={searchTerm}
onSearchChange={handleSearchChange}
/>
);
}

export default EmployeeDirectoryWithHooks;

The custom hook gives you one place to improve search behavior later. For example, you could add debouncing, API search, a “clear search” button, or a location filter without changing the display-focused class component.

Sample output after entering Manager:

Search employees
[ Manager ]

Showing 1 employee

John Miller
Sales Manager
Austin, Texas

This pattern also encourages a cleaner component design. The wrapper manages behavior, while the class component manages the presentation. Learn more about this separation in the React container component pattern.

When to Convert the Class Component

A hook wrapper works well when you need a small feature inside an older application. Still, I recommend converting a class component to a functional component when you plan major changes.

Functional components make it easier to share logic through custom hooks. They also avoid mixing class lifecycle methods with hook-based patterns across the same feature.

Convert the class component when:

  • You need several hooks, such as useState, useEffect, and useContext
  • You plan to add more reusable behavior or custom hooks
  • The component has become difficult to test or maintain
  • Your team already writes new components as functional components
  • You want one consistent component style across a new feature

Keep the class component with a wrapper when:

  • The component already works and needs only a focused hook-based addition
  • A full rewrite carries too much testing risk
  • You are modernizing a large React application in small, safe steps
  • A third-party base class or older pattern makes conversion expensive

For a complete migration approach, see how to convert a React class component to a functional component.

Things to Keep in Mind

  • Do not call hooks in classes: Call useState, useEffect, and custom hooks only inside functional components or other custom hooks
  • Keep the wrapper small: Pass focused values and handlers as props instead of moving unrelated class logic into the wrapper
  • Use clear prop names: Names such as searchTerm, onSearchChange, loading, and error make the class component easier to maintain
  • Handle cleanup in useEffect: Return a cleanup function for timers, subscriptions, or pending requests so removed components do not update state later
  • Avoid duplicate state: Calculate filtered employee records from the source list and search term instead of storing both lists as separate state values
  • Plan gradual migration: Use wrappers for targeted changes, but convert a class component when hook-based behavior becomes the main part of the feature

Frequently Asked Questions

Can I call useState inside a React class component?

No. React only allows useState inside a functional component or custom hook. Use a functional wrapper and pass the state value and setter-based handler into the class through props.

Can I use useEffect in a class component?

You cannot call useEffect directly in a class component. Create a functional wrapper that uses useEffect, then provide the loaded data, loading state, and errors to the class component as props.

What replaces componentDidMount with React Hooks?

In a functional component, useEffect(() => { ... }, []) commonly handles work that you would place in componentDidMount(). It runs after the component first renders in the browser.

Should I still use class components in React?

Existing class components still work, especially in mature applications. For new work, most teams choose functional components because hooks make stateful logic simpler to share and organize.

How do I pass hook data to a class component?

Call the hook in a functional parent component, then pass its returned values as props. Your class component accesses them with this.props, such as this.props.searchTerm or this.props.employees.

Can I use a custom hook with a class component?

Not directly. Call the custom hook inside a functional wrapper, then pass the values it returns into the class component as props. This keeps your custom hook compliant with React’s hook rules.

Using React Hooks inside class components really means using a functional wrapper that passes hook-powered values into the class through props. Start with a small wrapper for new behavior, then migrate the class to a functional component only when the feature needs a broader refactor.

You May Also Like

Leave a Comment

51 Python Programs

51 PYTHON PROGRAMS PDF FREE

Download a FREE PDF (112 Pages) Containing 51 Useful Python Programs.

pyython developer roadmap

Aspiring to be a Python developer?

Download a FREE PDF on how to become a Python developer.

Let’s be friends

Be the first to know about sales and special discounts.