Understanding React Hooks
React Hooks allow you to use state and other React features in function components, without writing a class. Introduced in React 16.8, they revolutionize how you write components by making them cleaner, easier to test, and simpler to reason about.
Before Hooks, you had to convert a function component to a class component if you needed to add state or use lifecycle methods. Hooks let you do all of that and more within functional components.
The Rules of Hooks
To ensure Hooks work correctly, you must follow two simple rules:
- Only Call Hooks at the Top Level: Don't call Hooks inside loops, conditions, or nested functions. This ensures that Hooks are called in the same order each time a component renders, which allows React to correctly preserve the state of Hooks between multiple
useState
anduseEffect
calls. - Only Call Hooks from React Functions: Call Hooks from React function components or from custom Hooks. Don't call them from regular JavaScript functions.
The useState
Hook
The useState
hook is the most common hook and is used to add state to a functional component.
Here's how it works:
import React, { useState } from 'react';
function Counter() {
// useState returns a pair: the current state value and a function that lets you update it.
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
In this example, useState(0)
initializes our state count
with 0
. setCount
is the function we use to update the count
state. When setCount
is called, React re-renders the Counter
component with the new count
value.
The useEffect
Hook
The useEffect
hook lets you perform side effects in function components. It is a close replacement for componentDidMount
, componentDidUpdate
, and componentWillUnmount
in class components.
Side effects can include data fetching, subscriptions, or manually changing the DOM.
Here is an example of fetching data with useEffect
:
import React, { useState, useEffect } from 'react';
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
async function fetchUser() {
const response = await fetch(`https://api.example.com/users/${userId}`);
const data = await response.json();
setUser(data);
}
fetchUser();
}, [userId]); // The dependency array
if (!user) {
return <p>Loading...</p>;
}
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
);
}
The second argument to useEffect
is the dependency array. The effect will only re-run if the values in this array change. If you pass an empty array []
, the effect will only run once after the initial render.
Custom Hooks
Custom Hooks allow you to extract component logic into reusable functions. A custom hook is a JavaScript function whose name starts with "use" and that may call other Hooks.
Let's create a custom hook to track the window width:
import { useState, useEffect } from 'react';
function useWindowWidth() {
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
const handleResize = () => setWidth(window.innerWidth);
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
return width;
}
Now you can use this custom hook in any component to get the current window width:
function MyComponent() {
const width = useWindowWidth();
return <p>Window width is {width}px</p>;
}
Custom Hooks are a powerful way to share stateful logic, reduce duplication, and keep your components clean and focused on their specific tasks. By adopting hooks, you can write more declarative and maintainable React applications.