Before hooks arrived in React 16.8 7,897 (February 2019), a component that needed state or lifecycle had to be a class extending Component: props under this.props, state under this.state, updates through this.setState, and markup returned from render(). React still supports classes and has never deprecated them, but the Component reference now opens with "we don't recommend using them in new code". You will meet them in older codebases and in interview questions, so read the syntax even if you never write it.
class Greeting extends React.Component { // legacy
constructor(props) {
super(props);
this.state = { open: false };
this.toggle = this.toggle.bind(this); // or a class field arrow
}
toggle() { this.setState(s => ({ open: !s.open })); }
render() {
const { name } = this.props;
return <button onClick={this.toggle}>{name}: {String(this.state.open)}</button>;
}
}
function Greeting({ name }) { // modern equivalent
const [open, setOpen] = useState(false);
return <button onClick={() => setOpen(o => !o)}>{name}: {String(open)}</button>;
}The class version is longer, needs this bound correctly, and — the reason hooks exist — has no way to share stateful logic except higher-order components and render props, both of which nest wrappers around the tree. A custom hook (Custom Hooks) shares the logic with none of that.
| Class member | Function equivalent |
|---|---|
| constructor state, render() | useState, the function body |
| componentDidMount, componentDidUpdate | useEffect with dependencies |
| componentWillUnmount | the cleanup returned from useEffect |
| shouldComponentUpdate, PureComponent | memo, or the React Compiler |
| getDerivedStateFromError, componentDidCatch | no equivalent — still class only |
Error boundaries are the one thing you cannot express as a function; React states plainly that there is no way to write one as a function component. Most projects install react-error-boundary 7,993 (https://github.com/bvaughn/react-error-boundary 7,993 ) rather than hand-write the class (Error Boundaries). React 19 also removed several class-era APIs outright — string refs (this.refs.input), legacy context (contextTypes, getChildContext), module pattern factories, React.createFactory and propTypes checking — so a class written in 2017 will not run unmodified.