Effects & Data Fetching

The first time I tried to load products from my Inventory Service API into the POS frontend, I put the fetch call directly in my component's body. The page froze, my browser crashed, and I had made about 10,000 API requests in 3 seconds. That's when I learned that some code can't just run during renderβ€”you need effects.

Loading data from an API, subscribing to WebSockets, updating the document titleβ€”these are side effects. They interact with things outside React's rendering system. The useEffect hook is how you handle them safely.

What Are Effects?

An effect is code that runs after React finishes rendering. It's for operations that shouldn't happen during rendering:

In the POS system:

  • Fetching the product catalog from /api/products β†’ Effect

  • Updating the page title with cart count β†’ Effect

  • Connecting to inventory WebSocket for real-time updates β†’ Effect

  • Calculating the total price β†’ NOT an effect (pure calculation)

  • Rendering a product card β†’ NOT an effect (that's just rendering)

The rule: If it talks to the outside world (API, browser API, timers), use an effect.

Your First Effect: Fetching Products

Let's build what every POS system needsβ€”loading products from an API.

The Broken Approach (Don't Do This)

First, let's see what doesn't work:

What happens?

  1. Component renders

  2. Fetch runs, eventually calls setProducts

  3. setProducts causes a re-render

  4. Component renders again

  5. Fetch runs again (goto step 2)

  6. Infinite loop! πŸ’₯

The Correct Approach with useEffect

Now it works! The effect runs once after mount, fetches data, and updates state.

Understanding useEffect

Three parts:

  1. Effect function: The code that runs after render

  2. Dependency array: Controls when the effect re-runs

  3. Cleanup function (optional): Runs before the effect runs again or component unmounts

The Dependency Array

This is the most important (and confusing) part of useEffect:

In the POS system, I use different patterns:

Loading States: Building Production-Quality Data Fetching

A bare fetch isn't enough. Users need to know when data is loading and when errors occur.

Complete Example with Loading and Error States

Three states:

  1. Loading: Show a spinner or skeleton

  2. Error: Show error message with retry option

  3. Success: Show the data

This is the pattern I use in every component that fetches data.

Using async/await (Better Syntax)

The then/catch chain works, but async/await is cleaner:

Why the inner function? useEffect expects a regular function or a function that returns a cleanup function. It can't handle a Promise (which async functions return).

Cleanup Functions: Preventing Memory Leaks

When I added real-time inventory updates via WebSocket, I noticed the POS system kept opening new connections every time I navigated between pages. Each connection stayed open, leaking memory. Cleanup functions fixed this.

Example: Subscribing to Real-Time Updates

When cleanup runs:

  1. When the component unmounts (user navigates away)

  2. Before the effect runs again (if productId changes)

Common cleanup scenarios:

  • WebSocket connections β†’ ws.close()

  • Event listeners β†’ removeEventListener()

  • Timers β†’ clearTimeout() / clearInterval()

  • API requests β†’ abort controller

Canceling Fetch Requests

Why this matters: Users click around fast. Without cleanup, old requests might complete after newer ones, showing stale data.

Dependency Array Deep Dive

This is where most bugs happen. Let's understand it thoroughly.

Missing Dependencies

Fix: Add searchTerm to dependencies:

Too Many Dependencies (Infinite Loops)

Fix option 1: Depend on individual values:

Fix option 2: Use separate state:

Real POS Example: Product Catalog with Filters

Here's how I built the main product listing page with category filtering:

What's happening:

  1. Two separate effects for two separate concerns

  2. Categories load once (depends on tenantId)

  3. Products reload when category filter or tenant changes

  4. Full loading/error/empty/success state handling

  5. Clean separation of responsibilities

Common Mistakes to Avoid

1. Forgetting the Dependency Array

Fix: Always provide a dependency array, even if empty.

2. Modifying State in Render

Fix: Move state updates to effects or event handlers.

3. Not Handling Cleanup

Fix: Return cleanup function:

4. Fetching in a Loop

Fix: Fetch in one request:

5. Stale Closures

Fix: Use functional update:

Key Learnings

  1. Effects run after render, not during

  2. Dependency array controls when effects re-run

  3. Empty array [] means run once on mount

  4. Cleanup functions prevent memory leaks

  5. Loading/error states are essential for good UX

  6. Abort fetch requests when dependencies change

  7. Never call setState directly in render

  8. Use async/await inside effects for cleaner code

Next Steps

Now you can fetch data and handle side effects. But what happens when you need to pass data between components? In the next article, we'll explore component communicationβ€”how to pass data down with props and callbacks up to manage state across multiple components in the POS cart system.

Continue to: Component Communication β†’

Last updated