DEV Community

Cover image for ๐Ÿง  Smarter Data Fetching in React Without Extra Libraries: Prefetching & a Shared Store
Gervais Yao Amoah
Gervais Yao Amoah

Posted on

๐Ÿง  Smarter Data Fetching in React Without Extra Libraries: Prefetching & a Shared Store

When building modern React apps, especially ones that fetch external data (like a movie database), it's common to reach for useEffect() and let the component lifecycle do the work. But this often leads to issues like:

  • โŒ No server-side rendering
  • ๐ŸŒŠ Network waterfalls (delayed or chained requests)
  • ๐ŸŒ€ Race conditions
  • ๐Ÿšซ No caching or preloading

Recently, I explored a pattern that drastically improves data fetching in React without adding a new library or framework. It's simple, scalable, and leverages JavaScript's core features like events and memory caching.

Let me walk you through it.


๐Ÿ‘Ž The Old Way: useEffect() All Over

In the typical approach, we might have:

  • A getActorList() function that fetches a list of actors in a movie.
  • A getActorDetails(id) function for actor-specific data.
  • On each relevant page (e.g., /cast, /actor/:id), we call these functions inside useEffect.

But this means:

  • Data is fetched after component render.
  • Navigation between pages causes repetitive requests.
  • No easy way to share or cache data between components or navigation events.

โœ… The New Way: Prefetching + Shared Store + Custom Hook

Here's the approach in a nutshell:

1. A Shared Data Store

We use a simple in-memory map (an object or Map) as a central store:

const store = new Map();
Enter fullscreen mode Exit fullscreen mode

2. Custom Hook: useData(key)

This hook looks for the data in the store. If itโ€™s there, it returns it. If not, it listens for a dataFetched event and resolves when the data becomes available.

function useData(key) {
  const [data, setData] = useState(null);

  if (store.has(key)) return store.get(key);

  window.addEventListener('dataFetched', () => store.get(key));
  return data;
}
Enter fullscreen mode Exit fullscreen mode

3. Service Layer

We move all fetch logic into a dedicated file:

// services.js
export async function getActorList(movieId) { /* ... */ }
export async function getActorDetails(actorId) { /* ... */ }
Enter fullscreen mode Exit fullscreen mode

4. Prefetching Functions

function prefetchData(key, fn) {
  fn().then(data => {
    store.set(key, data);
    window.dispatchEvent(new Event('dataFetched'));
  });
}

function prefetchDataOnEvent(key, fn) {
  if (store.has(key)) return;
  prefetchData(key, fn);
}
Enter fullscreen mode Exit fullscreen mode

๐ŸŽฌ Real-World Example: "Inception" Movie Page

Letโ€™s say we're building a movie site. The user lands on the "Inception" page and sees the cast.

On Parent Component

We prefetch cast data early:

prefetchData('inception-cast', () => getActorList('inception'));
Enter fullscreen mode Exit fullscreen mode

Then on the cast page:

function MovieHome() {
  const actors = useData('inception-cast');
  ...
}
Enter fullscreen mode Exit fullscreen mode

No useEffect, no delay โ€” just immediate access if it's been prefetched.

On Hover (or Link Focus)

When the user hovers an actor's card:

<Link to={`/actor/${actor.id}`}
onMouseEnter={() => prefetchDataOnEvent(actorId, () => getActorDetails(actorId))}
>
  View Details
</Link>
Enter fullscreen mode Exit fullscreen mode

Then, on the actor details page:

function ActorDetails() {
  const actor = useData(actorId);
  ...
}
Enter fullscreen mode Exit fullscreen mode

If the data was prefetched, itโ€™s instant. If not, it still works, just a bit slower.


โšก Advantages

  • โœ… No external library
  • โœ… No useEffect hell
  • โœ… Data reuse across navigation
  • โœ… Easy caching & preloading
  • โœ… Simpler mental model

๐Ÿงช When Should You Use This?

This pattern works best in:

  • SPA apps without SSR (e.g., create-react-app, Vite)
  • Projects where route transitions are predictable
  • Pages with nested or repeated API calls

It's not a replacement for advanced tools like React Query or SWR โ€” but itโ€™s perfect for small to medium projects where you want control and simplicity.


๐Ÿš€ Final Thoughts

This technique opened my eyes to how much smarter React apps can be with a bit of architectural discipline. Itโ€™s lean, effective, and feels like native React โ€” just better structured.

That said, there's room for improvement:

  • ๐Ÿ› ๏ธ Add error handling to catch failed fetches gracefully.
  • ๐Ÿ“ถ Track status like loading or success, so useData could return an object like { isLoading, data, error }.
  • ๐Ÿ” Add time-based cache invalidation or manual refresh mechanisms.

This is just a solid foundation โ€” and from here, you can evolve it toward more robust patterns depending on your appโ€™s needs.

Iโ€™d love to hear your thoughts! Have you tried this kind of setup before? Whatโ€™s your go-to method for client-side data management in React?

Top comments (1)