javascript
52 lines · 10 steps
Building a stale-while-revalidate hook in React
A useSWR hook serves cached data instantly while refetching in the background, deduping concurrent requests.
Explained by
highlit
1import { useState, useEffect, useCallback, useRef } from 'react';
2
3const cache = new Map();
4const inflight = new Map();
5
6function fetchAndStore(key, fetcher) {
7 if (inflight.has(key)) return inflight.get(key);
8 const promise = Promise.resolve(fetcher(key))
9 .then((data) => {
10 cache.set(key, { data, timestamp: Date.now() });
11 inflight.delete(key);
12 return data;
13 })
14 .catch((err) => {
15 inflight.delete(key);
16 throw err;
17 });
18 inflight.set(key, promise);
19 return promise;
20}
21
22export function useSWR(key, fetcher) {
23 const entry = key ? cache.get(key) : undefined;
24 const [data, setData] = useState(entry?.data);
25 const [error, setError] = useState(undefined);
26 const [isValidating, setIsValidating] = useState(false);
27 const fetcherRef = useRef(fetcher);
28 fetcherRef.current = fetcher;
29
30 const revalidate = useCallback(async () => {
31 if (!key) return;
32 setIsValidating(true);
33 try {
34 const fresh = await fetchAndStore(key, fetcherRef.current);
35 setData(fresh);
36 setError(undefined);
37 } catch (err) {
38 setError(err);
39 } finally {
40 setIsValidating(false);
41 }
42 }, [key]);
43
44 useEffect(() => {
45 if (!key) return;
46 const cached = cache.get(key);
47 if (cached) setData(cached.data);
48 revalidate();
49 }, [key, revalidate]);
50
51 return { data, error, isValidating, mutate: revalidate };
52}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Module-level Maps give a cache and dedup registry that survive component remounts and are shared across every hook instance.
- 2Tracking in-flight promises by key collapses concurrent requests for the same data into a single network call.
- 3Serving cached data first and revalidating after keeps the UI instant while still converging on fresh values.
Related explainers
python
from fastapi import FastAPI, WebSocket, WebSocketDisconnect app = FastAPI()
Building a WebSocket chat with FastAPI
websockets
broadcast
connection-management
Intermediate
9 steps
javascript
function evaluate(expression) { const tokens = tokenize(expression); let pos = 0;
Building a recursive descent calculator
parsing
recursion
operator-precedence
Intermediate
8 steps
rust
use axum::{ extract::{Path, State}, response::sse::{Event, KeepAlive, Sse}, };
Streaming import progress with SSE in Axum
server-sent-events
streams
watch-channel
Advanced
7 steps
php
<?php namespace App\Services;
Building a cached daily leaderboard in Laravel
caching
aggregation
eager-loading
Intermediate
9 steps
typescript
import { useEffect, useState } from "react"; interface Section { id: string;
Building a scroll-spy hook in React
custom-hooks
intersectionobserver
dom-observation
Intermediate
8 steps
python
import secrets from django.contrib.auth import authenticate, login from django.core.cache import cache
Two-factor login with OTP in Django
two-factor-auth
one-time-passwords
caching
Intermediate
9 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/building-a-stale-while-revalidate-hook-in-react-explained-javascript-93cd/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.