react-declarative-docs / documents /docs_guides_async-data.html
tripolskypetr's picture
patch
9375fc1
<!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/guides/async-data | 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_guides_async-data.html">docs/guides/async-data</a></li></ul></div><div class="tsd-panel tsd-typography"><a id="async-data-patterns-in-react-declarative" class="tsd-anchor"></a><h1 class="tsd-anchor-link">Async Data Patterns in react-declarative<a href="#async-data-patterns-in-react-declarative" 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>React-declarative ships with a cohesive set of primitives for every async data pattern you will encounter: loading initial form data, preventing duplicate submissions, queuing actions in order, tracking batch progress, and fetching data inside hooks. Each primitive returns a <code>loading</code> and <code>error</code> state alongside the <code>execute</code> function, so you can drive loading indicators without managing that state yourself.</p>
<a id="loading-form-data-with-handler" class="tsd-anchor"></a><h2 class="tsd-anchor-link">Loading form data with <code>handler</code><a href="#loading-form-data-with-handler" 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>The <code>handler</code> prop on <code>&lt;One /&gt;</code> accepts either a plain object or an async function that resolves to the initial form data. React-declarative calls it once when the component mounts.</p>
<pre><code class="tsx"><span class="hl-0">import</span><span class="hl-1"> { </span><span class="hl-2">One</span><span class="hl-1"> } </span><span class="hl-0">from</span><span class="hl-1"> </span><span class="hl-3">&#39;react-declarative&#39;</span><span class="hl-1">;</span><br/><br/><span class="hl-6">&lt;</span><span class="hl-7">One</span><br/><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><br/><span class="hl-1"> </span><span class="hl-8">handler</span><span class="hl-1">=</span><span class="hl-4">{async</span><span class="hl-9"> () </span><span class="hl-4">=&gt;</span><span class="hl-9"> {</span><br/><span class="hl-9"> </span><span class="hl-4">const</span><span class="hl-9"> </span><span class="hl-10">data</span><span class="hl-9"> </span><span class="hl-1">=</span><span class="hl-9"> </span><span class="hl-0">await</span><span class="hl-9"> </span><span class="hl-5">fetchUserProfile</span><span class="hl-9">(</span><span class="hl-2">userId</span><span class="hl-9">);</span><br/><span class="hl-9"> </span><span class="hl-0">return</span><span class="hl-9"> </span><span class="hl-2">data</span><span class="hl-9">;</span><br/><span class="hl-9"> }</span><span class="hl-4">}</span><br/><span class="hl-6">/&gt;</span>
</code><button type="button">Copy</button></pre>
<p>For a list grid, <code>ListTyped</code> accepts a <code>handler</code> (also called <code>ListHandler</code>) that must return an object with <code>rows</code> and <code>total</code>:</p>
<pre><code class="tsx"><span class="hl-0">import</span><span class="hl-1"> { </span><span class="hl-2">ListTyped</span><span class="hl-1"> } </span><span class="hl-0">from</span><span class="hl-1"> </span><span class="hl-3">&#39;react-declarative&#39;</span><span class="hl-1">;</span><br/><br/><span class="hl-6">&lt;</span><span class="hl-7">ListTyped</span><br/><span class="hl-1"> </span><span class="hl-8">columns</span><span class="hl-1">=</span><span class="hl-4">{</span><span class="hl-2">columns</span><span class="hl-4">}</span><br/><span class="hl-1"> </span><span class="hl-8">filters</span><span class="hl-1">=</span><span class="hl-4">{</span><span class="hl-2">filters</span><span class="hl-4">}</span><br/><span class="hl-1"> </span><span class="hl-8">handler</span><span class="hl-1">=</span><span class="hl-4">{async</span><span class="hl-9"> ({ </span><span class="hl-2">search</span><span class="hl-9">, </span><span class="hl-2">sort</span><span class="hl-9">, </span><span class="hl-2">pagination</span><span class="hl-9">, </span><span class="hl-2">filterData</span><span class="hl-9"> }) </span><span class="hl-4">=&gt;</span><span class="hl-9"> {</span><br/><span class="hl-9"> </span><span class="hl-4">const</span><span class="hl-9"> { </span><span class="hl-10">items</span><span class="hl-9">, </span><span class="hl-10">count</span><span class="hl-9"> } </span><span class="hl-1">=</span><span class="hl-9"> </span><span class="hl-0">await</span><span class="hl-9"> </span><span class="hl-5">fetchUsers</span><span class="hl-9">({</span><br/><span class="hl-9"> </span><span class="hl-2">q:</span><span class="hl-9"> </span><span class="hl-2">search</span><span class="hl-9">,</span><br/><span class="hl-9"> </span><span class="hl-2">sortField:</span><span class="hl-9"> </span><span class="hl-2">sort</span><span class="hl-9">.</span><span class="hl-2">field</span><span class="hl-9">,</span><br/><span class="hl-9"> </span><span class="hl-2">sortDir:</span><span class="hl-9"> </span><span class="hl-2">sort</span><span class="hl-9">.</span><span class="hl-2">dir</span><span class="hl-9">,</span><br/><span class="hl-9"> </span><span class="hl-2">page:</span><span class="hl-9"> </span><span class="hl-2">pagination</span><span class="hl-9">.</span><span class="hl-2">page</span><span class="hl-9">,</span><br/><span class="hl-9"> </span><span class="hl-2">limit:</span><span class="hl-9"> </span><span class="hl-2">pagination</span><span class="hl-9">.</span><span class="hl-2">rowsPerPage</span><span class="hl-9">,</span><br/><span class="hl-9"> </span><span class="hl-1">...</span><span class="hl-2">filterData</span><span class="hl-9">,</span><br/><span class="hl-9"> });</span><br/><span class="hl-9"> </span><span class="hl-0">return</span><span class="hl-9"> {</span><br/><span class="hl-9"> </span><span class="hl-2">rows:</span><span class="hl-9"> </span><span class="hl-2">items</span><span class="hl-9">,</span><br/><span class="hl-9"> </span><span class="hl-2">total:</span><span class="hl-9"> </span><span class="hl-2">count</span><span class="hl-9">,</span><br/><span class="hl-9"> };</span><br/><span class="hl-9"> }</span><span class="hl-4">}</span><br/><span class="hl-6">/&gt;</span>
</code><button type="button">Copy</button></pre>
<blockquote>
<p><strong>Note:</strong> The list <code>handler</code> always returns <code>{ rows, total }</code>. The <code>total</code> value drives pagination controls.</p>
</blockquote>
<a id="usesinglerunaction--prevent-duplicate-calls" class="tsd-anchor"></a><h2 class="tsd-anchor-link"><code>useSinglerunAction</code> — prevent duplicate calls<a href="#usesinglerunaction--prevent-duplicate-calls" 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>When a user clicks a button multiple times, <code>useSinglerunAction</code> guarantees the underlying async function runs only once per pending execution. If <code>execute</code> is called again while the previous promise is still pending, the second call is dropped.</p>
<pre><code class="tsx"><span class="hl-0">import</span><span class="hl-1"> { </span><span class="hl-2">useSinglerunAction</span><span class="hl-1">, </span><span class="hl-2">ActionIcon</span><span class="hl-1"> } </span><span class="hl-0">from</span><span class="hl-1"> </span><span class="hl-3">&#39;react-declarative&#39;</span><span class="hl-1">;</span><br/><span class="hl-0">import</span><span class="hl-1"> { </span><span class="hl-2">CloudUpload</span><span class="hl-1"> } </span><span class="hl-0">from</span><span class="hl-1"> </span><span class="hl-3">&#39;@mui/icons-material&#39;</span><span class="hl-1">;</span><br/><br/><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-5">UploadButton</span><span class="hl-1"> = ({ </span><span class="hl-2">onChange</span><span class="hl-1"> }) </span><span class="hl-4">=&gt;</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">execute</span><span class="hl-1"> } = </span><span class="hl-5">useSinglerunAction</span><span class="hl-1">(</span><span class="hl-4">async</span><span class="hl-1"> () </span><span class="hl-4">=&gt;</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">file</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-5">chooseFile</span><span class="hl-1">(</span><span class="hl-3">&#39;image/jpeg, image/png&#39;</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">file</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">filePath</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-5">uploadFile</span><span class="hl-1">(</span><span class="hl-2">file</span><span class="hl-1">);</span><br/><span class="hl-1"> </span><span class="hl-5">onChange</span><span class="hl-1">(</span><span class="hl-2">filePath</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><br/><span class="hl-1"> </span><span class="hl-6">&lt;</span><span class="hl-7">ActionIcon</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">execute</span><span class="hl-4">}</span><span class="hl-6">&gt;</span><br/><span class="hl-1"> </span><span class="hl-6">&lt;</span><span class="hl-7">CloudUpload</span><span class="hl-1"> </span><span class="hl-6">/&gt;</span><br/><span class="hl-1"> </span><span class="hl-6">&lt;/</span><span class="hl-7">ActionIcon</span><span class="hl-6">&gt;</span><br/><span class="hl-1"> );</span><br/><span class="hl-1">};</span>
</code><button type="button">Copy</button></pre>
<p>The hook returns <code>{ loading, error, execute }</code>. Pass <code>loading</code> to a spinner or disable a button while the action is running.</p>
<pre><code class="tsx"><span class="hl-4">const</span><span class="hl-1"> { </span><span class="hl-10">loading</span><span class="hl-1">, </span><span class="hl-10">error</span><span class="hl-1">, </span><span class="hl-10">execute</span><span class="hl-1"> } = </span><span class="hl-5">useSinglerunAction</span><span class="hl-1">(</span><span class="hl-4">async</span><span class="hl-1"> () </span><span class="hl-4">=&gt;</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">saveForm</span><span class="hl-1">(</span><span class="hl-2">formData</span><span class="hl-1">);</span><br/><span class="hl-1">});</span><br/><br/><span class="hl-6">&lt;</span><span class="hl-18">button</span><span class="hl-1"> </span><span class="hl-8">disabled</span><span class="hl-1">=</span><span class="hl-4">{</span><span class="hl-2">loading</span><span class="hl-4">}</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">execute</span><span class="hl-4">}</span><span class="hl-6">&gt;</span><br/><span class="hl-1"> </span><span class="hl-4">{</span><span class="hl-2">loading</span><span class="hl-9"> </span><span class="hl-1">?</span><span class="hl-9"> </span><span class="hl-3">&#39;Saving...&#39;</span><span class="hl-9"> </span><span class="hl-1">:</span><span class="hl-9"> </span><span class="hl-3">&#39;Save&#39;</span><span class="hl-4">}</span><br/><span class="hl-6">&lt;/</span><span class="hl-18">button</span><span class="hl-6">&gt;</span>
</code><button type="button">Copy</button></pre>
<a id="usequeuedaction--ordered-async-execution" class="tsd-anchor"></a><h2 class="tsd-anchor-link"><code>useQueuedAction</code> — ordered async execution<a href="#usequeuedaction--ordered-async-execution" 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>useQueuedAction</code> runs each call in the order it was enqueued. When a new call arrives before the previous one has resolved, it waits its turn rather than cancelling or being dropped. This is useful for real-time state updates (WebSocket messages, Redux-style reducers) where ordering matters.</p>
<pre><code class="tsx"><span class="hl-0">import</span><span class="hl-1"> { </span><span class="hl-2">useQueuedAction</span><span class="hl-1"> } </span><span class="hl-0">from</span><span class="hl-1"> </span><span class="hl-3">&#39;react-declarative&#39;</span><span class="hl-1">;</span><br/><br/><span class="hl-4">const</span><span class="hl-1"> { </span><span class="hl-10">execute</span><span class="hl-1"> } = </span><span class="hl-5">useQueuedAction</span><span class="hl-1">(</span><br/><span class="hl-1"> </span><span class="hl-4">async</span><span class="hl-1"> ({ </span><span class="hl-2">type</span><span class="hl-1">, </span><span class="hl-2">payload</span><span class="hl-1"> }) </span><span class="hl-4">=&gt;</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">type</span><span class="hl-1"> === </span><span class="hl-3">&#39;create-action&#39;</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">createRecord</span><span class="hl-1">(</span><span class="hl-2">payload</span><span class="hl-1">);</span><br/><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">type</span><span class="hl-1"> === </span><span class="hl-3">&#39;update-action&#39;</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">updateRecord</span><span class="hl-1">(</span><span class="hl-2">payload</span><span class="hl-1">);</span><br/><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">type</span><span class="hl-1"> === </span><span class="hl-3">&#39;remove-action&#39;</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">deleteRecord</span><span class="hl-1">(</span><span class="hl-2">payload</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/><span class="hl-1"> </span><span class="hl-5">onLoadStart</span><span class="hl-2">:</span><span class="hl-1"> () </span><span class="hl-4">=&gt;</span><span class="hl-1"> </span><span class="hl-5">setAppbarLoader</span><span class="hl-1">(</span><span class="hl-4">true</span><span class="hl-1">),</span><br/><span class="hl-1"> </span><span class="hl-5">onLoadEnd</span><span class="hl-2">:</span><span class="hl-1"> () </span><span class="hl-4">=&gt;</span><span class="hl-1"> </span><span class="hl-5">setAppbarLoader</span><span class="hl-1">(</span><span class="hl-4">false</span><span class="hl-1">),</span><br/><span class="hl-1"> }</span><br/><span class="hl-1">);</span><br/><br/><span class="hl-17">// Subscribe to real-time events and queue each one</span><br/><span class="hl-5">useEffect</span><span class="hl-1">(() </span><span class="hl-4">=&gt;</span><span class="hl-1"> </span><span class="hl-2">kanbanService</span><span class="hl-1">.</span><span class="hl-2">createSubject</span><span class="hl-1">.</span><span class="hl-5">subscribe</span><span class="hl-1">(</span><span class="hl-2">execute</span><span class="hl-1">), []);</span><br/><span class="hl-5">useEffect</span><span class="hl-1">(() </span><span class="hl-4">=&gt;</span><span class="hl-1"> </span><span class="hl-2">kanbanService</span><span class="hl-1">.</span><span class="hl-2">updateSubject</span><span class="hl-1">.</span><span class="hl-5">subscribe</span><span class="hl-1">(</span><span class="hl-2">execute</span><span class="hl-1">), []);</span><br/><span class="hl-5">useEffect</span><span class="hl-1">(() </span><span class="hl-4">=&gt;</span><span class="hl-1"> </span><span class="hl-2">kanbanService</span><span class="hl-1">.</span><span class="hl-2">removeSubject</span><span class="hl-1">.</span><span class="hl-5">subscribe</span><span class="hl-1">(</span><span class="hl-2">execute</span><span class="hl-1">), []);</span>
</code><button type="button">Copy</button></pre>
<p>The returned <code>execute</code> also exposes <code>execute.cancel()</code> to abandon any queued calls and <code>execute.clear()</code> to reset the queue state.</p>
<a id="useasyncprogress--batch-processing-with-progress-tracking" class="tsd-anchor"></a><h2 class="tsd-anchor-link"><code>useAsyncProgress</code> — batch processing with progress tracking<a href="#useasyncprogress--batch-processing-with-progress-tracking" 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>useAsyncProgress</code> processes an array of items one at a time and reports a <code>0–100</code> percent value as it goes. Use it for bulk imports, multi-step wizards, or any operation where you need a <code>&lt;LinearProgress /&gt;</code>.</p>
<pre><code class="tsx"><span class="hl-0">import</span><span class="hl-1"> { </span><span class="hl-2">useAsyncProgress</span><span class="hl-1">, </span><span class="hl-2">ActionButton</span><span class="hl-1"> } </span><span class="hl-0">from</span><span class="hl-1"> </span><span class="hl-3">&#39;react-declarative&#39;</span><span class="hl-1">;</span><br/><br/><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-5">ImportPage</span><span class="hl-1"> = () </span><span class="hl-4">=&gt;</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">progress</span><span class="hl-1">, </span><span class="hl-10">setProgress</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/><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> [</span><span class="hl-10">errors</span><span class="hl-1">, </span><span class="hl-10">setErrors</span><span class="hl-1">] = </span><span class="hl-5">useState</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-10">execute</span><span class="hl-1"> } = </span><span class="hl-5">useAsyncProgress</span><span class="hl-1">(</span><br/><span class="hl-1"> </span><span class="hl-4">async</span><span class="hl-1"> ({ </span><span class="hl-2">data</span><span class="hl-1"> }) </span><span class="hl-4">=&gt;</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">createContact</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><span class="hl-5">onProgress</span><span class="hl-2">:</span><span class="hl-1"> (</span><span class="hl-2">percent</span><span class="hl-1">) </span><span class="hl-4">=&gt;</span><span class="hl-1"> </span><span class="hl-5">setProgress</span><span class="hl-1">(</span><span class="hl-2">percent</span><span class="hl-1">),</span><br/><span class="hl-1"> </span><span class="hl-5">onError</span><span class="hl-2">:</span><span class="hl-1"> (</span><span class="hl-2">errors</span><span class="hl-1">) </span><span class="hl-4">=&gt;</span><span class="hl-1"> </span><span class="hl-5">setErrors</span><span class="hl-1">(</span><span class="hl-2">errors</span><span class="hl-1">),</span><br/><span class="hl-1"> </span><span class="hl-5">onEnd</span><span class="hl-2">:</span><span class="hl-1"> (</span><span class="hl-2">isOk</span><span class="hl-1">) </span><span class="hl-4">=&gt;</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">isOk</span><span class="hl-1">) </span><span class="hl-2">history</span><span class="hl-1">.</span><span class="hl-5">replace</span><span class="hl-1">(</span><span class="hl-3">&#39;/report&#39;</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-4">const</span><span class="hl-1"> </span><span class="hl-5">handleChooseFile</span><span class="hl-1"> = </span><span class="hl-4">async</span><span class="hl-1"> () </span><span class="hl-4">=&gt;</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">file</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-5">chooseFile</span><span class="hl-1">(</span><br/><span class="hl-1"> </span><span class="hl-3">&#39;application/vnd.openxmlformats-officedocument.spreadsheetml.sheet&#39;</span><br/><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">file</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">rows</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-5">parseExcelContacts</span><span class="hl-1">(</span><span class="hl-2">file</span><span class="hl-1">);</span><br/><span class="hl-1"> </span><span class="hl-5">execute</span><span class="hl-1">(</span><br/><span class="hl-1"> </span><span class="hl-2">rows</span><span class="hl-1">.</span><span class="hl-5">map</span><span class="hl-1">((</span><span class="hl-2">row</span><span class="hl-1">, </span><span class="hl-2">idx</span><span class="hl-1">) </span><span class="hl-4">=&gt;</span><span class="hl-1"> ({</span><br/><span class="hl-1"> </span><span class="hl-2">data:</span><span class="hl-1"> </span><span class="hl-2">row</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">label:</span><span class="hl-1"> </span><span class="hl-3">`Row №</span><span class="hl-4">${</span><span class="hl-2">idx</span><span class="hl-9"> </span><span class="hl-1">+</span><span class="hl-9"> </span><span class="hl-16">2</span><span class="hl-4">}</span><span class="hl-3">`</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/><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">&lt;&gt;</span><br/><span class="hl-1"> </span><span class="hl-6">&lt;</span><span class="hl-7">LinearProgress</span><span class="hl-1"> </span><span class="hl-8">variant</span><span class="hl-1">=</span><span class="hl-3">&quot;determinate&quot;</span><span class="hl-1"> </span><span class="hl-8">value</span><span class="hl-1">=</span><span class="hl-4">{</span><span class="hl-2">progress</span><span class="hl-4">}</span><span class="hl-1"> </span><span class="hl-6">/&gt;</span><br/><span class="hl-1"> </span><span class="hl-6">&lt;</span><span class="hl-7">ActionButton</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">handleChooseFile</span><span class="hl-4">}</span><span class="hl-6">&gt;</span><span class="hl-1">Choose XLSX</span><span class="hl-6">&lt;/</span><span class="hl-7">ActionButton</span><span class="hl-6">&gt;</span><br/><span class="hl-1"> </span><span class="hl-6">&lt;/&gt;</span><br/><span class="hl-1"> );</span><br/><span class="hl-1">};</span>
</code><button type="button">Copy</button></pre>
<p>Each item in the array you pass to <code>execute</code> should have <code>{ label, data }</code>. The <code>label</code> is used in the <code>onError</code> callback to identify which item failed.</p>
<a id="useasyncvalue--async-data-in-components" class="tsd-anchor"></a><h2 class="tsd-anchor-link"><code>useAsyncValue</code> — async data in components<a href="#useasyncvalue--async-data-in-components" 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>useAsyncValue</code> fetches data when the component mounts (or when its <code>deps</code> change) and exposes the result alongside loading and error states. It is the hook-based equivalent of the <code>handler</code> prop.</p>
<pre><code class="tsx"><span class="hl-0">import</span><span class="hl-1"> { </span><span class="hl-2">useAsyncValue</span><span class="hl-1"> } </span><span class="hl-0">from</span><span class="hl-1"> </span><span class="hl-3">&#39;react-declarative&#39;</span><span class="hl-1">;</span><br/><br/><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-5">ProfileCard</span><span class="hl-1"> = ({ </span><span class="hl-2">userId</span><span class="hl-1"> }) </span><span class="hl-4">=&gt;</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">profile</span><span class="hl-1">, { </span><span class="hl-10">loading</span><span class="hl-1">, </span><span class="hl-10">error</span><span class="hl-1"> }] = </span><span class="hl-5">useAsyncValue</span><span class="hl-1">(</span><br/><span class="hl-1"> </span><span class="hl-4">async</span><span class="hl-1"> () </span><span class="hl-4">=&gt;</span><span class="hl-1"> </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-5">fetchUserProfile</span><span class="hl-1">(</span><span class="hl-2">userId</span><span class="hl-1">),</span><br/><span class="hl-1"> { </span><span class="hl-2">deps:</span><span class="hl-1"> [</span><span class="hl-2">userId</span><span class="hl-1">] }</span><br/><span class="hl-1"> );</span><br/><br/><span class="hl-1"> </span><span class="hl-0">if</span><span class="hl-1"> (</span><span class="hl-2">loading</span><span class="hl-1">) </span><span class="hl-0">return</span><span class="hl-1"> </span><span class="hl-6">&lt;</span><span class="hl-7">CircularProgress</span><span class="hl-1"> </span><span class="hl-6">/&gt;</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">error</span><span class="hl-1">) </span><span class="hl-0">return</span><span class="hl-1"> </span><span class="hl-6">&lt;</span><span class="hl-18">p</span><span class="hl-6">&gt;</span><span class="hl-1">Failed to load profile.</span><span class="hl-6">&lt;/</span><span class="hl-18">p</span><span class="hl-6">&gt;</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">profile</span><span class="hl-1">) </span><span class="hl-0">return</span><span class="hl-1"> </span><span class="hl-4">null</span><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">&lt;</span><span class="hl-18">pre</span><span class="hl-6">&gt;</span><span class="hl-4">{</span><span class="hl-10">JSON</span><span class="hl-9">.</span><span class="hl-5">stringify</span><span class="hl-9">(</span><span class="hl-2">profile</span><span class="hl-9">, </span><span class="hl-4">null</span><span class="hl-9">, </span><span class="hl-16">2</span><span class="hl-9">)</span><span class="hl-4">}</span><span class="hl-6">&lt;/</span><span class="hl-18">pre</span><span class="hl-6">&gt;</span><br/><span class="hl-1"> );</span><br/><span class="hl-1">};</span>
</code><button type="button">Copy</button></pre>
<p>The hook returns a four-element tuple:</p>
<pre><code class="tsx"><span class="hl-4">const</span><span class="hl-1"> [</span><br/><span class="hl-1"> </span><span class="hl-10">data</span><span class="hl-1">, </span><span class="hl-17">// Data | null — the resolved value</span><br/><span class="hl-1"> { </span><span class="hl-10">loading</span><span class="hl-1">, </span><span class="hl-10">error</span><span class="hl-1">, </span><span class="hl-10">execute</span><span class="hl-1"> }, </span><span class="hl-17">// action state and manual re-fetch</span><br/><span class="hl-1"> </span><span class="hl-10">setData</span><span class="hl-1">, </span><span class="hl-17">// (data: Data) =&gt; void — update locally</span><br/><span class="hl-1"> { </span><span class="hl-10">waitForResult</span><span class="hl-1">, </span><span class="hl-10">data$</span><span class="hl-1"> } </span><span class="hl-17">// advanced: await first non-null value, get a ref</span><br/><span class="hl-1">] = </span><span class="hl-5">useAsyncValue</span><span class="hl-1">(</span><span class="hl-2">run</span><span class="hl-1">, </span><span class="hl-2">params</span><span class="hl-1">);</span>
</code><button type="button">Copy</button></pre>
<p>Call <code>setData</code> to update the local value optimistically after a mutation, without re-fetching:</p>
<pre><code class="tsx"><span class="hl-4">const</span><span class="hl-1"> [</span><span class="hl-10">profile</span><span class="hl-1">, </span><span class="hl-10">action</span><span class="hl-1">, </span><span class="hl-10">setProfile</span><span class="hl-1">] = </span><span class="hl-5">useAsyncValue</span><span class="hl-1">(() </span><span class="hl-4">=&gt;</span><span class="hl-1"> </span><span class="hl-5">fetchUserProfile</span><span class="hl-1">(</span><span class="hl-2">userId</span><span class="hl-1">));</span><br/><br/><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-5">handleSave</span><span class="hl-1"> = </span><span class="hl-4">async</span><span class="hl-1"> (</span><span class="hl-2">changes</span><span class="hl-1">) </span><span class="hl-4">=&gt;</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">updated</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-5">saveUserProfile</span><span class="hl-1">(</span><span class="hl-2">userId</span><span class="hl-1">, </span><span class="hl-2">changes</span><span class="hl-1">);</span><br/><span class="hl-1"> </span><span class="hl-5">setProfile</span><span class="hl-1">(</span><span class="hl-2">updated</span><span class="hl-1">); </span><span class="hl-17">// update without re-fetching</span><br/><span class="hl-1">};</span>
</code><button type="button">Copy</button></pre>
<a id="useasyncaction--general-async-with-cancellation" class="tsd-anchor"></a><h2 class="tsd-anchor-link"><code>useAsyncAction</code> — general async with cancellation<a href="#useasyncaction--general-async-with-cancellation" 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>useAsyncAction</code> is the lowest-level hook. It wraps any async function with <code>loading</code>/<code>error</code> state and cancels any in-flight execution when <code>execute</code> is called again.</p>
<pre><code class="tsx"><span class="hl-0">import</span><span class="hl-1"> { </span><span class="hl-2">useAsyncAction</span><span class="hl-1"> } </span><span class="hl-0">from</span><span class="hl-1"> </span><span class="hl-3">&#39;react-declarative&#39;</span><span class="hl-1">;</span><br/><br/><span class="hl-4">const</span><span class="hl-1"> { </span><span class="hl-10">loading</span><span class="hl-1">, </span><span class="hl-10">error</span><span class="hl-1">, </span><span class="hl-10">execute</span><span class="hl-1"> } = </span><span class="hl-5">useAsyncAction</span><span class="hl-1">(</span><br/><span class="hl-1"> </span><span class="hl-4">async</span><span class="hl-1"> (</span><span class="hl-2">userId</span><span class="hl-1">: </span><span class="hl-7">string</span><span class="hl-1">) </span><span class="hl-4">=&gt;</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-0">await</span><span class="hl-1"> </span><span class="hl-5">fetchUserProfile</span><span class="hl-1">(</span><span class="hl-2">userId</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">fallback</span><span class="hl-2">:</span><span class="hl-1"> (</span><span class="hl-2">e</span><span class="hl-1">) </span><span class="hl-4">=&gt;</span><span class="hl-1"> </span><span class="hl-2">console</span><span class="hl-1">.</span><span class="hl-5">error</span><span class="hl-1">(</span><span class="hl-3">&#39;Failed:&#39;</span><span class="hl-1">, </span><span class="hl-2">e</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>
<a id="actionbutton-and-actionicon" class="tsd-anchor"></a><h2 class="tsd-anchor-link"><code>ActionButton</code> and <code>ActionIcon</code><a href="#actionbutton-and-actionicon" 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>Both components accept an async <code>onClick</code> handler and automatically show a loading indicator while the promise is pending — no extra state needed.</p>
<p><strong>ActionButton</strong></p>
<pre><code class="tsx"><span class="hl-0">import</span><span class="hl-1"> { </span><span class="hl-2">ActionButton</span><span class="hl-1"> } </span><span class="hl-0">from</span><span class="hl-1"> </span><span class="hl-3">&#39;react-declarative&#39;</span><span class="hl-1">;</span><br/><br/><span class="hl-6">&lt;</span><span class="hl-7">ActionButton</span><br/><span class="hl-1"> </span><span class="hl-8">onClick</span><span class="hl-1">=</span><span class="hl-4">{async</span><span class="hl-9"> () </span><span class="hl-4">=&gt;</span><span class="hl-9"> {</span><br/><span class="hl-9"> </span><span class="hl-0">await</span><span class="hl-9"> </span><span class="hl-5">saveChanges</span><span class="hl-9">(</span><span class="hl-2">formData</span><span class="hl-9">);</span><br/><span class="hl-9"> }</span><span class="hl-4">}</span><br/><span class="hl-6">&gt;</span><br/><span class="hl-1"> Save changes</span><br/><span class="hl-6">&lt;/</span><span class="hl-7">ActionButton</span><span class="hl-6">&gt;</span>
</code><button type="button">Copy</button></pre>
<p><strong>ActionIcon</strong></p>
<pre><code class="tsx"><span class="hl-0">import</span><span class="hl-1"> { </span><span class="hl-2">ActionIcon</span><span class="hl-1"> } </span><span class="hl-0">from</span><span class="hl-1"> </span><span class="hl-3">&#39;react-declarative&#39;</span><span class="hl-1">;</span><br/><span class="hl-0">import</span><span class="hl-1"> { </span><span class="hl-2">CloudUpload</span><span class="hl-1"> } </span><span class="hl-0">from</span><span class="hl-1"> </span><span class="hl-3">&#39;@mui/icons-material&#39;</span><span class="hl-1">;</span><br/><br/><span class="hl-6">&lt;</span><span class="hl-7">ActionIcon</span><br/><span class="hl-1"> </span><span class="hl-8">onClick</span><span class="hl-1">=</span><span class="hl-4">{async</span><span class="hl-9"> () </span><span class="hl-4">=&gt;</span><span class="hl-9"> {</span><br/><span class="hl-9"> </span><span class="hl-0">await</span><span class="hl-9"> </span><span class="hl-5">uploadFile</span><span class="hl-9">(</span><span class="hl-2">selectedFile</span><span class="hl-9">);</span><br/><span class="hl-9"> }</span><span class="hl-4">}</span><br/><span class="hl-6">&gt;</span><br/><span class="hl-1"> </span><span class="hl-6">&lt;</span><span class="hl-7">CloudUpload</span><span class="hl-1"> </span><span class="hl-6">/&gt;</span><br/><span class="hl-6">&lt;/</span><span class="hl-7">ActionIcon</span><span class="hl-6">&gt;</span>
</code><button type="button">Copy</button></pre>
<blockquote>
<p><strong>Tip:</strong> Combine <code>ActionButton</code> with <code>useSinglerunAction</code> when you need the single-execution guarantee alongside the automatic loading indicator: <code>&lt;ActionButton onClick={execute}&gt;Save&lt;/ActionButton&gt;</code>.</p>
</blockquote>
<a id="choosing-the-right-primitive" class="tsd-anchor"></a><h2 class="tsd-anchor-link">Choosing the right primitive<a href="#choosing-the-right-primitive" 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><table>
<thead>
<tr>
<th>Situation</th>
<th>Use</th>
</tr>
</thead>
<tbody>
<tr>
<td>Load initial form data</td>
<td><code>handler</code> prop on <code>&lt;One /&gt;</code></td>
</tr>
<tr>
<td>Load list data with pagination</td>
<td><code>handler</code> prop on <code>&lt;ListTyped /&gt;</code> returning <code>{ rows, total }</code></td>
</tr>
<tr>
<td>Prevent double-submit</td>
<td><code>useSinglerunAction</code></td>
</tr>
<tr>
<td>Real-time ordered updates</td>
<td><code>useQueuedAction</code></td>
</tr>
<tr>
<td>Bulk import with progress bar</td>
<td><code>useAsyncProgress</code></td>
</tr>
<tr>
<td>Fetch remote data in a component</td>
<td><code>useAsyncValue</code></td>
</tr>
<tr>
<td>General async with cancellation</td>
<td><code>useAsyncAction</code></td>
</tr>
<tr>
<td>Button that shows spinner while async runs</td>
<td><code>ActionButton</code> / <code>ActionIcon</code></td>
</tr>
</tbody>
</table>
</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="#async-data-patterns-in-react-declarative"><span>Async <wbr/>Data <wbr/>Patterns in react-<wbr/>declarative</span></a><ul><li><a href="#loading-form-data-with-handler"><span>Loading form data with handler</span></a></li><li><a href="#usesinglerunaction--prevent-duplicate-calls"><span>use<wbr/>Singlerun<wbr/>Action — prevent duplicate calls</span></a></li><li><a href="#usequeuedaction--ordered-async-execution"><span>use<wbr/>Queued<wbr/>Action — ordered async execution</span></a></li><li><a href="#useasyncprogress--batch-processing-with-progress-tracking"><span>use<wbr/>Async<wbr/>Progress — batch processing with progress tracking</span></a></li><li><a href="#useasyncvalue--async-data-in-components"><span>use<wbr/>Async<wbr/>Value — async data in components</span></a></li><li><a href="#useasyncaction--general-async-with-cancellation"><span>use<wbr/>Async<wbr/>Action — general async with cancellation</span></a></li><li><a href="#actionbutton-and-actionicon"><span>Action<wbr/>Button and <wbr/>Action<wbr/>Icon</span></a></li><li><a href="#choosing-the-right-primitive"><span>Choosing the right primitive</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>