Case Study: Speeding Up Dashboard Performance in React with SWR Caching

When building enterprise-grade React applications, performance issues often trace back to how data is loaded. Standard patterns introduce jarring loading indicators, disrupt scroll positions, and trigger excessive server queries. Modern web design demands zero-latency navigation. If a user navigates away from a tab and returns, the UI should immediately render stale data while silently fetching fresh results in the background.

To solve this, we designed useResource—the newest feature in our open-source utility suite, react-hook-lab. Below, we'll walk through how this SWR-based architecture, alongside performance improvements made directly to the useIndexedDB hook, makes it easy to build fluid, robust UIs that gracefully transition online and offline.

The Architecture of useResource

Instead of managing local state, cache persistence, and server validation across separate, disjointed contexts, useResource registers hooks globally within an internal controller manager. It manages garbage collection, dedupes parallel requests, and interfaces directly with the browser's IndexedDB storage.

To facilitate this, we added an enabled configuration parameter to useIndexedDB. By doing so, the cache manager can turn storage operations on or off depending on the state of the parent hook, resolving race conditions and reducing memory footprints.

Let's check out how to implement this pattern across common application components.

Implementation Code Examples

1. Seamless SWR Caching

By defining our data dependencies declaratively with a unique key, we can safely share cache states across completely disconnected page components without invoking context providers.

import React from 'react';
import { useResource } from 'react-hook-lab';

const getMarketRates = async (signal) => {
  const res = await fetch('https://api.coincap.io/v2/assets', { signal });
  const json = await res.json();
  return json.data;
};

export function MarketDashboard() {
  const { data, loading, error } = useResource({
    key: 'crypto-rates',
    fetcher: getMarketRates,
    staleTime: 5000,
  });

  if (loading && !data) return <p>Fetching active rates...</p>;
  if (error) return <p>Failed to load data: {error.message}</p>;

  return (
    <ul>
      {data?.slice(0, 5).map((coin) => (
        <li key={coin.id}>{coin.name}: ${parseFloat(coin.priceUsd).toFixed(2)}</li>
      ))}
    </ul>
  );
}

2. Persistent Offline Storage with Optimistic Editing

For critical data like configurations or checklists, utilizing local persistence guarantees your users can launch the application instantly even under poor network conditions.

import React from 'react';
import { useResource } from 'react-hook-lab';

const saveConfigSettings = async (signal) => {
  const res = await fetch('/api/settings', { signal });
  return res.json();
};

export function UserSettings() {
  const { data: config, mutate } = useResource({
    key: 'app-settings',
    fetcher: saveConfigSettings,
    cache: 'indexeddb',
    persist: { store: 'settings-store' },
    initialData: { theme: 'light', notifications: true },
  });

  const toggleTheme = () => {
    mutate((prev) => ({
      ...prev,
      theme: prev?.theme === 'light' ? 'dark' : 'light'
    }));
  };

  return (
    <div style={{ background: config?.theme === 'dark' ? '#333' : '#fff' }}>
      <p>Current Theme: {config?.theme}</p>
      <button onClick={toggleTheme}>Toggle Interface Theme</button>
    </div>
  );
}

Resources

Comments

Popular posts from this blog

Supercharge Your React Apps: Declarative Client Downloads and Desktop Notifications

How We Built a Performance-Safe Deep Clone Hook for React Developers

Multi-Tab State Sync Made Easy: Introducing useSharedState in react-hook-lab!