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 insideuseEffect
.
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();
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;
}
3. Service Layer
We move all fetch logic into a dedicated file:
// services.js
export async function getActorList(movieId) { /* ... */ }
export async function getActorDetails(actorId) { /* ... */ }
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);
}
🎬 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'));
Then on the cast page:
function MovieHome() {
const actors = useData('inception-cast');
...
}
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>
Then, on the actor details page:
function ActorDetails() {
const actor = useData(actorId);
...
}
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)