React Hooks Showdown: useMemo vs useCallback vs useState vs useEffect
1. useState: The Memory
What it does: Allows a functional component to "remember" information between renders.
When to use: Whenever you have data that changes over time and needs to trigger a re-render to update the UI (e.g., form inputs, toggle states, counters).
const [count, setCount] = useState(0); // Update state setCount(count + 1);2. useEffect: The Side Effect
What it does: Performs side effects in functional components. "Side effects" are things like data fetching, subscriptions, or manually changing the DOM.
When to use: When you need to do something after the component renders or when a specific value changes.
useEffect(() => { // This runs after every render document.title = `You clicked ${count} times`; // Optional cleanup mechanism return () => { // Clean up code here }; }, [count]); // Only re-run if 'count' changes3. useMemo: The Calculator
What it does: Memoizes (caches) the result of a calculation. It only re-calculates the value when one of its dependencies changes.
When to use: Optimization. Use it to avoid expensive calculations on every render.
const expensiveValue = useMemo(() => { return computeExpensiveValue(a, b); }, [a, b]); // Only re-compute if 'a' or 'b' changesNote: Don't overuse this. Memoization has its own cost.
4. useCallback: The Function Saver
What it does: Memoizes a function definition. It returns the same function instance between renders unless its dependencies change.
When to use: Optimization. Primarily useful when passing callbacks to optimized child components (like those wrapped in React.memo) to prevent unnecessary re-renders of the child.
const handleClick = useCallback(() => { doSomething(a, b); }, [a, b]); // Function identity remains stable unless 'a' or 'b' changesSummary Table
| Hook | Returns | Purpose | Re-runs when... |
|---|---|---|---|
| useState | [state, setter] | Manage state | Setter is called |
| useEffect | undefined | Side effects | Dependencies change |
| useMemo | Calculated Value | Cache expensive calculation | Dependencies change |
| useCallback | Memoized Function | Stable function identity | Dependencies change |
Key Difference: useMemo vs useCallback
useMemocaches the result of a function call.useCallbackcaches the function itself.
useCallback(fn, deps)is equivalent touseMemo(() => fn, deps).
Real World Example: Language Switching
A common question is: "Why use useEffect for fetching data when useState holds it?"
Let's say we have a Language Switcher (EN/TR).
The Wrong Way (Trying to use useState for fetching)
// This won't work because fetch is async and returns a Promise, not the data immediately. const [books] = useState(fetch(`/stories/books_${language}.piml`));The Right Way (useEffect + useState)
- State holds the result (the parsed books).
- Effect handles the process (fetching when language changes).
const { language } = useContext(DndContext); // "en" or "tr" const [books, setBooks] = useState([]); // Holds the data // Run this side effect whenever 'language' changes useEffect(() => { const fetchData = async () => { // 1. Fetch the file based on the dynamic language variable const response = await fetch(`/stories/books_${language}.piml`); // 2. Parse the result const text = await response.text(); const data = parsePiml(text); // 3. Update state (triggers re-render) setBooks(data.books); }; fetchData(); }, [language]); // <--- The dependency that triggers the re-fetchThis pattern ensures that every time the user clicks "TR", the effect re-runs, fetches the Turkish content, updates the state, and the UI refreshes automatically.