export class QueryCache {
constructor() {
this.entries = new Map();
}
getEntry(key) {
if (!this.entries.has(key)) {
this.entries.set(key, {
status: 'idle',
data: undefined,
error: undefined,
promise: null,
listeners: new Set(),
});
}
return this.entries.get(key);
}
subscribe(key, listener) {
const entry = this.getEntry(key);
entry.listeners.add(listener);
return () => entry.listeners.delete(listener);
}
setEntry(key, patch) {
const entry = this.getEntry(key);
Object.assign(entry, patch);
entry.listeners.forEach((fn) => fn());
}
fetchQuery(key, queryFn) {
const entry = this.getEntry(key);
if (entry.promise) return entry.promise;
this.setEntry(key, { status: 'loading', error: undefined });
const promise = Promise.resolve()
.then(() => queryFn())
.then((data) => {
this.setEntry(key, { status: 'success', data, promise: null });
return data;
})
.catch((error) => {
this.setEntry(key, { status: 'error', error, promise: null });
throw error;
});
entry.promise = promise;
return promise;
}
}
import React, { createContext, useContext, useRef } from 'react';
import { QueryCache } from './QueryCache';
const QueryContext = createContext(null);
export function QueryProvider({ children, cache }) {
const cacheRef = useRef(cache || new QueryCache());
return (
<QueryContext.Provider value={cacheRef.current}>
{children}
</QueryContext.Provider>
);
}
export function useQueryClient() {
const cache = useContext(QueryContext);
if (!cache) {
throw new Error('useQueryClient must be used within a <QueryProvider>');
}
return cache;
}
import { useCallback, useEffect, useSyncExternalStore } from 'react';
import { useQueryClient } from './QueryProvider';
export function useQuery(queryKey, queryFn, options = {}) {
const { enabled = true } = options;
const cache = useQueryClient();
const key = JSON.stringify(queryKey);
const subscribe = useCallback(
(listener) => cache.subscribe(key, listener),
[cache, key]
);
const getSnapshot = useCallback(
() => cache.getEntry(key),
[cache, key]
);
const entry = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
const refetch = useCallback(
() => cache.fetchQuery(key, () => queryFn(queryKey)),
[cache, key, queryFn, queryKey]
);
useEffect(() => {
if (!enabled) return;
refetch().catch(() => {});
}, [enabled, refetch]);
return {
data: entry.data,
error: entry.error,
status: entry.status,
isLoading: entry.status === 'loading' && entry.data === undefined,
isFetching: entry.status === 'loading',
refetch,
};
}
import React from 'react';
import { useQuery } from './useQuery';
async function fetchUser([, userId]) {
const res = await fetch(`/api/users/${userId}`);
if (!res.ok) throw new Error(`Failed to load user ${userId}`);
return res.json();
}
export function UserProfile({ userId }) {
const { data, error, isLoading, isFetching, refetch } = useQuery(
['user', userId],
fetchUser
);
if (isLoading) return <p>Loading…</p>;
if (error) return <p role="alert">{error.message}</p>;
return (
<section>
<h2>{data.name}</h2>
<p>{data.email}</p>
<button onClick={refetch} disabled={isFetching}>
{isFetching ? 'Refreshing…' : 'Refresh'}
</button>
</section>
);
}
This snippet implements a small useQuery-style data layer from scratch, showing how libraries like React Query or SWR work under the hood without the dependency. The core idea is a single in-memory cache keyed by a stable string, plus request deduplication so that N components asking for the same key trigger exactly one network call.
In QueryCache, the cache is a plain Map keyed by a serialized query key. Each entry holds the last data, an error, a status, and a set of subscriber callbacks. subscribe registers a listener and returns an unsubscribe function, mirroring the store pattern React's useSyncExternalStore expects. The fetchQuery method is where deduplication happens: an in-flight Promise is stashed on the entry so concurrent callers share it rather than firing duplicate requests. Results are written back with setEntry, which notifies every subscriber. This separates cache mechanics from React entirely, which makes the logic easy to reason about and test.
In QueryProvider, a single QueryCache instance is created once with useRef and handed down through context via QueryContext. Keeping the cache in a ref (not state) means the provider itself never re-renders when cache data changes — only the components subscribed to a given key do. useQueryClient is a thin accessor that throws when used outside the provider, catching a common wiring mistake early.
In useQuery hook, useSyncExternalStore binds a component to one cache entry. The subscribe callback filters by key so a component only re-renders when its own data changes, and getSnapshot reads the current entry. A useEffect kicks off fetchQuery on mount and whenever the key changes, adopting a stale-while-revalidate posture: cached data is returned immediately while a background refresh runs. The key is serialized with JSON.stringify so array or object keys stay stable across renders.
The main trade-offs are that this cache is memory-only with no eviction, TTL, or garbage collection, so long-lived apps would need those additions. It is ideal when a project wants deduped, shared, cache-backed fetching without pulling in a full data-fetching library.
Related snips
export interface RetryOptions {
retries: number;
baseMs: number;
maxMs: number;
signal?: AbortSignal;
onRetry?: (attempt: number, delay: number, err: unknown) => void;
Exponential backoff with jitter for retries
module Api
module V1
class UsersController < BaseController
def show
user = User.includes(:profile).find(params[:id])
ETags for conditional requests and caching
// Creating a Promise
const myPromise = new Promise((resolve, reject) => {
const success = true;
setTimeout(() => {
if (success) {
Promises and async/await patterns for asynchronous JavaScript
export type Settled<R> =
| { status: 'fulfilled'; value: R }
| { status: 'rejected'; reason: unknown };
export interface ConcurrencyOptions {
limit: number;
Simple concurrency limiter for batch operations
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = ["form"]
static values = { delay: { type: Number, default: 250 } }
Debounced live search with Stimulus + Turbo Streams
json.array! @posts do |post|
json.cache! ['v1', post], expires_in: 1.hour do
json.id post.id
json.title post.title
json.excerpt post.excerpt
json.published_at post.published_at
Fragment caching for expensive JSON serialization
Share this code
Here's the card — post it anywhere.