| name | mongez-supportive-is-recipes |
| description | Idiomatic composition patterns for `@mongez/supportive-is` predicates, covering form validation, deep merge, empty filtering, mobile-aware UI, keyboard shortcuts, URL coercion, polymorphic APIs, and TypeScript type narrowing.
TRIGGER when: code combines multiple `@mongez/supportive-is` imports (e.g. `isEmpty` + `isEmail` + `isUrl` for forms, or `isPlainObject` for deep merge); user asks "how do I validate a form with supportive-is", "deep merge that respects class instances", "filter out empty values", "Cmd vs Ctrl shortcut", "polymorphic string-or-regex argument", or "TS type narrowing with isString"; typical import is `import { isEmpty, isEmail, isUrl } from "@mongez/supportive-is"` — combining several predicates in one module.
SKIP: single-predicate questions — use the category-specific skill (`mongez-supportive-is-primitives`/`-collections`/`-formats`/`-misc`/`-environment`); React-specific form-state libraries like `react-hook-form` or `formik`; `@mongez/reinforcements` recipes for object/array transformations.
|
Recipes
Idiomatic compositions across @mongez/supportive-is predicates.
Form validation
import { isEmpty, isEmail, isUrl } from "@mongez/supportive-is";
type ContactForm = {
name?: string;
email?: string;
website?: string;
};
function validate(form: ContactForm) {
const errors: Partial<Record<keyof ContactForm, string>> = {};
if (isEmpty(form.name)) errors.name = "Name is required";
if (isEmpty(form.email)) errors.email = "Email is required";
else if (!isEmail(form.email!)) errors.email = "Email is invalid";
if (!isEmpty(form.website) && !isUrl(form.website!)) {
errors.website = "Website must be a full http(s) URL";
}
return errors;
}
A deep merge that respects class instances
isPlainObject is the right predicate for "should I merge into this, or replace it?" — it returns false for Dates, Maps, Sets, and class instances, all of which want to be replaced, not merged.
import { isPlainObject } from "@mongez/supportive-is";
function deepMerge<T extends object>(target: T, source: Partial<T>): T {
for (const key of Object.keys(source) as (keyof T)[]) {
const next = source[key];
const current = target[key];
if (isPlainObject(next) && isPlainObject(current)) {
target[key] = deepMerge(
current as object,
next as object,
) as T[keyof T];
} else {
target[key] = next as T[keyof T];
}
}
return target;
}
Filtering nullable / empty entries
import { isEmpty } from "@mongez/supportive-is";
function pickNotEmpty<T extends Record<string, unknown>>(input: T): Partial<T> {
const out: Partial<T> = {};
for (const k of Object.keys(input) as (keyof T)[]) {
if (!isEmpty(input[k])) out[k] = input[k];
}
return out;
}
pickNotEmpty({ a: "x", b: "", c: null, d: 0 });
Mobile-aware UI without a state library
import { isMobile } from "@mongez/supportive-is";
const isMobileDevice = isMobile.any();
export function Card() {
return (
<article style={{ padding: isMobileDevice ? 12 : 24 }}>
...
</article>
);
}
Cmd vs Ctrl for keyboard shortcuts
import { isMac } from "@mongez/supportive-is";
const MOD = isMac() ? "Cmd" : "Ctrl";
export function SaveHint() {
return <kbd>{MOD} + S</kbd>;
}
Safe URL coercion
new URL("...") throws on bad input. Wrap with isUrl first:
import { isUrl } from "@mongez/supportive-is";
function safeUrl(input: string): URL | null {
if (!isUrl(input)) return null;
try {
return new URL(input);
} catch {
return null;
}
}
const u = safeUrl("https://example.com/page?q=1");
u?.searchParams.get("q");
Polymorphic API — string or regex
import { isRegex } from "@mongez/supportive-is";
function find(haystack: string, needle: string | RegExp) {
const re = isRegex(needle) ? needle : new RegExp(needle, "g");
return Array.from(haystack.matchAll(re), (m) => m[0]);
}
Type narrowing in TypeScript
None of the predicates ship as declared TypeScript type guards — they're all typed (value: any) => boolean. Wrap them with your own typed guard when you need narrowing on a union:
import { isString, isPlainObject } from "@mongez/supportive-is";
function isStr(v: unknown): v is string {
return isString(v);
}
function isObjLike<T extends object>(v: unknown): v is T {
return isPlainObject(v);
}
function greet(v: string | number) {
if (isStr(v)) {
return `Hello, ${v.toUpperCase()}`;
}
return `Number: ${v.toFixed(2)}`;
}
The wrapper compiles to a single call — there's no runtime cost.