React Hooks Deep Dive
Comprehensive guide to React hooks - the modern way to manage state and side effects in functional components:
useState Hook
useState is the most fundamental hook for managing local component state. It returns a stateful value and a function to update it.
function Counter() {
const [count, setCount] = useState(0);
const [name, setName] = useState('');
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
<input
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Enter name"
/>
</div>
);
}Functional Updates
When the new state depends on the previous state, use a function to ensure you get the latest value:
// Good - functional update
setCount(prevCount => prevCount + 1);
// Avoid - might use stale state
setCount(count + 1);useEffect Hook
useEffect lets you perform side effects in functional components. It combines componentDidMount, componentDidUpdate, and componentWillUnmount.
Basic useEffect
useEffect(() => {
// Runs after every render
document.title = `Count: ${count}`;
});useEffect with Dependencies
useEffect(() => {
// Runs only when count changes
document.title = `Count: ${count}`;
}, [count]);useEffect with Cleanup
useEffect(() => {
const timer = setInterval(() => {
setCount(prev => prev + 1);
}, 1000);
return () => {
clearInterval(timer);
};
}, []);Mount/Unmount Effect
useEffect(() => {
// Runs only on mount
console.log('Component mounted');
return () => {
// Runs on unmount
console.log('Component unmounted');
};
}, []);useContext Hook
useContext provides a way to pass data through the component tree without having to pass props down manually at every level.
// Create context
const ThemeContext = createContext();
// Provider component
function App() {
return (
<ThemeContext.Provider value="dark">
<Header />
</ThemeContext.Provider>
);
}
// Consumer component
function Header() {
const theme = useContext(ThemeContext);
return <h1 className={theme}>Header</h1>;
}useReducer Hook
useReducer is an alternative to useState for managing complex state logic. It follows the reducer pattern and is useful when state updates depend on previous state.
const initialState = { count: 0 };
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
Count: {state.count}
<button onClick={() => dispatch({type: 'increment'})}>+</button>
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
</div>
);
}useMemo Hook
useMemo memoizes the result of a computation and only recalculates when dependencies change. It helps optimize expensive calculations.
function ExpensiveComponent({ items }) {
const expensiveValue = useMemo(() => {
return items.reduce((sum, item) => sum + item.value, 0);
}, [items]);
return <div>Total: {expensiveValue}</div>;
}useCallback Hook
useCallback memoizes a function and only creates a new function when dependencies change. It helps prevent unnecessary re-renders of child components.
function Parent({ items }) {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
console.log('Button clicked');
}, []);
return (
<div>
<button onClick={() => setCount(count + 1)}>Count: {count}</button>
<Child onClick={handleClick} />
</div>
);
}useRef Hook
useRef returns a mutable ref object that persists for the full lifetime of the component. It can be used to access DOM elements or store mutable values.
function TextInput() {
const inputRef = useRef(null);
const focusInput = () => {
inputRef.current.focus();
};
return (
<div>
<input ref={inputRef} />
<button onClick={focusInput}>Focus Input</button>
</div>
);
}Custom Hooks
Custom hooks are JavaScript functions that start with "use" and can call other hooks. They allow you to extract component logic into reusable functions.
// Custom hook
function useCounter(initialValue = 0) {
const [count, setCount] = useState(initialValue);
const increment = useCallback(() => setCount(c => c + 1), []);
const decrement = useCallback(() => setCount(c => c - 1), []);
const reset = useCallback(() => setCount(initialValue), [initialValue]);
return { count, increment, decrement, reset };
}
// Using the custom hook
function Counter() {
const { count, increment, decrement, reset } = useCounter(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
<button onClick={reset}>Reset</button>
</div>
);
}Hook Rules
Only call hooks at the top level of React functions
Don't call hooks inside loops, conditions, or nested functions
Only call hooks from React function components or custom hooks
Common Hook Patterns
Data Fetching Hook
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetch(url)
.then(res => res.json())
.then(data => {
setData(data);
setLoading(false);
})
.catch(err => {
setError(err);
setLoading(false);
});
}, [url]);
return { data, loading, error };
}Local Storage Hook
function useLocalStorage(key, initialValue) {
const [storedValue, setStoredValue] = useState(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
return initialValue;
}
});
const setValue = (value) => {
try {
setStoredValue(value);
window.localStorage.setItem(key, JSON.stringify(value));
} catch (error) {
console.error(error);
}
};
return [storedValue, setValue];
}Keep your own version of these notes — editable, searchable, and organised by your stack.
Start free