Featured image of post Deep Dive into React: Understanding useEffect, useRef, useMemo, and useState with Real-World Examples

Deep Dive into React: Understanding useEffect, useRef, useMemo, and useState with Real-World Examples

Explore the intricacies of React hooks through practical scenarios and detailed explanations. Understand how to effectively use useEffect, useRef, useMemo, and useState for robust and performant React applications.

React is a bit like exploring an ancient castle—fascinating, full of hidden chambers, and occasionally a bit spooky. You start confidently, believing you know your way around. But suddenly, you’re facing strange bugs, disappearing event listeners, or effects going wild with infinite loops.

Today, let’s embark on a React journey together. Using a practical example script, we’ll peel back the layers of confusion around hooks like useEffect, useRef, useMemo, and useState. We’ll explore exactly how these hooks interact with the DOM, understand their lifecycle deeply, and uncover how their implementation affects your app’s performance.

Grab your gear, adventurer—we’re diving deep!


The Example Script: Setting the Stage

Here’s our adventurous React component:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import { useEffect, useRef, useState, useMemo } from 'react';

function AdventureComponent() {
  const [inputValue, setInputValue] = useState('');
  const [count, setCount] = useState(0);
  const inputRef = useRef(null);

  const expensiveCalculation = useMemo(() => {
    console.log("Calculating heavily...");
    return count * 100;
  }, [count]);

  useEffect(() => {
    const input = inputRef.current;
    const handleFocus = () => console.log('Input Focused');

    input.addEventListener('focus', handleFocus);

    return () => input.removeEventListener('focus', handleFocus);
  }, []);

  useEffect(() => {
    const intervalId = setInterval(() => {
      console.log('Interval tick:', inputValue);
    }, 1000);

    return () => clearInterval(intervalId);
  }, [inputValue]);

  return (
    <div>
      <input
        ref={inputRef}
        value={inputValue}
        onChange={(e) => setInputValue(e.target.value)}
      />
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <div>Count: {count}</div>
      <div>Expensive Value: {expensiveCalculation}</div>
    </div>
  );
}

Now, let’s illuminate every decision, hook, and potential pitfall.


useState: The Magical Variable Keeper

What it Does

useState allows your React component to maintain internal state. It triggers re-rendering whenever the state changes.

Example in Script

1
2
const [inputValue, setInputValue] = useState('');
const [count, setCount] = useState(0);

When you call setInputValue or setCount, React schedules a re-render. This keeps your UI responsive and updated.

Pitfalls

  • Frequent state updates: Too many updates can lead to performance degradation due to excessive renders.

useRef: A Reference Through Time

What it Does

useRef provides a stable reference to a DOM node or a value that persists throughout the component’s lifecycle but doesn’t cause re-renders.

Example in Script

1
2
3
const inputRef = useRef(null);

<input ref={inputRef} />

Initially, inputRef.current is null. Once React renders the input element, inputRef.current points to the actual DOM node.

Lifecycle

  • Initially: inputRef.current is null.
  • After first render: references the actual DOM element.
  • On component unmount: React handles cleanup automatically; your ref becomes null again.

Pitfalls

  • Direct DOM manipulation: Safe only when React won’t manage that part. Always attach listeners through refs to avoid stale DOM nodes.

useEffect: Master of Side Effects

What it Does

Handles side effects like event listeners, network requests, or timers. Runs after every render unless dependency array dictates otherwise.

Example in Script

1
2
3
4
5
6
7
8
useEffect(() => {
  const input = inputRef.current;
  const handleFocus = () => console.log('Input Focused');

  input.addEventListener('focus', handleFocus);

  return () => input.removeEventListener('focus', handleFocus);
}, []);

This setup runs only once after the initial render (because of the empty dependency array), attaching the event listener to the input.

Cleanup Pattern

  • React calls the returned cleanup function before re-running the effect or when the component unmounts. Always clean up listeners or timers here to avoid memory leaks.

Pitfalls

  • Infinite loops: Omitting or misusing dependencies can lead to unwanted loops.
  • Stale closures: Forgetting dependencies can leave your effect using outdated values.

useMemo: Remembering Expensive Calculations

What it Does

Caches the result of heavy calculations and recalculates only when dependencies change.

Example in Script

1
2
3
4
const expensiveCalculation = useMemo(() => {
  console.log("Calculating heavily...");
  return count * 100;
}, [count]);

This calculation only runs when count changes. Otherwise, React uses the cached value, saving CPU cycles.

Pitfalls

  • Overuse: Misuse can introduce overhead rather than optimizing.

Native DOM APIs and React: Friends or Foes?

Native DOM functions (document.getElementById, addEventListener) are powerful—but React has its own ideas about the DOM.

When Safe

  • Global events (e.g., window resize)
  • Event listeners on stable, non-managed DOM nodes (via refs)

When Dangerous

  • Directly manipulating DOM elements managed by React (causing mismatches and bugs)

Example (dangerous)

1
2
3
4
useEffect(() => {
  const el = document.getElementById('my-input');
  el.addEventListener('focus', handler);
}, []);

If React replaces the DOM node later, your listener stops working, causing a mysterious bug.


Performance and Optimization Tips

  • Always include proper dependency arrays in useEffect and useMemo.
  • Minimize state updates; batch them whenever possible.
  • Use useRef to avoid unnecessary re-renders.

Conclusion: Your React Adventure Awaits

Understanding React hooks deeply is like having a reliable map on an adventure. It equips you to handle bugs, optimize performance, and write clean, robust code.

Remember, React isn’t magical—it’s just cleverly designed. Once you grasp its nuances, you’ll find it more exciting and less mysterious. Your React adventure is just beginning; keep exploring, stay curious, and the path will grow clearer with every step.