# React Hooks: Deep Dive into Modern State Management
## Introduction to React Hooks
React Hooks revolutionized how developers write functional components by
introducing state and lifecycle capabilities previously available only in class
components. Hooks enable code reuse, better organization, and cleaner component
logic. This comprehensive guide explores React Hooks in depth, covering built-in
hooks, custom hooks, and best practices.
## What Are React Hooks?
Hooks are JavaScript functions that let you "hook into" React features. They allow
functional components to have state, lifecycle effects, context, and other React
features without writing class components. Hooks were introduced in React 16.8 and
have become the standard way to write React applications.
### Key Benefits
**Simpler Code**: Functional components with hooks are often more concise than
class components.
**Code Reuse**: Custom hooks enable logic sharing between components without
higher-order components or render props.
**Better Organization**: Related logic can be organized together in a single hook.
**Easier Testing**: Hooks are plain JavaScript functions that are easier to test.
## Rules of Hooks
Before diving into specific hooks, understand the two critical rules:
**Rule 1: Only Call Hooks at the Top Level**
Don't call hooks inside loops, conditions, or nested functions. Hooks must always
be called in the same order.
```javascript
// ✓ Correct
function MyComponent() {
const [count, setCount] = useState(0);
const [name, setName] = useState('');
return <div>Count: {count}</div>;
}
// ✗ Wrong - calling hook inside condition
function BadComponent({ condition }) {
if (condition) {
const [count, setCount] = useState(0); // ERROR!
}
return <div></div>;
}
```
**Rule 2: Only Call Hooks from React Functions**
Hooks should only be called from React functional components or custom hooks, not
from regular JavaScript functions.
## useState Hook
The most fundamental hook for managing state in functional components.
### Basic Usage
```javascript
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>
);
}
```
### Multiple State Variables
```javascript
function UserForm() {
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [age, setAge] = useState('');
return (
<form>
<input
value={name}
onChange={(e) => setName([Link])}
placeholder="Name"
/>
<input
value={email}
onChange={(e) => setEmail([Link])}
placeholder="Email"
/>
<input
value={age}
onChange={(e) => setAge([Link])}
placeholder="Age"
/>
</form>
);
}
```
### State Updates with Objects
```javascript
function UserProfile() {
const [user, setUser] = useState({
name: 'John',
email: 'john@[Link]',
city: 'New York'
});
const updateUser = (field, value) => {
setUser(prevUser => ({
...prevUser,
[field]: value
}));
};
return (
<div>
<input
value={[Link]}
onChange={(e) => updateUser('name', [Link])}
/>
</div>
);
}
```
### Functional Updates
Use the previous state to calculate the new state:
```javascript
function Counter() {
const [count, setCount] = useState(0);
const increment = () => {
// Previous state approach - ensures correct update
setCount(prevCount => prevCount + 1);
};
return (
<button onClick={increment}>
Count: {count}
</button>
);
}
```
## useEffect Hook
The useEffect hook handles side effects like data fetching, subscriptions, and DOM
manipulation.
### Basic Side Effects
```javascript
import { useState, useEffect } from 'react';
function Timer() {
const [seconds, setSeconds] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setSeconds(s => s + 1);
}, 1000);
// Cleanup function
return () => clearInterval(interval);
}, []); // Empty dependency array - runs once on mount
return <p>Timer: {seconds}s</p>;
}
```
### Dependency Array
The dependency array controls when the effect runs:
```javascript
function DataFetcher({ userId }) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
setLoading(true);
fetch(`/api/users/${userId}`)
.then(res => [Link]())
.then(data => {
setData(data);
setLoading(false);
});
}, [userId]); // Effect runs when userId changes
if (loading) return <p>Loading...</p>;
return <div>User: {[Link]}</div>;
}
```
### Dependency Array Patterns
```javascript
// No dependency array - runs after every render
useEffect(() => {
[Link]('After every render');
});
// Empty array - runs once after mount
useEffect(() => {
[Link]('After mount only');
}, []);
// With dependencies - runs when any dependency changes
useEffect(() => {
[Link]('After mount or when count changes');
}, [count]);
// Multiple dependencies
useEffect(() => {
[Link]('Runs when userId or postId changes');
}, [userId, postId]);
```
### Complex Data Fetching
```javascript
function PostList() {
const [posts, setPosts] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let isMounted = true;
const fetchPosts = async () => {
try {
const response = await fetch('/api/posts');
const data = await [Link]();
if (isMounted) {
setPosts(data);
setError(null);
}
} catch (err) {
if (isMounted) {
setError([Link]);
}
} finally {
if (isMounted) {
setLoading(false);
}
}
};
fetchPosts();
// Cleanup
return () => {
isMounted = false;
};
}, []);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error}</p>;
return (
<ul>
{[Link](post => (
<li key={[Link]}>{[Link]}</li>
))}
</ul>
);
}
```
## useContext Hook
Access context values without nesting Consumer components.
### Setting Up Context
```javascript
import { createContext, useContext, useState } from 'react';
// Create context
const ThemeContext = createContext();
// Provider component
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
const toggleTheme = () => {
setTheme(prev => prev === 'light' ? 'dark' : 'light');
};
return (
<[Link] value={{ theme, toggleTheme }}>
{children}
</[Link]>
);
}
// Custom hook to use context
function useTheme() {
const context = useContext(ThemeContext);
if (!context) {
throw new Error('useTheme must be used within ThemeProvider');
}
return context;
}
// Usage
function App() {
return (
<ThemeProvider>
<Header />
<Main />
</ThemeProvider>
);
}
function Header() {
const { theme, toggleTheme } = useTheme();
return (
<header style={{ background: theme === 'light' ? '#fff' : '#333' }}>
<button onClick={toggleTheme}>
Switch to {theme === 'light' ? 'dark' : 'light'} mode
</button>
</header>
);
}
```
## useReducer Hook
For complex state logic involving multiple sub-values or when next state depends on
previous one.
### Basic useReducer Pattern
```javascript
const initialState = { count: 0 };
function reducer(state, action) {
switch ([Link]) {
case 'INCREMENT':
return { count: [Link] + 1 };
case 'DECREMENT':
return { count: [Link] - 1 };
case 'RESET':
return { count: 0 };
default:
return state;
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
<p>Count: {[Link]}</p>
<button onClick={() => dispatch({ type: 'INCREMENT' })}>
+
</button>
<button onClick={() => dispatch({ type: 'DECREMENT' })}>
-
</button>
<button onClick={() => dispatch({ type: 'RESET' })}>
Reset
</button>
</div>
);
}
```
### useReducer with Payload
```javascript
const initialState = { count: 0, step: 1 };
function reducer(state, action) {
switch ([Link]) {
case 'ADD':
return { ...state, count: [Link] + [Link] };
case 'SET_STEP':
return { ...state, step: [Link] };
default:
return state;
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
const handleIncrement = () => {
dispatch({ type: 'ADD', payload: [Link] });
};
return (
<div>
<p>Count: {[Link]}, Step: {[Link]}</p>
<button onClick={handleIncrement}>Add {[Link]}</button>
<input
type="number"
value={[Link]}
onChange={(e) => dispatch({
type: 'SET_STEP',
payload: parseInt([Link])
})}
/>
</div>
);
}
```
## useCallback Hook
Memoize callback functions to prevent unnecessary re-renders of child components.
```javascript
import { useCallback, useState, memo } from 'react';
const ExpensiveChild = memo(({ onButtonClick }) => {
[Link]('ExpensiveChild rendered');
return <button onClick={onButtonClick}>Click me</button>;
});
function Parent() {
const [count, setCount] = useState(0);
const [otherState, setOtherState] = useState('');
// Without useCallback - new function on every render
const handleClickBad = () => {
[Link]('Button clicked');
};
// With useCallback - same function reference
const handleClickGood = useCallback(() => {
[Link]('Button clicked');
}, []); // Empty dependencies means function never changes
// useCallback with dependencies
const handleAdd = useCallback(() => {
setCount(c => c + 1);
}, []); // Safe because setCount doesn't change
return (
<div>
<ExpensiveChild onButtonClick={handleClickGood} />
<p>Count: {count}</p>
<input
value={otherState}
onChange={(e) => setOtherState([Link])}
/>
</div>
);
}
```
## useMemo Hook
Memoize expensive computations to improve performance.
```javascript
import { useMemo, useState } from 'react';
function ComponentWithExpensiveCalculation() {
const [count, setCount] = useState(0);
const [items, setItems] = useState([1, 2, 3, 4, 5]);
// Expensive calculation
const expensiveValue = useMemo(() => {
[Link]('Computing expensive value');
return [Link]((acc, item) => {
// Simulate expensive computation
let result = item;
for (let i = 0; i < 1000000000; i++) {
result += 0;
}
return acc + result;
}, 0);
}, [items]); // Only recalculate when items change
return (
<div>
<p>Expensive Value: {expensiveValue}</p>
<button onClick={() => setCount(count + 1)}>
Increment Count: {count}
</button>
<p>Note: expensiveValue doesn't recalculate on count change</p>
</div>
);
}
```
## useRef Hook
Access DOM nodes directly or keep a mutable value that doesn't cause re-renders.
```javascript
import { useRef, useState } from 'react';
function TextInput() {
const inputRef = useRef(null);
const focusInput = () => {
[Link]();
};
return (
<div>
<input ref={inputRef} type="text" />
<button onClick={focusInput}>Focus Input</button>
</div>
);
}
```
### Storing Mutable Values
```javascript
function Timer() {
const intervalRef = useRef(null);
const [seconds, setSeconds] = useState(0);
const startTimer = () => {
[Link] = setInterval(() => {
setSeconds(s => s + 1);
}, 1000);
};
const stopTimer = () => {
clearInterval([Link]);
};
return (
<div>
<p>Time: {seconds}s</p>
<button onClick={startTimer}>Start</button>
<button onClick={stopTimer}>Stop</button>
</div>
);
}
```
## Creating Custom Hooks
Custom hooks allow you to extract component logic into reusable functions.
### Example: useFormInput Hook
```javascript
function useFormInput(initialValue = '') {
const [value, setValue] = useState(initialValue);
return {
value,
setValue,
bind: {
value,
onChange: e => setValue([Link])
},
reset: () => setValue(initialValue)
};
}
function LoginForm() {
const email = useFormInput('');
const password = useFormInput('');
const handleSubmit = (e) => {
[Link]();
[Link]([Link], [Link]);
[Link]();
[Link]();
};
return (
<form onSubmit={handleSubmit}>
<input {...[Link]} type="email" placeholder="Email" />
<input {...[Link]} type="password" placeholder="Password" />
<button type="submit">Login</button>
</form>
);
}
```
### Example: useAsync Hook for Data Fetching
```javascript
function useAsync(asyncFunction, immediate = true) {
const [status, setStatus] = useState('idle');
const [data, setData] = useState(null);
const [error, setError] = useState(null);
const execute = useCallback(async () => {
setStatus('pending');
try {
const response = await asyncFunction();
setData(response);
setStatus('success');
} catch (err) {
setError(err);
setStatus('error');
}
}, [asyncFunction]);
useEffect(() => {
if (immediate) {
execute();
}
}, [execute, immediate]);
return { status, data, error, execute };
}
function UserProfile({ userId }) {
const fetchUser = async () => {
const res = await fetch(`/api/users/${userId}`);
return [Link]();
};
const { status, data: user, error } = useAsync(fetchUser);
if (status === 'pending') return <p>Loading...</p>;
if (status === 'error') return <p>Error: {[Link]}</p>;
return <p>User: {[Link]}</p>;
}
```
## Hook Dependencies and Performance
### Common Mistakes
```javascript
// ✗ Mistake 1: Missing dependency
function BadEffect({ userId }) {
useEffect(() => {
// This should depend on userId but doesn't
fetchUser(userId);
}, []); // Bug: missing userId dependency
}
// ✓ Correct
function GoodEffect({ userId }) {
useEffect(() => {
fetchUser(userId);
}, [userId]); // Correctly includes dependency
}
// ✗ Mistake 2: Unnecessary object in dependency
function BadObject() {
const config = { timeout: 5000 };
useEffect(() => {
// config is recreated every render
setupConnection(config);
}, [config]); // This causes infinite loops
}
// ✓ Correct
function GoodObject() {
const config = useMemo(() => ({ timeout: 5000 }), []);
useEffect(() => {
setupConnection(config);
}, [config]);
}
```
## Hooks Best Practices
**1. Keep Hooks Simple**: Each hook should do one thing well.
**2. Extract Common Patterns**: Move repeated logic into custom hooks.
**3. Use ESLint Plugin**: Install `eslint-plugin-react-hooks` to catch violations.
```json
{
"plugins": ["react-hooks"],
"rules": {
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn"
}
}
```
**4. Name Custom Hooks with "use" Prefix**: This signals to other developers that
it's a hook.
**5. Separate Concerns**: Don't mix state logic, effects, and rendering in complex
ways.
**6. Document Your Hooks**: Explain parameters, return values, and dependencies.
```javascript
/**
* Manages form input state
* @param {string} initialValue - Initial input value
* @returns {Object} Input value, setter, and bind object
*/
function useFormInput(initialValue = '') {
// Implementation
}
```
## Conclusion
React Hooks have fundamentally changed how developers build React applications. By
mastering built-in hooks and creating custom hooks, you can write cleaner, more
maintainable, and more reusable code. Understanding the rules of hooks, managing
dependencies correctly, and following best practices will make you a proficient
React developer capable of building complex applications with functional
components.