partition()
Split a signal-held array into two derived signals — the first matches the predicate, the second doesn’t. Both sides re-compute when the source changes; source order is preserved on both sides.
Shipped from the sub-path @whisq/core/collections, not the main entry.
Signature
Section titled “Signature”import { partition } from "@whisq/core/collections";import type { ReadonlySignal } from "@whisq/core";
function partition<T>( source: () => T[], predicate: (item: T) => boolean,): [ReadonlySignal<T[]>, ReadonlySignal<T[]>];Parameters
Section titled “Parameters”| Param | Type | Description |
|---|---|---|
source | () => T[] | Reactive accessor for the source array. Typically () => mySignal.value. |
predicate | (item: T) => boolean | Returns true to send the item to the first side (matching), false for the second side (notMatching). |
Returns
Section titled “Returns”A tuple [matching, notMatching] of ReadonlySignal<T[]>. Each side is an independent computed() — subscribing to one does not subscribe to the other, so an effect reading only pending.value doesn’t re-run when only items in done change.
Example — todo “active vs done”
Section titled “Example — todo “active vs done””import { signal, each, ul, li, p, button } from "@whisq/core";import { partition } from "@whisq/core/collections";
interface Todo { id: string; text: string; done: boolean }
const todos = signal<Todo[]>([ { id: "1", text: "Learn Whisq", done: false }, { id: "2", text: "Ship it", done: true },]);
const [pending, done] = partition(() => todos.value, (t) => !t.done);
// Read either side independently — subscribers don't cross-pollinate.p(() => `${pending.value.length} left`);p(() => `${done.value.length} done`);
button({ onclick: () => todos.value = pending.value }, "Clear completed");Why not just two computeds?
Section titled “Why not just two computeds?”You could write:
const pending = computed(() => todos.value.filter((t) => !t.done));const done = computed(() => todos.value.filter((t) => t.done));That works and is fine for short cases. partition is a one-line replacement that:
- Keeps the predicate in one place — the
notMatchingside is automatically the inverse, so there’s no risk of the two predicates drifting out of sync. - Returns a tuple destructured at the call site, matching how partitioning reads in most other languages and libraries.
Reactivity behaviour is identical to two computed()s — both sides use reference-equality on the produced arrays (matching computed() semantics). If you need structural-equality semantics, that’s the caller’s job — wrap the result in your own equality-checked computed().
Semantics
Section titled “Semantics”- Source order preserved. Both sides receive items in the order they appear in
source(). No re-sorting. - Reference equality on output. Re-runs only fire when the partitioned array would change in content (per
computed()’s default equality). - Sub-path import. Lives on
@whisq/core/collectionsto keep the main entry under 5 KB.
See also: computed(), /api/imports/#collections, State Management guide, bindField() → Writing through filtered / partitioned views — pass the source signal to bindField, not the partitioned side, when editing per-row inside a filtered each().
Docs current to v0.1.0-alpha.9 . All releases →