DEV Community

Kamran Khan
Kamran Khan

Posted on

My Dashboard fetches two things at once — and cancels both on unmount

My Dashboard page needs two things: the stats for the cards, and the five most recent users for the table underneath. Here's exactly how it loads them:

useEffect(() => {
  let cancelled = false;

  async function load() {
    try {
      const [statsRes, usersRes] = await Promise.all([
        client.get('/dashboard/stats'),
        isAdmin ? client.get('/users', { params: { per_page: 5 } }) : Promise.resolve(null),
      ]);
      if (cancelled) return;
      setStats(statsRes.data);
      if (usersRes) setRecentUsers(usersRes.data.data ?? []);
    } catch (err) {
      if (!cancelled) setError(err.response?.data?.message || 'Failed to load dashboard data.');
    } finally {
      if (!cancelled) setLoading(false);
    }
  }

  load();
  return () => {
    cancelled = true;
  };
}, [isAdmin]);
Enter fullscreen mode Exit fullscreen mode

Three decisions in here that I reuse on every page of the kit:

One: parallel fetches with Promise.all. The stats call and the users call don't depend on each other, so they go out together. The recent-users fetch only runs for admins — everyone else resolves null and the table section never renders. No separate loading states to juggle, one loading flag for the whole page.

Two: the cancelled flag. Every setState after an await is guarded. If the user navigates away mid-fetch, the cleanup flips the flag and the late responses are dropped silently. No "state update on an unmounted component" warnings, no stale data landing on the wrong page. It's five extra lines and it makes every page feel solid.

Three: honest loading and error states. Loading is a centred spinner (animate-spin with an indigo top border). Failure is a rose-coloured box with the API's own message, or a fallback string. No skeletons pretending the data is almost here, no silent empty tables.

And one small human touch: Welcome back, {user?.name} at the top. Dashboards are cold by default; a name costs nothing.

This same shape — Promise.all, cancelled flag, spinner, error box — is copy-pasted into my Users page too. A starter kit teaches by repeating one good pattern until it's muscle memory, not by showing three clever ones.


This Dashboard page ships in my Laravel + React SaaS Starter Kit — the React frontend wired to a real Laravel + Sanctum API: https://kamranofficial.gumroad.com/l/mhekoig

Top comments (0)