You cannot use React hooks inside a class component. Hooks only work in function components and in custom hooks, and React throws an error if you call one from a class. There are three practical ways round it: wrap the class in a small function component that calls the hook and passes the values down as props, use a render-prop component for the same job, or convert the class to a function component, which is usually the right answer. This guide shows the real error, all three approaches running, and a lifecycle-to-hooks map for the conversion.
Every example was built and run with React 19.3.0 in a Vite project, and the screenshots come from the browser. Reference: Rules of Hooks on react.dev.
Why hooks do not work in class components
Hooks rely on React keeping a list of state slots for the component that is currently rendering, in the order the hooks are called. A class component does not have that list: its state lives on the instance, in this.state. So when a hook is called from a class render, React cannot find a matching dispatcher and stops. This is the message it gives, caught and printed on the page:
import { Component, useState } from "react";
class Counter extends Component {
render() {
let message = "no error";
try {
const [count] = useState(0); // a hook inside a class: not allowed
message = `count is ${count}`;
} catch (error) {
message = `${error.name}: ${error.message}`;
console.log("React said:", error.message);
}
return (
<div style={{ fontFamily: "system-ui", padding: 16 }}>
<h3 style={{ margin: "0 0 8px" }}>Calling useState() inside a class</h3>
<pre style={{ background: "#fff4f4", border: "1px solid #f3c2c2", borderRadius: 6,
padding: 12, whiteSpace: "pre-wrap", color: "#8a1f1f" }}>{message}</pre>
</div>
);
}
}
export default function App() {
return <Counter />;
}
useState() in render(). This is React’s own message.You will see the same error from three other mistakes, which is worth knowing because the text does not tell you which one you hit: calling a hook inside a condition or a loop, having two copies of React in node_modules, and calling a hook from an event handler rather than the component body.
The fix most teams use: a wrapper component
The class stays exactly as it is and receives what it needs through props. A tiny function component around it calls the hooks. This is what withRouter and similar helpers have always done, and it is the least invasive change you can make to a large class:
import { Component, useState } from "react";
// 1. the class stays a class, and receives everything it needs as props
class OrderPanel extends Component {
render() {
const { count, increment, city } = this.props;
return (
<div style={{ fontFamily: "system-ui", padding: 16 }}>
<h3 style={{ margin: "0 0 8px" }}>Orders from {city}</h3>
<p id="count">Selected: {count}</p>
<button id="add" onClick={increment}
style={{ padding: "6px 12px", borderRadius: 6, border: "1px solid #0b6bcb",
background: "#0b6bcb", color: "#fff", cursor: "pointer" }}>
Add an order
</button>
</div>
);
}
}
// 2. a function component calls the hooks and passes the values down
function withCounter(ClassComponent) {
return function Wrapper(props) {
const [count, setCount] = useState(0);
const increment = () => setCount(current => current + 1);
return <ClassComponent {...props} count={count} increment={increment} />;
};
}
const OrderPanelWithCounter = withCounter(OrderPanel);
export default function App() {
return <OrderPanelWithCounter city="Austin" />;
}
Pass callbacks down as well as values, as increment is here, so the class can update the state it does not own. If several classes need the same data, put the hooks in one wrapper and reuse it, in the same way you would pass a function to a child component.
The same idea as a render prop
If you would rather not wrap the class, invert it: a function component calls the hook and hands the value to a function you give it as children. This suits one-off cases, such as a single class that needs the window width:
import { Component, useState, useEffect } from "react";
// the hook lives in a function component that renders whatever you give it
function WindowWidth({ children }) {
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
const onResize = () => setWidth(window.innerWidth);
window.addEventListener("resize", onResize);
return () => window.removeEventListener("resize", onResize);
}, []);
return children(width);
}
class Layout extends Component {
render() {
return (
<WindowWidth>
{(width) => (
<p style={{ fontFamily: "system-ui", padding: 16 }}>
The window is {width}px wide, so this is the {width < 768 ? "mobile" : "desktop"} layout.
</p>
)}
</WindowWidth>
);
}
}
export default function App() {
console.log("rendered at", window.innerWidth, "px");
return <Layout />;
}
Output:
rendered at 900 px
Both approaches are the same trick in different clothing. The hook always runs inside a function component; the class only consumes the result.
Converting the class to a function component
This is the real fix. The two components below do the same job: a counter that ticks on a timer, logs when it passes two ticks, and cleans up when it is removed. The class uses three lifecycle methods; the function uses two effects. Both were mounted together and then unmounted with a button, and this is what each one logged:
import { Component, useState, useEffect } from "react";
// BEFORE: the class version
class ClassClock extends Component {
constructor(props) {
super(props);
this.state = { ticks: 0 };
}
componentDidMount() {
console.log("class: mounted");
this.timer = setInterval(() => this.setState(s => ({ ticks: s.ticks + 1 })), 100);
}
componentDidUpdate(prevProps, prevState) {
if (prevState.ticks !== this.state.ticks && this.state.ticks === 2) {
console.log("class: reached", this.state.ticks, "ticks");
}
}
componentWillUnmount() {
console.log("class: cleaned up");
clearInterval(this.timer);
}
render() {
return <p>class ticks: {this.state.ticks}</p>;
}
}
// AFTER: the same component with hooks
function HookClock() {
const [ticks, setTicks] = useState(0);
useEffect(() => { // componentDidMount + componentWillUnmount
console.log("hooks: mounted");
const timer = setInterval(() => setTicks(t => t + 1), 100);
return () => {
console.log("hooks: cleaned up");
clearInterval(timer);
};
}, []);
useEffect(() => { // componentDidUpdate, but only for ticks
if (ticks === 2) console.log("hooks: reached", ticks, "ticks");
}, [ticks]);
return <p>hook ticks: {ticks}</p>;
}
export default function App() {
const [show, setShow] = useState(true);
return (
<div style={{ fontFamily: "system-ui", padding: 16 }}>
<button id="toggle" onClick={() => setShow(s => !s)}
style={{ padding: "6px 12px", marginBottom: 8, borderRadius: 6,
border: "1px solid #0b6bcb", background: "#fff", color: "#0b6bcb", cursor: "pointer" }}>
unmount both
</button>
{show && (
<>
<ClassClock />
<HookClock />
</>
)}
</div>
);
}
Console output from mounting both, then unmounting them:
class: mounted
hooks: mounted
class: reached 2 ticks
hooks: reached 2 ticks
class: cleaned up
hooks: cleaned up
Notice the order in the output: with hooks the cleanup runs in the same effect that created the timer, so the setup and the teardown sit next to each other instead of in methods a hundred lines apart.
| Class lifecycle | Hook equivalent |
|---|---|
constructor state | useState(initial) |
componentDidMount | useEffect(() => { ... }, []) |
componentDidUpdate | useEffect(() => { ... }, [value]) |
componentWillUnmount | the function returned from useEffect |
shouldComponentUpdate | React.memo around the component |
static contextType | useContext(MyContext) |
instance fields (this.timer) | useRef() |
Context: contextType versus useContext
Context is the most common reason a class needs “a hook”. It does not: classes have had static contextType for years. The limitation is that a class can read only one context that way, while a function component can call useContext as many times as it needs:
import { Component, createContext, useContext } from "react";
const ThemeContext = createContext("light");
// a class reads context with static contextType (one context only)
class ClassBadge extends Component {
static contextType = ThemeContext;
render() {
return <span style={{ fontFamily: "system-ui" }}>class sees: {this.context}</span>;
}
}
// a function component uses the hook, and can read as many contexts as it likes
function HookBadge() {
const theme = useContext(ThemeContext);
return <span style={{ fontFamily: "system-ui", marginLeft: 16 }}>hook sees: {theme}</span>;
}
export default function App() {
return (
<ThemeContext.Provider value="dark">
<div style={{ padding: 16 }}>
<ClassBadge />
<HookBadge />
</div>
</ThemeContext.Provider>
);
}
this.context, the function through the hook.Should you convert everything?
- Converting is worth it when you are already changing the component, when it needs two or more contexts, or when the lifecycle methods have grown into a tangle of unrelated concerns.
- It is not worth it for a stable class that works, and class components are not deprecated: React 19 still supports them.
- Error boundaries are the one thing that still needs a class. There is no hook for
componentDidCatch. - Do not half-convert. A class with a wrapper for every hook it needs is harder to read than the function component it should have become.
More React guides worth reading:
- React components with children
- Pass a function to a child component
- Prevent a child component re-rendering
- Error boundaries with functional components
- React component naming conventions
Frequently asked questions
Can I use React hooks in a class component?
No. Hooks only work in function components and custom hooks. Calling one from a class throws an invalid hook call error at render time.
Can I call useState inside a class component?
No. Use this.state and this.setState in a class, or move the state into a function component that wraps it.
How do I pass hook data to a class component?
Wrap the class in a function component that calls the hook and passes the values, and any callbacks, down as props.
Can I use a custom hook with a class component?
Only indirectly. Call the custom hook in a wrapper function component and hand its return value to the class as props.
What replaces componentDidMount with hooks?
useEffect(() => { ... }, []). The function it returns replaces componentWillUnmount.
Are class components deprecated in React 19?
No. They still work and are still supported. Error boundaries in particular still have to be classes.
Why do I get an invalid hook call error?
Either the hook is in a class, inside a condition or loop, called from an event handler, or your project has two copies of React installed.
Bijay Kumar is a 13-time Microsoft MVP with more than 18 years in software development, and the founder of Python Guides and TSinfo Technologies. He started out building .NET and SharePoint solutions at HP, TCS and KPIT before moving into Python, machine learning and AI, and he also builds web apps with TypeScript and React. He writes the tutorials here himself, and every example is run before publishing so you see the real output. More about Bijay · Microsoft MVP profile · LinkedIn