React Hooks library for remote data fetching with stale-while-revalidate caching strategy
—
The useSWRImmutable hook is designed for static data that doesn't require revalidation, providing the same API as useSWR but with all revalidation options disabled.
Hook for static data that doesn't require revalidation, with all revalidation options disabled.
/**
* Hook for static data that doesn't require revalidation
* @param key - Unique identifier for the request
* @param fetcher - Function that fetches the data, or null to disable fetching
* @param config - Configuration options (revalidation options are disabled)
* @returns SWRResponse object identical to useSWR
*/
function useSWRImmutable<Data = any, Error = any>(
key: Key,
fetcher?: Fetcher<Data, Key> | null,
config?: SWRConfiguration<Data, Error>
): SWRResponse<Data, Error>;Usage Examples:
import useSWRImmutable from "swr/immutable";
// Static configuration data
const { data: config } = useSWRImmutable("/api/config", fetcher);
// User profile (changes infrequently)
const { data: profile } = useSWRImmutable(
userId ? `/api/users/${userId}/profile` : null,
fetcher
);
// Application constants
const { data: constants } = useSWRImmutable("/api/constants", fetcher);
// Translation data
const { data: translations } = useSWRImmutable(
`/api/i18n/${locale}`,
fetcher
);
// One-time data fetch
const { data: report } = useSWRImmutable(
`/api/reports/${reportId}`,
fetcher
);useSWRImmutable automatically disables all revalidation options:
// These options are automatically set to false:
const defaultConfig = {
revalidateOnFocus: false,
revalidateIfStale: false,
revalidateOnReconnect: false,
// All other options work normally
};Comparison with useSWR:
// Regular useSWR - will revalidate on focus, reconnect, etc.
const { data: dynamicData } = useSWR("/api/dynamic", fetcher);
// useSWRImmutable - will never revalidate automatically
const { data: staticData } = useSWRImmutable("/api/static", fetcher);
// Equivalent to useSWR with manual revalidation disabled
const { data: manualStatic } = useSWR("/api/static", fetcher, {
revalidateOnFocus: false,
revalidateIfStale: false,
revalidateOnReconnect: false,
});Perfect for:
// Application configuration
const { data: appConfig } = useSWRImmutable("/api/app-config", fetcher);
// Feature flags (when they don't change during session)
const { data: features } = useSWRImmutable("/api/feature-flags", fetcher);
// Static content (documentation, help text)
const { data: helpContent } = useSWRImmutable(
`/api/help/${section}`,
fetcher
);
// Cached computed results
const { data: expensiveComputation } = useSWRImmutable(
["compute", complexParams],
([, params]) => performExpensiveComputation(params)
);
// Historical data (doesn't change)
const { data: historicalData } = useSWRImmutable(
`/api/history/${date}`,
fetcher
);
// Reference data (currencies, countries, etc.)
const { data: currencies } = useSWRImmutable("/api/currencies", fetcher);
const { data: countries } = useSWRImmutable("/api/countries", fetcher);Even with useSWRImmutable, you can still trigger manual revalidation:
function StaticDataComponent() {
const { data, mutate } = useSWRImmutable("/api/config", fetcher);
// Manual refresh (still works)
const handleRefresh = () => {
mutate(); // This will revalidate even with useSWRImmutable
};
return (
<div>
<div>Config: {JSON.stringify(data)}</div>
<button onClick={handleRefresh}>Force Refresh</button>
</div>
);
}
// Global manual revalidation
import { mutate } from "swr";
const refreshStaticData = () => {
mutate("/api/config"); // Works even if using useSWRImmutable
};Conditional Immutability:
function DataComponent({ isStatic }: { isStatic: boolean }) {
// Choose hook based on data nature
const hook = isStatic ? useSWRImmutable : useSWR;
const { data, error } = hook("/api/data", fetcher);
return <div>{data ? JSON.stringify(data) : "Loading..."}</div>;
}Cache Warming:
function App() {
// Pre-load static data that will be needed throughout the app
useSWRImmutable("/api/config", fetcher);
useSWRImmutable("/api/user-permissions", fetcher);
useSWRImmutable("/api/feature-flags", fetcher);
return <Router />;
}
// Later components can access the cached data instantly
function FeatureComponent() {
const { data: features } = useSWRImmutable("/api/feature-flags", fetcher);
// This will return cached data immediately, no loading state
return features?.newFeature ? <NewFeature /> : <OldFeature />;
}Locale-Specific Data:
function LocalizedApp() {
const [locale, setLocale] = useState("en");
// Translation data is immutable per locale
const { data: translations } = useSWRImmutable(
`/api/i18n/${locale}`,
fetcher
);
// When locale changes, a new request is made but data for each locale
// is cached permanently (until page refresh)
return (
<div>
<select value={locale} onChange={(e) => setLocale(e.target.value)}>
<option value="en">English</option>
<option value="es">Spanish</option>
<option value="fr">French</option>
</select>
<div>{translations ? <TranslatedContent translations={translations} /> : "Loading..."}</div>
</div>
);
}Static Resource Loading:
// Hook for loading static resources
function useStaticResource<T>(path: string): T | undefined {
const { data } = useSWRImmutable(
path,
async (path: string) => {
const response = await fetch(path);
if (!response.ok) {
throw new Error(`Failed to load ${path}`);
}
return response.json();
}
);
return data;
}
// Usage
function DocumentationPage() {
const schema = useStaticResource<JsonSchema>("/schemas/api.json");
const examples = useStaticResource<Examples>("/examples/api-examples.json");
if (!schema || !examples) {
return <div>Loading documentation...</div>;
}
return <ApiDocumentation schema={schema} examples={examples} />;
}Performance Optimization:
// Use useSWRImmutable for expensive computations that don't change
function ExpensiveChart({ dataParams }: { dataParams: DataParams }) {
const { data: processedData } = useSWRImmutable(
["processed-data", dataParams],
([, params]) => {
// This expensive computation will only run once per unique params
return processLargeDataset(params);
}
);
return <Chart data={processedData} />;
}
// Heavy external API calls for static data
function CountryInfo({ countryCode }: { countryCode: string }) {
const { data: countryDetails } = useSWRImmutable(
`country-${countryCode}`,
() => fetchCountryFromExternalAPI(countryCode), // Expensive external call
{
// Even though it's immutable, we might want error retry
shouldRetryOnError: true,
errorRetryInterval: 1000,
}
);
return <div>{countryDetails?.name}</div>;
}When to choose useSWRImmutable over useSWR:
When to stick with useSWR:
Performance Considerations:
// Good: Static data that never changes
const { data: constants } = useSWRImmutable("/api/constants", fetcher);
// Good: Heavy computation with stable inputs
const { data: result } = useSWRImmutable(
["heavy-calc", stableParams],
computeExpensiveResult
);
// Avoid: Data that might change (use useSWR instead)
// const { data: userNotifications } = useSWRImmutable("/api/notifications", fetcher);
// Avoid: External APIs that might return different data
// const { data: weather } = useSWRImmutable("/api/weather", fetcher);Install with Tessl CLI
npx tessl i tessl/npm-swr