Mastering React Hooks: A Beginner’s Guide with Code Examples
React Hooks have revolutionized the way developers write React components. They allow you to use state and lifecycle methods in functional components, making code cleaner and easier to maintain.
In this post, we’ll cover:
- What React Hooks are and why they matter
- How to use essential hooks (
useState
,useEffect
,useContext
) - Creating custom hooks for reusable logic
- Common pitfalls and best practices
Let’s dive in!
1. What Are React Hooks?
Hooks are built-in functions that let you use state and lifecycle features in functional components without writing class components.
Before Hooks (using class components):
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>
);
}
}
With Hooks (using functional components):
import { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
Why Hooks?
- Less boilerplate
- Easier to read and maintain
- No need for
this
keyword
2. Understanding useState
Hook
The useState
hook lets you add state to functional components.
Example
import { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<h2>Counter: {count}</h2>
<button onClick={() => setCount(count + 1)}>Increase</button>
<button onClick={() => setCount(count - 1)}>Decrease</button>
</div>
);
}
export default Counter;
Key Points:
useState(0)
initializes state with a default value (0
).setCount(newValue)
updates the state.- Clicking the button re-renders the component with updated state.
3. Using useEffect
for Side Effects
The useEffect
hook is used to handle side effects (e.g., fetching data, DOM manipulation, subscriptions).
Example: Fetching API Data with useEffect
import { useState, useEffect } from "react";
function FetchData() {
const [data, setData] = useState([]);
useEffect(() => {
fetch("https://jsonplaceholder.typicode.com/posts")
.then((response) => response.json())
.then((data) => setData(data));
// Cleanup function (optional)
return () => console.log("Component unmounted");
}, []); // Empty dependency array runs effect only once
return (
<div>
<h2>Fetched Posts</h2>
<ul>
{data.slice(0, 5).map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</div>
);
}
export default FetchData;
Key Points:
useEffect(callback, [dependencies])
runs after render.- Empty dependency array
[]
means it runs only once (likecomponentDidMount
). - If dependencies are provided (
[count]
), it re-runs whencount
changes.
4. Managing Global State with useContext
The useContext
hook lets you share global state without prop drilling.
Example: Theme Context
import { createContext, useState, useContext } from "react";
// 1. Create Context
const ThemeContext = createContext();
// 2. Theme Provider
function ThemeProvider({ children }) {
const [theme, setTheme] = useState("light");
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
);
}
// 3. Custom Hook to use Theme
function useTheme() {
return useContext(ThemeContext);
}
// 4. Themed Button Component
function ThemedButton() {
const { theme, setTheme } = useTheme();
return (
<button
style={{
background: theme === "light" ? "#fff" : "#333",
color: theme === "light" ? "#000" : "#fff",
}}
onClick={() => setTheme(theme === "light" ? "dark" : "light")}
>
Toggle Theme
</button>
);
}
// 5. App Component
function App() {
return (
<ThemeProvider>
<ThemedButton />
</ThemeProvider>
);
}
export default App;
📌 Key Points:
createContext()
defines a global store.useContext(ThemeContext)
allows access without passing props.- Wrapping in
ThemeProvider
ensures all components can access it.
5. Creating a Custom Hook (useLocalStorage
)
You can create custom hooks to reuse logic across components.
Example: Persisting Data in Local Storage
import { useState, useEffect } from "react";
function useLocalStorage(key, initialValue) {
const [value, setValue] = useState(() => {
return JSON.parse(localStorage.getItem(key)) || initialValue;
});
useEffect(() => {
localStorage.setItem(key, JSON.stringify(value));
}, [key, value]);
return [value, setValue];
}
// Usage in a Counter component
function Counter() {
const [count, setCount] = useLocalStorage("count", 0);
return (
<div>
<h2>Count: {count}</h2>
<button onClick={() => setCount(count + 1)}>Increase</button>
</div>
);
}
export default Counter;
📌 Key Points:
useLocalStorage
behaves likeuseState
but syncs with local storage.useEffect
updates storage whenevercount
changes.- Helps persist data across page reloads.
6. Common Mistakes and Best Practices
❌ Wrong: Updating state inside render
function App() {
const [count, setCount] = useState(0);
setCount(count + 1); // ❌ Infinite loop!
}
✅ Correct: Use event handlers
<button onClick={() => setCount(count + 1)}>Increase</button>
Final Thoughts
React Hooks make components simpler, more readable, and more reusable. Mastering them will help you write better React applications with less code.
Quick Recap:
✅ useState
– Manage component state
✅ useEffect
– Handle side effects
✅ useContext
– Manage global state
✅ Custom hooks – Extract reusable logic
What’s your favorite React Hook? Let me know in the comments! 🚀`