React Hooks Deep Dive: useState, useEffect, useContext, useRef, and Custom Hooks
React Hooks
The New Era of Functional Components
React Hooks were first introduced in version 16.8 and have revolutionized the React ecosystem. Previously, essential features like State or Lifecycle methods were exclusive to Class Components. This often led to verbose code, difficulty in reusing logic, and the dreaded "wrapper hell".
With Hooks, you can leverage the full power of React inside Functional Components. This article dives deep into the most critical hooks that every React developer must master.
1. useState: Managing State
This is the most basic hook. It allows a functional component to "remember" information between renders.
const [state, setState] = useState(initialValue);
Unlike this.setState in Class Components (which merges objects), the useState updater function overwrites the old value completely. Let's look at an example of managing an object:
// WRONG: Old information is lost if not copied
setUser({ name: 'Tuan' });
// RIGHT: Use Spread Operator to preserve old fields
const [user, setUser] = useState({ name: 'Nam', age: 25 });
const updateName = () => {
setUser(prevUser => ({
...prevUser, // Copy old data
name: 'Tuan' // Update new data
}));
};
2. useEffect: Handling Side Effects
useEffect is a "super hook" that replaces three lifecycle methods: componentDidMount, componentDidUpdate, and componentWillUnmount.
It is used for tasks like: fetching APIs, subscribing to event listeners, or manually changing the DOM.
Three Common Use Cases
-
1. No Dependency Array:
Runs after EVERY render. Rarely used as it can cause infinite loops.
useEffect(() => { ... }) -
2. Empty Dependency Array ([]):
Runs only ONCE after mount (equivalent to componentDidMount).
useEffect(() => { ... }, []) -
3. With Dependencies ([prop, state]):
Runs on mount AND whenever any value in the array changes.
useEffect(() => { ... }, [userId])
Cleanup Function: To avoid memory leaks (e.g., when using setInterval or addEventListener), you need to return a cleanup function:
useEffect(() => {
const handleResize = () => console.log(window.innerWidth);
window.addEventListener('resize', handleResize);
// This function runs when the component unmounts
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);
3. useContext: Avoiding Prop Drilling
When you need to pass data through many component layers (e.g., Theme, User Auth, Language), manual prop passing (Prop Drilling) becomes cumbersome. useContext allows you to access values from a Context Provider anywhere in the component tree.
const ThemeContext = createContext('light');
function App() {
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
function Toolbar() {
// No need to receive props, get directly from Context
const theme = useContext(ThemeContext);
return <div className={theme}>Toolbar Styled</div>;
}
4. useRef: Mutable References
useRef is commonly known for accessing DOM elements directly, but it has another critical use: storing mutable values without triggering re-renders.
Accessing DOM
const inputRef = useRef(null);
const focusInput = () => {
inputRef.current.focus();
};
return <input ref={inputRef} />;
Storing Variables
const count = useRef(0);
// This change DOES NOT
// cause the component to re-render
count.current++;
5. Custom Hooks: Reusing Logic
This is the most powerful feature. If you find yourself copy-pasting logic (e.g., fetching data, form handling) between components, extract it into a Custom Hook.
The only rule: The function name must start with use.
// useWindowWidth.js
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;
}
// Usage in Component
function MyComponent() {
const width = useWindowWidth();
return <span>Window width is: {width}</span>;
}
The Two Golden Rules of Hooks
- Only Call Hooks at the Top Level: Never call hooks inside loops, conditions, or nested functions. React relies on the call order to manage state.
- Only Call Hooks from React Functions: Only call them from React Functional Components or Custom Hooks.
Conclusion
React Hooks are not just a syntax change; they represent a shift in programming mindset (Mental Model). Moving from "Lifecycle" thinking to "Synchronization" (synchronizing data).
By mastering useState, useEffect, and the ability to create Custom Hooks, you will write React applications that are cleaner, easier to maintain, and more performant.
