| <!DOCTYPE html><html class="default" lang="en" data-base=".."><head><meta charset="utf-8"/><meta http-equiv="x-ua-compatible" content="IE=edge"/><title>docs/advanced/reactive-programming | react-declarative</title><meta name="description" content="Documentation for react-declarative"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="stylesheet" href="../assets/style.css"/><link rel="stylesheet" href="../assets/highlight.css"/><script defer src="../assets/main.js"></script><script async src="../assets/icons.js" id="tsd-icons-script"></script><script async src="../assets/search.js" id="tsd-search-script"></script><script async src="../assets/navigation.js" id="tsd-nav-script"></script><script async src="../assets/hierarchy.js" id="tsd-hierarchy-script"></script></head><body><script>document.documentElement.dataset.theme = localStorage.getItem("tsd-theme") || "os";document.body.style.display="none";setTimeout(() => app?app.showPage():document.body.style.removeProperty("display"),500)</script><header class="tsd-page-toolbar"><div class="tsd-toolbar-contents container"><div class="table-cell" id="tsd-search"><div class="field"><label for="tsd-search-field" class="tsd-widget tsd-toolbar-icon search no-caption"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-search"></use></svg></label><input type="text" id="tsd-search-field" aria-label="Search"/></div><div class="field"><div id="tsd-toolbar-links"></div></div><ul class="results"><li class="state loading">Preparing search index...</li><li class="state failure">The search index is not available</li></ul><a href="../index.html" class="title">react-declarative</a></div><div class="table-cell" id="tsd-widgets"><a href="#" class="tsd-widget tsd-toolbar-icon menu no-caption" data-toggle="menu" aria-label="Menu"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-menu"></use></svg></a></div></div></header><div class="container container-main"><div class="col-content"><div class="tsd-page-title"><ul class="tsd-breadcrumb"><li><a href="../modules.html">react-declarative</a></li><li><a href="docs_advanced_reactive-programming.html">docs/advanced/reactive-programming</a></li></ul></div><div class="tsd-panel tsd-typography"><a id="reactive-programming-with-subject-and-source" class="tsd-anchor"></a><h1 class="tsd-anchor-link">Reactive programming with Subject and Source<a href="#reactive-programming-with-subject-and-source" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h1><p><code>react-declarative</code> includes a self-contained reactive programming toolkit: <code>Subject</code> and <code>BehaviorSubject</code> for event streams, a <code>Source</code> class for composable MapReduce pipelines, and a set of React hooks that wire these primitives into your component tree cleanly. You do not need RxJS—the library's own implementations are enough for the patterns commonly needed alongside form and grid components.</p> |
| <a id="subject" class="tsd-anchor"></a><h2 class="tsd-anchor-link">Subject<a href="#subject" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h2><p><code>Subject</code> is a push-based event emitter. Call <code>next</code> to emit a value; call <code>subscribe</code> to listen. Subscribers receive only values emitted after they subscribed.</p> |
| <pre><code class="typescript"><span class="hl-0">import</span><span class="hl-1"> { </span><span class="hl-2">Subject</span><span class="hl-1"> } </span><span class="hl-0">from</span><span class="hl-1"> </span><span class="hl-3">"react-declarative"</span><span class="hl-1">;</span><br/><br/><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-10">clickSubject</span><span class="hl-1"> = </span><span class="hl-4">new</span><span class="hl-1"> </span><span class="hl-5">Subject</span><span class="hl-1"><</span><span class="hl-7">MouseEvent</span><span class="hl-1">>();</span><br/><br/><span class="hl-17">// Emit</span><br/><span class="hl-2">document</span><span class="hl-1">.</span><span class="hl-5">addEventListener</span><span class="hl-1">(</span><span class="hl-3">"click"</span><span class="hl-1">, (</span><span class="hl-2">e</span><span class="hl-1">) </span><span class="hl-4">=></span><span class="hl-1"> </span><span class="hl-2">clickSubject</span><span class="hl-1">.</span><span class="hl-5">next</span><span class="hl-1">(</span><span class="hl-2">e</span><span class="hl-1">));</span><br/><br/><span class="hl-17">// Subscribe — returns an unsubscribe function</span><br/><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-10">unsub</span><span class="hl-1"> = </span><span class="hl-2">clickSubject</span><span class="hl-1">.</span><span class="hl-5">subscribe</span><span class="hl-1">((</span><span class="hl-2">e</span><span class="hl-1">) </span><span class="hl-4">=></span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">console</span><span class="hl-1">.</span><span class="hl-5">log</span><span class="hl-1">(</span><span class="hl-3">"clicked at"</span><span class="hl-1">, </span><span class="hl-2">e</span><span class="hl-1">.</span><span class="hl-2">clientX</span><span class="hl-1">, </span><span class="hl-2">e</span><span class="hl-1">.</span><span class="hl-2">clientY</span><span class="hl-1">);</span><br/><span class="hl-1">});</span><br/><br/><span class="hl-17">// Later</span><br/><span class="hl-5">unsub</span><span class="hl-1">();</span> |
| </code><button type="button">Copy</button></pre> |
|
|
| <a id="behaviorsubject" class="tsd-anchor"></a><h2 class="tsd-anchor-link">BehaviorSubject<a href="#behaviorsubject" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h2><p><code>BehaviorSubject</code> is like <code>Subject</code> but it stores the most recent value. New subscribers immediately receive the current value, then any subsequent emissions.</p> |
| <pre><code class="typescript"><span class="hl-0">import</span><span class="hl-1"> { </span><span class="hl-2">BehaviorSubject</span><span class="hl-1"> } </span><span class="hl-0">from</span><span class="hl-1"> </span><span class="hl-3">"react-declarative"</span><span class="hl-1">;</span><br/><br/><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-10">userSubject</span><span class="hl-1"> = </span><span class="hl-4">new</span><span class="hl-1"> </span><span class="hl-5">BehaviorSubject</span><span class="hl-1"><</span><span class="hl-7">string</span><span class="hl-1"> | </span><span class="hl-7">null</span><span class="hl-1">>(</span><span class="hl-4">null</span><span class="hl-1">);</span><br/><br/><span class="hl-2">userSubject</span><span class="hl-1">.</span><span class="hl-5">next</span><span class="hl-1">(</span><span class="hl-3">"alice"</span><span class="hl-1">);</span><br/><br/><span class="hl-17">// New subscriber gets "alice" immediately, then any future values</span><br/><span class="hl-2">userSubject</span><span class="hl-1">.</span><span class="hl-5">subscribe</span><span class="hl-1">((</span><span class="hl-2">user</span><span class="hl-1">) </span><span class="hl-4">=></span><span class="hl-1"> </span><span class="hl-2">console</span><span class="hl-1">.</span><span class="hl-5">log</span><span class="hl-1">(</span><span class="hl-3">"current user:"</span><span class="hl-1">, </span><span class="hl-2">user</span><span class="hl-1">));</span><br/><br/><span class="hl-17">// Access current value directly</span><br/><span class="hl-2">console</span><span class="hl-1">.</span><span class="hl-5">log</span><span class="hl-1">(</span><span class="hl-2">userSubject</span><span class="hl-1">.</span><span class="hl-2">data</span><span class="hl-1">); </span><span class="hl-17">// "alice"</span> |
| </code><button type="button">Copy</button></pre> |
|
|
| <a id="eventemitter" class="tsd-anchor"></a><h2 class="tsd-anchor-link">EventEmitter<a href="#eventemitter" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h2><p><code>EventEmitter</code> provides named-event pub/sub when you need multiple separate event channels on a single object.</p> |
| <pre><code class="typescript"><span class="hl-0">import</span><span class="hl-1"> { </span><span class="hl-2">EventEmitter</span><span class="hl-1"> } </span><span class="hl-0">from</span><span class="hl-1"> </span><span class="hl-3">"react-declarative"</span><span class="hl-1">;</span><br/><br/><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-10">emitter</span><span class="hl-1"> = </span><span class="hl-4">new</span><span class="hl-1"> </span><span class="hl-5">EventEmitter</span><span class="hl-1">();</span><br/><br/><span class="hl-2">emitter</span><span class="hl-1">.</span><span class="hl-5">subscribe</span><span class="hl-1">(</span><span class="hl-3">"login"</span><span class="hl-1">, (</span><span class="hl-2">user</span><span class="hl-1">) </span><span class="hl-4">=></span><span class="hl-1"> </span><span class="hl-2">console</span><span class="hl-1">.</span><span class="hl-5">log</span><span class="hl-1">(</span><span class="hl-3">"logged in:"</span><span class="hl-1">, </span><span class="hl-2">user</span><span class="hl-1">));</span><br/><span class="hl-2">emitter</span><span class="hl-1">.</span><span class="hl-5">subscribe</span><span class="hl-1">(</span><span class="hl-3">"logout"</span><span class="hl-1">, () </span><span class="hl-4">=></span><span class="hl-1"> </span><span class="hl-2">console</span><span class="hl-1">.</span><span class="hl-5">log</span><span class="hl-1">(</span><span class="hl-3">"logged out"</span><span class="hl-1">));</span><br/><br/><span class="hl-2">emitter</span><span class="hl-1">.</span><span class="hl-5">emit</span><span class="hl-1">(</span><span class="hl-3">"login"</span><span class="hl-1">, { </span><span class="hl-2">name:</span><span class="hl-1"> </span><span class="hl-3">"alice"</span><span class="hl-1"> });</span><br/><span class="hl-2">emitter</span><span class="hl-1">.</span><span class="hl-5">emit</span><span class="hl-1">(</span><span class="hl-3">"logout"</span><span class="hl-1">);</span> |
| </code><button type="button">Copy</button></pre> |
|
|
| <a id="react-hooks" class="tsd-anchor"></a><h2 class="tsd-anchor-link">React hooks<a href="#react-hooks" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h2><a id="usesubject" class="tsd-anchor"></a><h3 class="tsd-anchor-link">useSubject<a href="#usesubject" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><p><code>useSubject</code> creates a <code>Subject</code> that is stable across renders. Pass an optional external subject to bridge an outer stream into a component-scoped one.</p> |
| <pre><code class="tsx"><span class="hl-0">import</span><span class="hl-1"> { </span><span class="hl-2">useSubject</span><span class="hl-1"> } </span><span class="hl-0">from</span><span class="hl-1"> </span><span class="hl-3">"react-declarative"</span><span class="hl-1">;</span><br/><br/><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-5">SaveButton</span><span class="hl-1"> = () </span><span class="hl-4">=></span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-10">saveSubject</span><span class="hl-1"> = </span><span class="hl-5">useSubject</span><span class="hl-1"><</span><span class="hl-7">void</span><span class="hl-1">>();</span><br/><br/><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-5">handleClick</span><span class="hl-1"> = () </span><span class="hl-4">=></span><span class="hl-1"> </span><span class="hl-2">saveSubject</span><span class="hl-1">.</span><span class="hl-5">next</span><span class="hl-1">();</span><br/><br/><span class="hl-1"> </span><span class="hl-5">useEffect</span><span class="hl-1">(</span><br/><span class="hl-1"> () </span><span class="hl-4">=></span><span class="hl-1"> </span><span class="hl-2">saveSubject</span><span class="hl-1">.</span><span class="hl-5">subscribe</span><span class="hl-1">(() </span><span class="hl-4">=></span><span class="hl-1"> </span><span class="hl-2">console</span><span class="hl-1">.</span><span class="hl-5">log</span><span class="hl-1">(</span><span class="hl-3">"save triggered"</span><span class="hl-1">)),</span><br/><span class="hl-1"> []</span><br/><span class="hl-1"> );</span><br/><br/><span class="hl-1"> </span><span class="hl-0">return</span><span class="hl-1"> </span><span class="hl-6"><</span><span class="hl-18">button</span><span class="hl-1"> </span><span class="hl-8">onClick</span><span class="hl-1">=</span><span class="hl-4">{</span><span class="hl-2">handleClick</span><span class="hl-4">}</span><span class="hl-6">></span><span class="hl-1">Save</span><span class="hl-6"></</span><span class="hl-18">button</span><span class="hl-6">></span><span class="hl-1">;</span><br/><span class="hl-1">};</span> |
| </code><button type="button">Copy</button></pre> |
|
|
| <a id="usebehaviorsubject" class="tsd-anchor"></a><h3 class="tsd-anchor-link">useBehaviorSubject<a href="#usebehaviorsubject" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><p><code>useBehaviorSubject</code> creates a stable <code>BehaviorSubject</code> with an optional initial value.</p> |
| <pre><code class="tsx"><span class="hl-0">import</span><span class="hl-1"> { </span><span class="hl-2">useBehaviorSubject</span><span class="hl-1"> } </span><span class="hl-0">from</span><span class="hl-1"> </span><span class="hl-3">"react-declarative"</span><span class="hl-1">;</span><br/><br/><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-5">StatusBadge</span><span class="hl-1"> = () </span><span class="hl-4">=></span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-10">statusSubject</span><span class="hl-1"> = </span><span class="hl-5">useBehaviorSubject</span><span class="hl-1"><</span><span class="hl-3">"idle"</span><span class="hl-1"> | </span><span class="hl-3">"loading"</span><span class="hl-1"> | </span><span class="hl-3">"done"</span><span class="hl-1">>(</span><span class="hl-3">"idle"</span><span class="hl-1">);</span><br/><br/><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-5">handleFetch</span><span class="hl-1"> = </span><span class="hl-4">async</span><span class="hl-1"> () </span><span class="hl-4">=></span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">statusSubject</span><span class="hl-1">.</span><span class="hl-5">next</span><span class="hl-1">(</span><span class="hl-3">"loading"</span><span class="hl-1">);</span><br/><span class="hl-1"> </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-5">fetchData</span><span class="hl-1">();</span><br/><span class="hl-1"> </span><span class="hl-2">statusSubject</span><span class="hl-1">.</span><span class="hl-5">next</span><span class="hl-1">(</span><span class="hl-3">"done"</span><span class="hl-1">);</span><br/><span class="hl-1"> };</span><br/><br/><span class="hl-1"> </span><span class="hl-17">// Combine with useSubjectValue to read in JSX</span><br/><span class="hl-1"> </span><span class="hl-0">return</span><span class="hl-1"> </span><span class="hl-6"><</span><span class="hl-18">button</span><span class="hl-1"> </span><span class="hl-8">onClick</span><span class="hl-1">=</span><span class="hl-4">{</span><span class="hl-2">handleFetch</span><span class="hl-4">}</span><span class="hl-6">></span><span class="hl-1">Fetch</span><span class="hl-6"></</span><span class="hl-18">button</span><span class="hl-6">></span><span class="hl-1">;</span><br/><span class="hl-1">};</span> |
| </code><button type="button">Copy</button></pre> |
|
|
| <a id="usechangesubject" class="tsd-anchor"></a><h3 class="tsd-anchor-link">useChangeSubject<a href="#usechangesubject" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><p><code>useChangeSubject</code> turns a plain React value into a subject that emits whenever the value changes. This is useful for reacting to form data changes without adding a manual <code>useEffect</code> dependency array.</p> |
| <pre><code class="tsx"><span class="hl-0">import</span><span class="hl-1"> { </span><span class="hl-2">useChangeSubject</span><span class="hl-1">, </span><span class="hl-2">useSubscription</span><span class="hl-1"> } </span><span class="hl-0">from</span><span class="hl-1"> </span><span class="hl-3">"react-declarative"</span><span class="hl-1">;</span><br/><br/><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-5">AutoSaveForm</span><span class="hl-1"> = ({ </span><span class="hl-2">data</span><span class="hl-1"> }) </span><span class="hl-4">=></span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-10">dataChangeSubject</span><span class="hl-1"> = </span><span class="hl-5">useChangeSubject</span><span class="hl-1">(</span><span class="hl-2">data</span><span class="hl-1">);</span><br/><br/><span class="hl-1"> </span><span class="hl-5">useSubscription</span><span class="hl-1">(() </span><span class="hl-4">=></span><br/><span class="hl-1"> </span><span class="hl-2">dataChangeSubject</span><span class="hl-1">.</span><span class="hl-5">subscribe</span><span class="hl-1">((</span><span class="hl-2">newData</span><span class="hl-1">) </span><span class="hl-4">=></span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-17">// Called every time `data` changes</span><br/><span class="hl-1"> </span><span class="hl-5">saveToServer</span><span class="hl-1">(</span><span class="hl-2">newData</span><span class="hl-1">);</span><br/><span class="hl-1"> })</span><br/><span class="hl-1"> );</span><br/><br/><span class="hl-1"> </span><span class="hl-0">return</span><span class="hl-1"> </span><span class="hl-6"><</span><span class="hl-7">One</span><span class="hl-1"> </span><span class="hl-8">fields</span><span class="hl-1">=</span><span class="hl-4">{</span><span class="hl-2">fields</span><span class="hl-4">}</span><span class="hl-1"> </span><span class="hl-8">handler</span><span class="hl-1">=</span><span class="hl-4">{</span><span class="hl-2">data</span><span class="hl-4">}</span><span class="hl-1"> </span><span class="hl-6">/></span><span class="hl-1">;</span><br/><span class="hl-1">};</span> |
| </code><button type="button">Copy</button></pre> |
|
|
| <a id="usesubscription" class="tsd-anchor"></a><h3 class="tsd-anchor-link">useSubscription<a href="#usesubscription" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><p><code>useSubscription</code> is a thin wrapper around <code>useEffect</code> for subscribing to subjects. It runs the setup function once on mount and calls the returned unsubscribe function on unmount—no dependency array needed.</p> |
| <pre><code class="tsx"><span class="hl-0">import</span><span class="hl-1"> { </span><span class="hl-2">useSubscription</span><span class="hl-1"> } </span><span class="hl-0">from</span><span class="hl-1"> </span><span class="hl-3">"react-declarative"</span><span class="hl-1">;</span><br/><span class="hl-0">import</span><span class="hl-1"> { </span><span class="hl-2">ioc</span><span class="hl-1"> } </span><span class="hl-0">from</span><span class="hl-1"> </span><span class="hl-3">"../ioc/ioc"</span><span class="hl-1">;</span><br/><br/><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-5">NotificationBell</span><span class="hl-1"> = () </span><span class="hl-4">=></span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> [</span><span class="hl-10">count</span><span class="hl-1">, </span><span class="hl-10">setCount</span><span class="hl-1">] = </span><span class="hl-5">useState</span><span class="hl-1">(</span><span class="hl-16">0</span><span class="hl-1">);</span><br/><br/><span class="hl-1"> </span><span class="hl-5">useSubscription</span><span class="hl-1">(() </span><span class="hl-4">=></span><br/><span class="hl-1"> </span><span class="hl-2">ioc</span><span class="hl-1">.</span><span class="hl-2">notificationService</span><span class="hl-1">.</span><span class="hl-2">countSubject</span><span class="hl-1">.</span><span class="hl-5">subscribe</span><span class="hl-1">(</span><span class="hl-2">setCount</span><span class="hl-1">)</span><br/><span class="hl-1"> );</span><br/><br/><span class="hl-1"> </span><span class="hl-0">return</span><span class="hl-1"> </span><span class="hl-6"><</span><span class="hl-18">span</span><span class="hl-6">></span><span class="hl-4">{</span><span class="hl-2">count</span><span class="hl-4">}</span><span class="hl-1"> notifications</span><span class="hl-6"></</span><span class="hl-18">span</span><span class="hl-6">></span><span class="hl-1">;</span><br/><span class="hl-1">};</span> |
| </code><button type="button">Copy</button></pre> |
|
|
| <a id="source-pipelines-mapreduce" class="tsd-anchor"></a><h2 class="tsd-anchor-link">Source pipelines (MapReduce)<a href="#source-pipelines-mapreduce" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h2><p><code>Source</code> provides a fluent API for composing data pipelines across multiple streams. It is the right tool when you need to join, filter, and transform multiple subjects before consuming the result.</p> |
| <a id="key-operators" class="tsd-anchor"></a><h3 class="tsd-anchor-link">Key operators<a href="#key-operators" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><table> |
| <thead> |
| <tr> |
| <th>Method</th> |
| <th>Purpose</th> |
| </tr> |
| </thead> |
| <tbody> |
| <tr> |
| <td><code>Source.join</code></td> |
| <td>Combines multiple observers; emits when any emits</td> |
| </tr> |
| <tr> |
| <td><code>Source.multicast</code></td> |
| <td>Shares a single upstream subscription among multiple consumers</td> |
| </tr> |
| <tr> |
| <td><code>.reduce(fn, init)</code></td> |
| <td>Accumulates values across emissions</td> |
| </tr> |
| <tr> |
| <td><code>.filter(fn)</code></td> |
| <td>Drops emissions that do not pass the predicate</td> |
| </tr> |
| <tr> |
| <td><code>.tap(fn)</code></td> |
| <td>Runs a side effect without changing the emitted value</td> |
| </tr> |
| <tr> |
| <td><code>.map(fn)</code></td> |
| <td>Transforms each emitted value</td> |
| </tr> |
| <tr> |
| <td><code>Source.fromInterval(ms)</code></td> |
| <td>Emits <code>void</code> on a fixed interval</td> |
| </tr> |
| </tbody> |
| </table> |
| <a id="verifycompleteemitter-example" class="tsd-anchor"></a><h3 class="tsd-anchor-link">verifyCompleteEmitter example<a href="#verifycompleteemitter-example" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><p>This example is taken directly from a real face-verification feature. It joins a state stream with a timer, counts consecutive valid frames, starts/stops a media recorder, and fires only when enough consecutive frames pass.</p> |
| <pre><code class="typescript"><span class="hl-0">import</span><span class="hl-1"> { </span><span class="hl-2">Source</span><span class="hl-1"> } </span><span class="hl-0">from</span><span class="hl-1"> </span><span class="hl-3">"react-declarative"</span><span class="hl-1">;</span><br/><br/><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-10">CC_SECONDS_TO_VERIFY</span><span class="hl-1"> = </span><span class="hl-16">3</span><span class="hl-1">;</span><br/><br/><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-10">verifyCompleteEmitter</span><span class="hl-1"> = </span><span class="hl-2">Source</span><span class="hl-1">.</span><span class="hl-5">multicast</span><span class="hl-1">(() </span><span class="hl-4">=></span><br/><span class="hl-1"> </span><span class="hl-2">Source</span><br/><span class="hl-1"> .</span><span class="hl-5">join</span><span class="hl-1">([</span><br/><span class="hl-1"> </span><span class="hl-2">captureStateEmitter</span><span class="hl-1">, </span><span class="hl-17">// emits { state: boolean } on each frame</span><br/><span class="hl-1"> </span><span class="hl-2">Source</span><span class="hl-1">.</span><span class="hl-5">fromInterval</span><span class="hl-1">(</span><span class="hl-16">1_000</span><span class="hl-1">), </span><span class="hl-17">// ticks every second</span><br/><span class="hl-1"> ])</span><br/><span class="hl-1"> .</span><span class="hl-5">reduce</span><span class="hl-1">((</span><span class="hl-2">acm</span><span class="hl-1">, [{ </span><span class="hl-2">state</span><span class="hl-1">: </span><span class="hl-2">isValid</span><span class="hl-1"> }]) </span><span class="hl-4">=></span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-17">// Reset counter to 0 on any invalid frame; otherwise increment</span><br/><span class="hl-1"> </span><span class="hl-0">if</span><span class="hl-1"> (</span><span class="hl-2">isValid</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-0">return</span><span class="hl-1"> </span><span class="hl-2">acm</span><span class="hl-1"> + </span><span class="hl-16">1</span><span class="hl-1">;</span><br/><span class="hl-1"> }</span><br/><span class="hl-1"> </span><span class="hl-0">return</span><span class="hl-1"> </span><span class="hl-16">0</span><span class="hl-1">;</span><br/><span class="hl-1"> }, </span><span class="hl-16">0</span><span class="hl-1">)</span><br/><span class="hl-1"> .</span><span class="hl-5">tap</span><span class="hl-1">((</span><span class="hl-2">ticker</span><span class="hl-1">) </span><span class="hl-4">=></span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-0">if</span><span class="hl-1"> (</span><span class="hl-2">ticker</span><span class="hl-1"> === </span><span class="hl-16">1</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-17">// Side effect: start recording on first valid tick</span><br/><span class="hl-1"> </span><span class="hl-2">mediaRecorderInstance</span><span class="hl-1">.</span><span class="hl-5">beginCapture</span><span class="hl-1">();</span><br/><span class="hl-1"> }</span><br/><span class="hl-1"> })</span><br/><span class="hl-1"> .</span><span class="hl-5">filter</span><span class="hl-1">((</span><span class="hl-2">ticker</span><span class="hl-1">) </span><span class="hl-4">=></span><span class="hl-1"> </span><span class="hl-2">ticker</span><span class="hl-1"> === </span><span class="hl-10">CC_SECONDS_TO_VERIFY</span><span class="hl-1">)</span><br/><span class="hl-1"> .</span><span class="hl-5">tap</span><span class="hl-1">(() </span><span class="hl-4">=></span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-17">// Side effect: stop recording when threshold is reached</span><br/><span class="hl-1"> </span><span class="hl-2">mediaRecorderInstance</span><span class="hl-1">.</span><span class="hl-5">endCapture</span><span class="hl-1">();</span><br/><span class="hl-1"> })</span><br/><span class="hl-1">);</span> |
| </code><button type="button">Copy</button></pre> |
|
|
| <p><code>Source.multicast</code> ensures only one upstream subscription is created regardless of how many consumers subscribe to <code>verifyCompleteEmitter</code>.</p> |
| <a id="when-to-use-reactive-primitives-vs-simpler-hooks" class="tsd-anchor"></a><h2 class="tsd-anchor-link">When to use reactive primitives vs. simpler hooks<a href="#when-to-use-reactive-primitives-vs-simpler-hooks" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h2><p><strong>Use Subject when…</strong></p> |
| <p>You need to fire a one-off event across components that are not in a parent/child relationship—for example, triggering a form save from a toolbar button, or forwarding a keyboard shortcut to a nested grid.</p> |
| <p><strong>Use BehaviorSubject when…</strong></p> |
| <p>You have shared mutable state that components need to read synchronously on mount—for example, the current user session, a feature flag, or a connection status. The <code>.data</code> getter lets you read it outside of a subscription.</p> |
| <p><strong>Use useChangeSubject when…</strong></p> |
| <p>You want to react to changes in a prop or local state value without writing a <code>useEffect</code> with a dependency array. It is especially useful for auto-save, debounced search, and analytics tracking tied to form data.</p> |
| <p><strong>Use Source pipelines when…</strong></p> |
| <p>You need to combine multiple streams with stateful logic—counters, timers joined with data streams, multi-step sequences. If you find yourself chaining several <code>useEffect</code> hooks with shared state variables, a <code>Source</code> pipeline is usually cleaner.</p> |
| <p><strong>Prefer simpler hooks when…</strong></p> |
| <p>The data flow is local and linear. <code>useState</code> + <code>useEffect</code> is still the right choice for straightforward async loading, conditional rendering, and single-component side effects. Reactive primitives add overhead that is only worthwhile when multiple components or streams are involved.</p> |
| <a id="complete-component-example" class="tsd-anchor"></a><h2 class="tsd-anchor-link">Complete component example<a href="#complete-component-example" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h2><pre><code class="tsx"><span class="hl-0">import</span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">Subject</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">BehaviorSubject</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">useSubject</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">useSubscription</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">useChangeSubject</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">One</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">FieldType</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">TypedField</span><span class="hl-1">,</span><br/><span class="hl-1">} </span><span class="hl-0">from</span><span class="hl-1"> </span><span class="hl-3">"react-declarative"</span><span class="hl-1">;</span><br/><br/><span class="hl-4">interface</span><span class="hl-1"> </span><span class="hl-7">IFormData</span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">query</span><span class="hl-1">: </span><span class="hl-7">string</span><span class="hl-1">;</span><br/><span class="hl-1">}</span><br/><br/><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-10">fields</span><span class="hl-1">: </span><span class="hl-7">TypedField</span><span class="hl-1">[] = [</span><br/><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">type:</span><span class="hl-1"> </span><span class="hl-2">FieldType</span><span class="hl-1">.</span><span class="hl-2">Text</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">name:</span><span class="hl-1"> </span><span class="hl-3">"query"</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">title:</span><span class="hl-1"> </span><span class="hl-3">"Search"</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">placeholder:</span><span class="hl-1"> </span><span class="hl-3">"Type to search…"</span><span class="hl-1">,</span><br/><span class="hl-1"> },</span><br/><span class="hl-1">];</span><br/><br/><span class="hl-17">// Module-level subject shared across components</span><br/><span class="hl-0">export</span><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-10">searchSubject</span><span class="hl-1"> = </span><span class="hl-4">new</span><span class="hl-1"> </span><span class="hl-5">BehaviorSubject</span><span class="hl-1"><</span><span class="hl-7">string</span><span class="hl-1">>(</span><span class="hl-3">""</span><span class="hl-1">);</span><br/><br/><span class="hl-0">export</span><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-5">SearchForm</span><span class="hl-1"> = () </span><span class="hl-4">=></span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-5">handleChange</span><span class="hl-1"> = (</span><span class="hl-2">data</span><span class="hl-1">: </span><span class="hl-7">Partial</span><span class="hl-1"><</span><span class="hl-7">IFormData</span><span class="hl-1">>) </span><span class="hl-4">=></span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-0">if</span><span class="hl-1"> (</span><span class="hl-2">data</span><span class="hl-1">.</span><span class="hl-2">query</span><span class="hl-1"> !== </span><span class="hl-4">undefined</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-2">searchSubject</span><span class="hl-1">.</span><span class="hl-5">next</span><span class="hl-1">(</span><span class="hl-2">data</span><span class="hl-1">.</span><span class="hl-2">query</span><span class="hl-1">);</span><br/><span class="hl-1"> }</span><br/><span class="hl-1"> };</span><br/><br/><span class="hl-1"> </span><span class="hl-0">return</span><span class="hl-1"> </span><span class="hl-6"><</span><span class="hl-7">One</span><span class="hl-1"> </span><span class="hl-8">fields</span><span class="hl-1">=</span><span class="hl-4">{</span><span class="hl-2">fields</span><span class="hl-4">}</span><span class="hl-1"> </span><span class="hl-8">onChange</span><span class="hl-1">=</span><span class="hl-4">{</span><span class="hl-2">handleChange</span><span class="hl-4">}</span><span class="hl-1"> </span><span class="hl-6">/></span><span class="hl-1">;</span><br/><span class="hl-1">};</span><br/><br/><span class="hl-0">export</span><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-5">SearchResults</span><span class="hl-1"> = () </span><span class="hl-4">=></span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> [</span><span class="hl-10">results</span><span class="hl-1">, </span><span class="hl-10">setResults</span><span class="hl-1">] = </span><span class="hl-5">useState</span><span class="hl-1"><</span><span class="hl-7">string</span><span class="hl-1">[]>([]);</span><br/><br/><span class="hl-1"> </span><span class="hl-5">useSubscription</span><span class="hl-1">(() </span><span class="hl-4">=></span><br/><span class="hl-1"> </span><span class="hl-2">searchSubject</span><span class="hl-1">.</span><span class="hl-5">subscribe</span><span class="hl-1">(</span><span class="hl-4">async</span><span class="hl-1"> (</span><span class="hl-2">query</span><span class="hl-1">) </span><span class="hl-4">=></span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-0">if</span><span class="hl-1"> (</span><span class="hl-2">query</span><span class="hl-1">.</span><span class="hl-2">length</span><span class="hl-1"> > </span><span class="hl-16">2</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-10">data</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-5">fetchResults</span><span class="hl-1">(</span><span class="hl-2">query</span><span class="hl-1">);</span><br/><span class="hl-1"> </span><span class="hl-5">setResults</span><span class="hl-1">(</span><span class="hl-2">data</span><span class="hl-1">);</span><br/><span class="hl-1"> }</span><br/><span class="hl-1"> })</span><br/><span class="hl-1"> );</span><br/><br/><span class="hl-1"> </span><span class="hl-0">return</span><span class="hl-1"> (</span><br/><span class="hl-1"> </span><span class="hl-6"><</span><span class="hl-18">ul</span><span class="hl-6">></span><br/><span class="hl-1"> </span><span class="hl-4">{</span><span class="hl-2">results</span><span class="hl-9">.</span><span class="hl-5">map</span><span class="hl-9">((</span><span class="hl-2">r</span><span class="hl-9">) </span><span class="hl-4">=></span><span class="hl-9"> </span><span class="hl-6"><</span><span class="hl-18">li</span><span class="hl-9"> </span><span class="hl-8">key</span><span class="hl-1">=</span><span class="hl-4">{</span><span class="hl-2">r</span><span class="hl-4">}</span><span class="hl-6">></span><span class="hl-4">{</span><span class="hl-2">r</span><span class="hl-4">}</span><span class="hl-6"></</span><span class="hl-18">li</span><span class="hl-6">></span><span class="hl-9">)</span><span class="hl-4">}</span><br/><span class="hl-1"> </span><span class="hl-6"></</span><span class="hl-18">ul</span><span class="hl-6">></span><br/><span class="hl-1"> );</span><br/><span class="hl-1">};</span> |
| </code><button type="button">Copy</button></pre> |
|
|
| </div></div><div class="col-sidebar"><div class="page-menu"><div class="tsd-navigation settings"><details class="tsd-accordion"><summary class="tsd-accordion-summary"><h3><svg width="20" height="20" viewBox="0 0 24 24" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-chevronDown"></use></svg>Settings</h3></summary><div class="tsd-accordion-details"><div class="tsd-filter-visibility"><span class="settings-label">Member Visibility</span><ul id="tsd-filter-options"><li class="tsd-filter-item"><label class="tsd-filter-input"><input type="checkbox" id="tsd-filter-protected" name="protected"/><svg width="32" height="32" viewBox="0 0 32 32" aria-hidden="true"><rect class="tsd-checkbox-background" width="30" height="30" x="1" y="1" rx="6" fill="none"></rect><path class="tsd-checkbox-checkmark" d="M8.35422 16.8214L13.2143 21.75L24.6458 10.25" stroke="none" stroke-width="3.5" stroke-linejoin="round" fill="none"></path></svg><span>Protected</span></label></li><li class="tsd-filter-item"><label class="tsd-filter-input"><input type="checkbox" id="tsd-filter-inherited" name="inherited" checked/><svg width="32" height="32" viewBox="0 0 32 32" aria-hidden="true"><rect class="tsd-checkbox-background" width="30" height="30" x="1" y="1" rx="6" fill="none"></rect><path class="tsd-checkbox-checkmark" d="M8.35422 16.8214L13.2143 21.75L24.6458 10.25" stroke="none" stroke-width="3.5" stroke-linejoin="round" fill="none"></path></svg><span>Inherited</span></label></li><li class="tsd-filter-item"><label class="tsd-filter-input"><input type="checkbox" id="tsd-filter-external" name="external"/><svg width="32" height="32" viewBox="0 0 32 32" aria-hidden="true"><rect class="tsd-checkbox-background" width="30" height="30" x="1" y="1" rx="6" fill="none"></rect><path class="tsd-checkbox-checkmark" d="M8.35422 16.8214L13.2143 21.75L24.6458 10.25" stroke="none" stroke-width="3.5" stroke-linejoin="round" fill="none"></path></svg><span>External</span></label></li></ul></div><div class="tsd-theme-toggle"><label class="settings-label" for="tsd-theme">Theme</label><select id="tsd-theme"><option value="os">OS</option><option value="light">Light</option><option value="dark">Dark</option></select></div></div></details></div><details open class="tsd-accordion tsd-page-navigation"><summary class="tsd-accordion-summary"><h3><svg width="20" height="20" viewBox="0 0 24 24" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-chevronDown"></use></svg>On This Page</h3></summary><div class="tsd-accordion-details"><a href="#reactive-programming-with-subject-and-source"><span>Reactive programming with <wbr/>Subject and <wbr/>Source</span></a><ul><li><a href="#subject"><span>Subject</span></a></li><li><a href="#behaviorsubject"><span>Behavior<wbr/>Subject</span></a></li><li><a href="#eventemitter"><span>Event<wbr/>Emitter</span></a></li><li><a href="#react-hooks"><span>React hooks</span></a></li><li><ul><li><a href="#usesubject"><span>use<wbr/>Subject</span></a></li><li><a href="#usebehaviorsubject"><span>use<wbr/>Behavior<wbr/>Subject</span></a></li><li><a href="#usechangesubject"><span>use<wbr/>Change<wbr/>Subject</span></a></li><li><a href="#usesubscription"><span>use<wbr/>Subscription</span></a></li></ul></li><li><a href="#source-pipelines-mapreduce"><span>Source pipelines (<wbr/>Map<wbr/>Reduce)</span></a></li><li><ul><li><a href="#key-operators"><span>Key operators</span></a></li><li><a href="#verifycompleteemitter-example"><span>verify<wbr/>Complete<wbr/>Emitter example</span></a></li></ul></li><li><a href="#when-to-use-reactive-primitives-vs-simpler-hooks"><span>When to use reactive primitives vs. simpler hooks</span></a></li><li><a href="#complete-component-example"><span>Complete component example</span></a></li></ul></div></details></div><div class="site-menu"><nav class="tsd-navigation"><a href="../modules.html">react-declarative</a><ul class="tsd-small-nested-navigation" id="tsd-nav-container"><li>Loading...</li></ul></nav></div></div></div><footer><p class="tsd-generator">Generated using <a href="https://typedoc.org/" target="_blank">TypeDoc</a></p></footer><div class="overlay"></div></body></html> |
|
|