Hooks appeared in React to solve fundamental problems with class components, such as difficulty reusing logic, confusing lifecycle, and troubles with understanding this. They replaced class components, providing a simpler and declarative way to manage state and side effects in functional components.
Main reasons for hooks appearance:
Hooks were introduced in React 16.8 as a solution to key problems developers faced when working with class components. They radically changed the approach to writing React components.
Class components didn’t allow easy logic reuse:
// With HOC and render-props code became cumbersome
const EnhancedComponent = withSubscription(
withMouseTracker(
withTheme(
withAuth(Component)
)
)
);Lifecycle methods often contained unrelated logic:
// Logic scattered across different lifecycle methods
componentDidMount() {
// Data subscription
// Interval setup
// State initialization
}
componentDidUpdate() {
// Subscription updates
// Prop changes checking
}
componentWillUnmount() {
// Subscription cleanup
// Interval stopping
}Hooks allowed using state and effects in functional components:
// Instead of class - function with hooks
function Component() {
const [state, setState] = useState(initialValue);
useEffect(() => {
// Effects
}, []);
return <div>{/* JSX */}</div>;
}Custom hooks replaced higher-order patterns and render-props:
// Instead of HOC - reusable hook
function useLocalStorage(key, initialValue) {
const [value, setValue] = useState(() => {
// Initialization logic
});
return [value, setValue];
}// Class component
class Counter extends Component {
state = { count: 0 };
increment = () => {
this.setState({ count: this.state.count + 1 });
};
render() {
return (
<button onClick={this.increment}>
{this.state.count}
</button>
);
}
}
// Functional component with hooks
function Counter() {
const [count, setCount] = useState(0);
const increment = () => setCount(count + 1);
return (
<button onClick={increment}>
{count}
</button>
);
}// Reusable logic as a hook
function useToggle(initialValue = false) {
const [value, setValue] = useState(initialValue);
const toggle = useCallback(() => setValue(!value), [value]);
return [value, toggle];
}// ❌ Mechanical replacement without understanding concepts
function BadComponent() {
const [state1, setState1] = useState();
const [state2, setState2] = useState();
const [state3, setState3] = useState();
// ... many useState calls in a row
}
// ✅ Grouping related state
function GoodComponent() {
const [formState, setFormState] = useState({
name: '',
email: '',
age: ''
});
}// ❌ Violating hook rules
function Component({ condition }) {
if (condition) {
const [state, setState] = useState(); // Error!
}
}
// ✅ Following hook rules
function Component({ condition }) {
const [state, setState] = useState();
if (condition) {
// Logic inside condition
}
}Hooks are fully compatible with existing code and can be used together with class components.
Hooks radically simplified React development, making code more readable, reusable, and maintainable compared to class components.
What problems do hooks solve compared to this class component?
class UserProfile extends Component {
state = {
user: null,
loading: true,
error: null
};
componentDidMount() {
this.fetchUser();
}
componentDidUpdate(prevProps) {
if (prevProps.userId !== this.props.userId) {
this.fetchUser();
}
}
componentWillUnmount() {
// Cleanup if needed
}
fetchUser = async () => {
try {
this.setState({ loading: true, error: null });
const user = await fetchUser(this.props.userId);
this.setState({ user, loading: false });
} catch (error) {
this.setState({ error, loading: false });
}
};
render() {
const { user, loading, error } = this.state;
if (loading) return <Loading />;
if (error) return <Error message={error.message} />;
if (!user) return <NotFound />;
return <UserDetails user={user} />;
}
}Answer: Hooks solve several key problems with this class component:
Class component problems:
Hook solution:
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchUser = async () => {
try {
setLoading(true);
setError(null);
const userData = await fetchUser(userId);
setUser(userData);
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
};
fetchUser();
}, [userId]); // Automatic subscription to userId changes
if (loading) return <Loading />;
if (error) return <Error message={error.message} />;
if (!user) return <NotFound />;
return <UserDetails user={user} />;
}Benefits of hook solution:
Hooks make code more declarative and understandable, eliminating the need to remember complex lifecycle method hierarchies.
Want more articles to prepare for interviews? Subscribe to EasyAdvice, bookmark the site and improve yourself every day 💪