In React, one of the key decisions you'll face when creating components is choosing between functional components and class components. Both types have their advantages and disadvantages, and the right choice depends on project requirements, team preferences, and your coding style. In this article, we’ll discuss when to use functions versus classes in React, with five examples of code illustrating the differences.
A functional component is the best choice when dealing with a component that only renders data passed as props.
// Functional component
const Welcome = ({ name }) => <h1>Welcome, {name}!</h1>;
Class:
// Class component
class Welcome extends React.Component {
render() {
return <h1>Welcome, {this.props.name}!</h1>;
}
}
Here, the function is shorter and more readable. The component has no internal state, so a class is unnecessary.
Class components are helpful when managing state was primarily done in classes, especially before Hooks became available. Nowadays, you can manage state in both types, but here's a class-based example.
import { useState } from 'react';
const Counter = () => {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
};
Class:
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
increment = () => {
this.setState({ count: this.state.count + 1 });
};
render() {
return (
<div>
<p>Count: {this.state.count}</p>
<button onClick={this.increment}>Increment</button>
</div>
);
}
}
Using functional components with Hooks is often preferred now, but classes are still useful for more complex components.
Hooks like useEffect
and useContext
make functional components capable of managing state and lifecycle behavior. Here’s an example using useEffect
to fetch data after component mount.
import { useEffect, useState } from 'react';
const UserProfile = ({ userId }) => {
const [profile, setProfile] = useState(null);
useEffect(() => {
fetch(`/api/user/${userId}`)
.then(response => response.json())
.then(data => setProfile(data));
}, [userId]);
if (!profile) return <p>Loading...</p>;
return <div>{profile.name}</div>;
};
When you need complex lifecycle management, class components can offer more control.
class Timer extends React.Component {
constructor(props) {
super(props);
this.state = { time: new Date() };
}
componentDidMount() {
this.timerID = setInterval(() => this.tick(), 1000);
}
componentWillUnmount() {
clearInterval(this.timerID);
}
tick() {
this.setState({ time: new Date() });
}
render() {
return <p>The time is {this.state.time.toLocaleTimeString()}</p>;
}
}
Scalability is essential for larger projects. Over time, classes can offer a more structured way to control code. However, Hooks allow for similar control without creating complex structures.
import { useState, useEffect } from 'react';
const DataFetcher = ({ url }) => {
const [data, setData] = useState(null);
useEffect(() => {
fetch(url)
.then(response => response.json())
.then(data => setData(data));
}, [url]);
return data ? <div>{data.content}</div> : <p>Loading...</p>;
};
useState
, useEffect
, and useContext
fulfill lifecycle and state management needs.The decision of which component type to choose depends on project requirements and specific needs of your code. In practice, functional components with Hooks are now more common for their simplicity and flexibility, while class components remain useful for managing complex logic in legacy applications.