Effect.ts: Absence as First-Class
State Management • Lesson 51 of 51
32. Ref: Practical Patterns
Common real-world uses of Ref with examples
Track a running count or sum:
const processItems = (items: string[]) => Effect.gen(function* () {
const processed = yield* Ref.make(0);
for (const item of items) {
yield* processItem(item);
yield* Ref.update(processed, n => n + 1);
}
const total = yield* Ref.get(processed);
console.log(`Processed ${total} items`);
});
Use for: Progress tracking, statistics, metrics
Track on/off state:
const modal = Effect.gen(function* () {
const isOpen = yield* Ref.make(false);
// Open
yield* Ref.set(isOpen, true);
// Toggle
yield* Ref.update(isOpen, b => !b);
// Check state
if (yield* Ref.get(isOpen)) {
console.log("Modal is open");
}
});
Use for: Loading states, visibility toggles, feature flags
Accumulate values from parallel operations:
const fetchAll = (urls: string[]) => Effect.gen(function* () {
const results = yield* Ref.make<Response[]>([]);
const errors = yield* Ref.make<Error[]>([]);
const fetchOne = (url: string) => Effect.gen(function* () {
try {
const response = yield* fetch(url);
yield* Ref.update(results, arr => [...arr, response]);
} catch (error) {
yield* Ref.update(errors, arr => [...arr, error]);
}
});
yield* Effect.all(
urls.map(fetchOne),
{ concurrency: 5 }
);
return {
results: yield* Ref.get(results),
errors: yield* Ref.get(errors)
};
});
Use for: Parallel aggregation, error collection, result batching
Store computed values:
const cache = Effect.gen(function* () {
const computed = yield* Ref.make<Map<string, number>>(new Map());
const getOrCompute = (key: string) => Effect.gen(function* () {
const cache = yield* Ref.get(computed);
if (cache.has(key)) {
return cache.get(key)!; // Cache hit
}
// Cache miss - compute it
const value = expensiveComputation(key);
yield* Ref.update(computed, m =>
new Map(m).set(key, value)
);
return value;
});
return getOrCompute;
});
Use for: Memoization, caching, lookup tables
Track state transitions:
type Status = "idle" | "loading" | "success" | "error";
const stateMachine = Effect.gen(function* () {
const status = yield* Ref.make<Status>("idle");
const start = () => Ref.set(status, "loading");
const succeed = () => Ref.set(status, "success");
const fail = () => Ref.set(status, "error");
const reset = () => Ref.set(status, "idle");
return { status, start, succeed, fail, reset };
});
Use for: Request states, UI states, process stages
Track requests over time:
const rateLimiter = Effect.gen(function* () {
const requests = yield* Ref.make<Date[]>([]);
const canMakeRequest = Effect.gen(function* () {
const now = new Date();
const oneMinuteAgo = new Date(now.getTime() - 60000);
// Clean old requests
yield* Ref.update(requests, arr =>
arr.filter(date => date > oneMinuteAgo)
);
const recent = yield* Ref.get(requests);
if (recent.length >= 10) {
return false; // Rate limited!
}
// Add this request
yield* Ref.update(requests, arr => [...arr, now]);
return true;
});
return canMakeRequest;
});
Use for: Rate limiting, throttling, quota tracking
Pass mutable config through a program:
const app = Effect.gen(function* () {
const config = yield* Ref.make({
theme: "light",
language: "en",
debug: false
});
// Update config anywhere
yield* Ref.update(config, c => ({ ...c, theme: "dark" }));
// Read config anywhere
const currentConfig = yield* Ref.get(config);
if (currentConfig.debug) {
console.log("Debug mode enabled");
}
});
Use for: App config, user preferences, feature toggles
Bad: Using Ref for everything
const bad = Effect.gen(function* () {
const a = yield* Ref.make(1);
const b = yield* Ref.make(2);
const c = yield* Ref.make(3);
// Too much mutable state!
});
Good: Use immutable values when possible
const good = Effect.gen(function* () {
const a = 1;
const b = 2;
const c = 3;
// Simple values, no mutation needed
});
Rule of thumb: Only use Ref when you actually need mutation.
| Pattern | Use Case | Example |
|---------|----------|---------|
| Counter | Track quantity | Ref.update(n => n + 1) |
| Flag | Boolean state | Ref.set(true) |
| Accumulator | Collect values | Ref.update(arr => [...arr, x]) |
| Cache | Store lookups | Ref.update(map => map.set(k, v)) |
| State Machine | Track status | Ref.set("loading") |
Real-world patterns for everyday Ref usage
Part 51 of 51 in the Effect.ts Absence Modeling series