TypeScript
when lets you declaratively gate side-effects, computations, or constant values behind one or more conditions (booleans or predicates). If all conditions are truthy, your callback runs (or your constant is returned). Otherwise it returns false.
type Condition<Args extends unknown[]> = boolean | ((...args: Args) => boolean);
type Nested<T> = T | ReadonlyArray<Nested<T>>;
export const when =
<Args extends unknown[], Result>(
...conditions: ReadonlyArray<Nested<Condition<Args>>>
) =>
(callback: ((...args: Args) => Result) | Result) =>
(...args: Args): Result | boolean => {
const passes = (c: Nested<Condition<Args>>): boolean =>
Array.isArray(c)
? c.every(passes)
: typeof c === "function"
? c(...args)
: Boolean(c);
if (conditions.every(passes)) {
if (typeof callback === "function")
return (callback as (...a: Args) => Result)(...args);
else if (callback) return callback as Result;
else return true;
} else return false;
};Usage:
when(...conditions)(callbackOrValue)(...args) -> Result | boolean
Conditions:
- booleans, or
- predicates that receive the same args as the final call.
- may be nested arbitrarily in arrays; everything is flattened.
Behavior:
- If every condition is truthy, then:
* if callback is a function: returns its return value (Result)
* if callback is a non-function value: returns that value (Result)
* if callback is falsy (rare): returns true
- If any condition is falsy, returns false.
Types:
- Args is the tuple of arguments passed at the final call.
- Result is the return type (or the constant’s type).
- Return type is `Result | boolean`.
Notes:
- All conditions must be synchronous booleans/predicates.
- Great for gating effects, “maybe” mapping, and permission checks.
when((x) => x > 0)((x) => x * 2)(5); // 10
when((x) => x > 0)((x) => x * 2)(0); // false
const multiplyIfPositive = when((x) => x > 0)((x) => x * 2);
multiplyIfPositive(10); // 20
multiplyIfPositive(-10); // false- React handlers (gate an action by event + state)
function Button({ ready }: { ready: boolean }) {
const handleClick = when<[React.MouseEvent], void>(
(e) => !e.ctrlKey, // don’t allow Ctrl+click
() => ready // only if component is “ready”
)((e) => {
// actual action
doThing(e);
});
return <button onClick={handleClick}>Run</button>;
}More examples
- Permission checks with reusable guards
type User = { id: string; roles: string[]; active: boolean };
const isActive = (u: User) => u.active;
const hasRole = (role: string) => (u: User) => u.roles.includes(role);
// Return a boolean using a constant-true callback:
const canEdit = when<[User], true>(isActive, hasRole("editor"))(true);
if (canEdit(currentUser)) {
// ...allowed path
} else {
// ...deny
}- “Maybe” mapping: apply transform only when a rule holds
// Discount only when amount > 100
const maybeDiscount = when<[number], number>(
(amount) => amount > 100
)((amount) => Math.round(amount * 0.9));
const amounts = [50, 120, 250];
const final = amounts.map((a) => maybeDiscount(a) === false ? a : discounted);
// -> [50, 108, 225]- Multiple, nested condition shapes.
You can freely mix booleans and predicates in nested arrays; everything flattens.
type Req = { user?: User; scope?: string[] };
const isAuthed = (r: Req) => !!r.user;
const hasScope = (s: string) => (r: Req) => r.scope?.includes(s) ?? false;
const allowWrite = when<[Req], true>(
[true, [isAuthed, [hasScope("write")]]]
)(true);
allowWrite({ user: { id: "1", roles: [], active: true }, scope: ["read", "write"] }); // true
allowWrite({ scope: ["write"] }); // falseChain of thought: pass args into conditions, and when they're all true, execute the callback function with args.
Useful when you need to conditionally ignore certain function calls.
More complex example (snippet from my custom MIDI sequencer frontend):
export const isIndex = (index) => (args) => int(index) === int(firstArg(args));
export const isRegularSwitch = (args) => firstArg(args) % 2 === 0
export const isEncoderSwitch = (args) => firstArg(args) % 2 === 1;
const updateIsEditingControlChangeParams = when(
showTrackControlChangeMenu,
isEncoderSwitch,
isIndex(CC_EDIT_MODE_SWITCH),
!isMuteMode
)(() => setIsEditingControlChangeParams((e) => !e));
const updateMutedTracks = when(
isRegularSwitch,
isMuteMode
)((args) => {
const switchIndex = int(firstArg(args)) / 2;
setMutedTracks((e) =>
e.includes(switchIndex)
? e.filter((v) => v !== switchIndex)
: [...new Set(e.concat(switchIndex))]
);
});
const updateIsFollowActive = when(
showTrackSettingsMenu,
!showTrackControlChangeMenu,
trackSettingsMenuPage === trackSettingsMenuPages.TRACK_CONDITIONS
)((args) => {
setIsFollowActive(getEncoderValue(args) === 1);
});
const events = [
["switch_pressed", updateIsEditingControlChangeParams],
["switch_released", updateMutedTracks],
["encoder_6", updateIsFollowActive],
];
events.forEach(([event, callback]) => gpio.on(event, callback));