react-declarative-docs / documents /docs_components_one.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/components/one | 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_components_one.html">docs/components/one</a></li></ul></div><div class="tsd-panel tsd-typography"><a id="one-component-schema-driven-forms-with-auto-state" class="tsd-anchor"></a><h1 class="tsd-anchor-link">One component: schema-driven forms with auto state<a href="#one-component-schema-driven-forms-with-auto-state" 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>&lt;One /&gt;</code> is the core form-building component in react-declarative. You describe your form as a plain JavaScript array of field objects (<code>TypedField[]</code>), pass it to the component, and the library handles layout, validation, state diffing, and change callbacks for you. Every field can be conditionally visible, disabled, or invalid based on the current form data — all declared inline without extra state management on your side.</p>
<a id="installation" class="tsd-anchor"></a><h2 class="tsd-anchor-link">Installation<a href="#installation" 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="bash"><span class="hl-5">npm</span><span class="hl-1"> </span><span class="hl-3">install</span><span class="hl-1"> </span><span class="hl-4">--save</span><span class="hl-1"> </span><span class="hl-3">react-declarative</span><span class="hl-1"> </span><span class="hl-3">tss-react</span><span class="hl-1"> </span><span class="hl-3">@mui/material</span><span class="hl-1"> </span><span class="hl-3">@emotion/react</span><span class="hl-1"> </span><span class="hl-3">@emotion/styled</span>
</code><button type="button">Copy</button></pre>
<a id="minimal-working-example" class="tsd-anchor"></a><h2 class="tsd-anchor-link">Minimal working example<a href="#minimal-working-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><span class="hl-2">One</span><span class="hl-1">, </span><span class="hl-2">TypedField</span><span class="hl-1">, </span><span class="hl-2">FieldType</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">interface</span><span class="hl-1"> </span><span class="hl-7">IUserData</span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">firstName</span><span class="hl-1">: </span><span class="hl-7">string</span><span class="hl-1">;</span><br/><span class="hl-1"> </span><span class="hl-2">lastName</span><span class="hl-1">: </span><span class="hl-7">string</span><span class="hl-1">;</span><br/><span class="hl-1"> </span><span class="hl-2">email</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">&lt;</span><span class="hl-7">IUserData</span><span class="hl-1">&gt;[] = [</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">Line</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">&#39;User info&#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><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">&#39;firstName&#39;</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">&#39;First name&#39;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">defaultValue:</span><span class="hl-1"> </span><span class="hl-3">&#39;&#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><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">&#39;lastName&#39;</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">&#39;Last name&#39;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">defaultValue:</span><span class="hl-1"> </span><span class="hl-3">&#39;&#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><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">&#39;email&#39;</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">&#39;Email address&#39;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">defaultValue:</span><span class="hl-1"> </span><span class="hl-3">&#39;&#39;</span><span class="hl-1">,</span><br/><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">UserForm</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-6">&lt;</span><span class="hl-7">One</span><span class="hl-1">&lt;</span><span class="hl-7">IUserData</span><span class="hl-1">&gt;</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">{</span><span class="hl-9">() </span><span class="hl-4">=&gt;</span><span class="hl-9"> ({ </span><span class="hl-2">firstName:</span><span class="hl-9"> </span><span class="hl-3">&#39;&#39;</span><span class="hl-9">, </span><span class="hl-2">lastName:</span><span class="hl-9"> </span><span class="hl-3">&#39;&#39;</span><span class="hl-9">, </span><span class="hl-2">email:</span><span class="hl-9"> </span><span class="hl-3">&#39;&#39;</span><span class="hl-9"> })</span><span class="hl-4">}</span><br/><span class="hl-1"> </span><span class="hl-8">onChange</span><span class="hl-1">=</span><span class="hl-4">{</span><span class="hl-9">(</span><span class="hl-2">data</span><span class="hl-9">, </span><span class="hl-2">initial</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">if</span><span class="hl-9"> (</span><span class="hl-1">!</span><span class="hl-2">initial</span><span class="hl-9">) </span><span class="hl-2">console</span><span class="hl-9">.</span><span class="hl-5">log</span><span class="hl-9">(</span><span class="hl-3">&#39;form changed&#39;</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-1"> </span><span class="hl-6">/&gt;</span><br/><span class="hl-1">);</span>
</code><button type="button">Copy</button></pre>
<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-2">TypedField</span><span class="hl-1">, </span><span class="hl-2">FieldType</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">interface</span><span class="hl-1"> </span><span class="hl-7">IUserData</span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">phone</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">interface</span><span class="hl-1"> </span><span class="hl-7">IPayload</span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">showPhone</span><span class="hl-1">: </span><span class="hl-7">boolean</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">&lt;</span><span class="hl-7">IUserData</span><span class="hl-1">, </span><span class="hl-7">IPayload</span><span class="hl-1">&gt;[] = [</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">&#39;phone&#39;</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">&#39;Phone&#39;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-5">isVisible</span><span class="hl-2">:</span><span class="hl-1"> (</span><span class="hl-2">data</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><span class="hl-2">payload</span><span class="hl-1">.</span><span class="hl-2">showPhone</span><span class="hl-1">,</span><br/><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">PhoneForm</span><span class="hl-1"> = ({ </span><span class="hl-2">showPhone</span><span class="hl-1"> }: { </span><span class="hl-2">showPhone</span><span class="hl-1">: </span><span class="hl-7">boolean</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-6">&lt;</span><span class="hl-7">One</span><span class="hl-1">&lt;</span><span class="hl-7">IUserData</span><span class="hl-1">, </span><span class="hl-7">IPayload</span><span class="hl-1">&gt;</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">{</span><span class="hl-9">() </span><span class="hl-4">=&gt;</span><span class="hl-9"> ({ </span><span class="hl-2">phone:</span><span class="hl-9"> </span><span class="hl-3">&#39;&#39;</span><span class="hl-9"> })</span><span class="hl-4">}</span><br/><span class="hl-1"> </span><span class="hl-8">payload</span><span class="hl-1">=</span><span class="hl-4">{</span><span class="hl-9">{ </span><span class="hl-2">showPhone</span><span class="hl-9"> }</span><span class="hl-4">}</span><br/><span class="hl-1"> </span><span class="hl-8">onChange</span><span class="hl-1">=</span><span class="hl-4">{</span><span class="hl-9">(</span><span class="hl-2">data</span><span class="hl-9">) </span><span class="hl-4">=&gt;</span><span class="hl-9"> </span><span class="hl-2">console</span><span class="hl-9">.</span><span class="hl-5">log</span><span class="hl-9">(</span><span class="hl-2">data</span><span class="hl-9">)</span><span class="hl-4">}</span><br/><span class="hl-1"> </span><span class="hl-6">/&gt;</span><br/><span class="hl-1">);</span>
</code><button type="button">Copy</button></pre>
<a id="key-props" class="tsd-anchor"></a><h2 class="tsd-anchor-link">Key props<a href="#key-props" 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><code>fields</code></strong><code>TypedField[]</code> (required)</p>
<p>The schema that describes every field and layout in the form. Each entry is a
plain object with a <code>type</code> (from <code>FieldType</code>) plus field-specific options
such as <code>name</code>, <code>title</code>, <code>defaultValue</code>, and validation callbacks.</p>
<hr>
<p><strong><code>handler</code></strong><code>Data | ((payload) =&gt; Data | Promise&lt;Data&gt;) | null</code></p>
<p>Provides the initial data for the form. You can pass a plain object, a
synchronous function, or an async function that fetches data from an API.
The result is merged into the form state on mount (and on <code>reloadSubject</code>
emission).</p>
<hr>
<p><strong><code>onChange</code></strong><code>(data: Data, initial: boolean) =&gt; void</code></p>
<p>Called every time any field value changes. The second argument <code>initial</code> is
<code>true</code> during the first render cycle when the form is populated from
<code>handler</code> — use this flag to skip saving on the first load.</p>
<hr>
<p><strong><code>payload</code></strong><code>Payload | (() =&gt; Payload)</code></p>
<p>An arbitrary object passed through to every field callback (<code>isVisible</code>,
<code>isDisabled</code>, <code>isInvalid</code>, etc.). Use it to carry contextual data such as
user roles or feature flags without putting them in the form data itself.</p>
<hr>
<p><strong><code>data</code></strong><code>Data | null</code></p>
<p>An alternative to <code>handler</code> for controlled usage. Pass the current value
from React state and update it in <code>onChange</code>. Prefer <code>handler</code> for async
data loading.</p>
<hr>
<p><strong><code>dirty</code></strong><code>boolean</code></p>
<p>When <code>true</code>, validation errors are shown immediately on all fields without
waiting for the user to focus each one. Useful when you want to validate on
submit.</p>
<hr>
<p><strong><code>readonly</code></strong><code>boolean</code></p>
<p>Puts every field into read-only mode. The form still renders with values but
no input is accepted.</p>
<hr>
<p><strong><code>disabled</code></strong><code>boolean</code></p>
<p>Disables all inputs across the entire form.</p>
<hr>
<p><strong><code>fieldDebounce</code></strong><code>number</code></p>
<p>Debounce delay in milliseconds applied to <code>FieldType.Text</code> fields before
<code>onChange</code> fires. Defaults to no debounce. Set to <code>500</code> or higher to reduce
the number of change events during rapid typing.</p>
<a id="field-level-callbacks" class="tsd-anchor"></a><h2 class="tsd-anchor-link">Field-level callbacks<a href="#field-level-callbacks" 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>Each field object in your <code>TypedField[]</code> array can declare three lifecycle
callbacks that receive the current form data and the payload. All three can
return a <code>boolean</code> or a <code>Promise&lt;boolean&gt;</code>.</p>
<pre><code class="tsx"><span class="hl-0">import</span><span class="hl-1"> { </span><span class="hl-2">TypedField</span><span class="hl-1">, </span><span class="hl-2">FieldType</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">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">email</span><span class="hl-1">: </span><span class="hl-7">string</span><span class="hl-1">;</span><br/><span class="hl-1"> </span><span class="hl-2">visible</span><span class="hl-1">: </span><span class="hl-7">boolean</span><span class="hl-1">;</span><br/><span class="hl-1"> </span><span class="hl-2">disabled</span><span class="hl-1">: </span><span class="hl-7">boolean</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">&lt;</span><span class="hl-7">IFormData</span><span class="hl-1">&gt;[] = [</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">&#39;email&#39;</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">&#39;Email&#39;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-5">isInvalid</span><span class="hl-1">({ </span><span class="hl-2">email</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">expr</span><span class="hl-1"> =</span><span class="hl-11"> /</span><span class="hl-12">^</span><span class="hl-13">[</span><span class="hl-14">\w</span><span class="hl-11">-.</span><span class="hl-13">]</span><span class="hl-15">+</span><span class="hl-11">@</span><span class="hl-13">([</span><span class="hl-11">\w-</span><span class="hl-13">]</span><span class="hl-15">+</span><span class="hl-14">\.</span><span class="hl-13">)</span><span class="hl-15">+</span><span class="hl-13">[</span><span class="hl-11">\w-</span><span class="hl-13">]</span><span class="hl-15">{2,4}</span><span class="hl-12">$</span><span class="hl-11">/</span><span class="hl-4">g</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">expr</span><span class="hl-1">.</span><span class="hl-5">test</span><span class="hl-1">(</span><span class="hl-2">email</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-3">&#39;Invalid email address&#39;</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-4">null</span><span class="hl-1">;</span><br/><span class="hl-1"> },</span><br/><span class="hl-1"> </span><span class="hl-5">isDisabled</span><span class="hl-1">({ </span><span class="hl-2">disabled</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">disabled</span><span class="hl-1">;</span><br/><span class="hl-1"> },</span><br/><span class="hl-1"> </span><span class="hl-5">isVisible</span><span class="hl-1">({ </span><span class="hl-2">visible</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">visible</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-2">type:</span><span class="hl-1"> </span><span class="hl-2">FieldType</span><span class="hl-1">.</span><span class="hl-2">Switch</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">&#39;visible&#39;</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">&#39;Show email field&#39;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">defaultValue:</span><span class="hl-1"> </span><span class="hl-4">true</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-2">type:</span><span class="hl-1"> </span><span class="hl-2">FieldType</span><span class="hl-1">.</span><span class="hl-2">Switch</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">&#39;disabled&#39;</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">&#39;Disable email field&#39;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">defaultValue:</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>
</code><button type="button">Copy</button></pre>
<blockquote>
<p><strong>Note:</strong> <code>isInvalid</code> should return a <strong>string</strong> with the error message when validation
fails, or <code>null</code> when the value is valid. The string is displayed as a helper
text below the field.</p>
</blockquote>
<a id="layout-types" class="tsd-anchor"></a><h2 class="tsd-anchor-link">Layout types<a href="#layout-types" 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>TypedField</code> entries with layout types (<code>FieldType.Group</code>, <code>FieldType.Paper</code>,
<code>FieldType.Expansion</code>, etc.) accept a nested <code>fields</code> array, letting you build
responsive 12-column grid layouts:</p>
<pre><code class="tsx"><span class="hl-0">import</span><span class="hl-1"> { </span><span class="hl-2">TypedField</span><span class="hl-1">, </span><span class="hl-2">FieldType</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">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">Group</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">phoneColumns:</span><span class="hl-1"> </span><span class="hl-3">&#39;12&#39;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">tabletColumns:</span><span class="hl-1"> </span><span class="hl-3">&#39;6&#39;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">desktopColumns:</span><span class="hl-1"> </span><span class="hl-3">&#39;4&#39;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">fields:</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">&#39;firstName&#39;</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">&#39;First name&#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><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">&#39;lastName&#39;</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">&#39;Last name&#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/><span class="hl-1">];</span>
</code><button type="button">Copy</button></pre>
<a id="typed-variant-onetyped" class="tsd-anchor"></a><h2 class="tsd-anchor-link">Typed variant: <code>OneTyped</code><a href="#typed-variant-onetyped" 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>OneTyped</code> is a stricter wrapper around <code>One</code> that enforces the generic type
parameter at the component level, making it impossible to pass incompatible
field schemas:</p>
<pre><code class="tsx"><span class="hl-0">import</span><span class="hl-1"> { </span><span class="hl-2">OneTyped</span><span class="hl-1">, </span><span class="hl-2">TypedField</span><span class="hl-1">, </span><span class="hl-2">FieldType</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">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">username</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">&lt;</span><span class="hl-7">IFormData</span><span class="hl-1">&gt;[] = [</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">&#39;username&#39;</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">&#39;Username&#39;</span><span class="hl-1">,</span><br/><span class="hl-1"> },</span><br/><span class="hl-1">];</span><br/><br/><span class="hl-17">// TypeScript will error if fields do not match IFormData</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-5">UsernameForm</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-6">&lt;</span><span class="hl-7">OneTyped</span><span class="hl-1">&lt;</span><span class="hl-7">IFormData</span><span class="hl-1">&gt;</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">{</span><span class="hl-9">() </span><span class="hl-4">=&gt;</span><span class="hl-9"> ({ </span><span class="hl-2">username:</span><span class="hl-9"> </span><span class="hl-3">&#39;&#39;</span><span class="hl-9"> })</span><span class="hl-4">}</span><br/><span class="hl-1"> </span><span class="hl-8">onChange</span><span class="hl-1">=</span><span class="hl-4">{</span><span class="hl-9">(</span><span class="hl-2">data</span><span class="hl-9">) </span><span class="hl-4">=&gt;</span><span class="hl-9"> </span><span class="hl-2">console</span><span class="hl-9">.</span><span class="hl-5">log</span><span class="hl-9">(</span><span class="hl-2">data</span><span class="hl-9">.</span><span class="hl-2">username</span><span class="hl-9">)</span><span class="hl-4">}</span><br/><span class="hl-1"> </span><span class="hl-6">/&gt;</span><br/><span class="hl-1">);</span>
</code><button type="button">Copy</button></pre>
<blockquote>
<p><strong>Tip:</strong> Use <code>OneTyped</code> in new projects — the stricter generics catch schema mismatches
at compile time rather than at runtime.</p>
</blockquote>
<a id="async-data-loading" class="tsd-anchor"></a><h2 class="tsd-anchor-link">Async data loading<a href="#async-data-loading" 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 accepts an async function, so you can fetch initial form
data from an API without any extra <code>useEffect</code> or loading state:</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-2">TypedField</span><span class="hl-1">, </span><span class="hl-2">FieldType</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">interface</span><span class="hl-1"> </span><span class="hl-7">IProfile</span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">bio</span><span class="hl-1">: </span><span class="hl-7">string</span><span class="hl-1">;</span><br/><span class="hl-1"> </span><span class="hl-2">website</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">&lt;</span><span class="hl-7">IProfile</span><span class="hl-1">&gt;[] = [</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><span class="hl-2">name:</span><span class="hl-1"> </span><span class="hl-3">&#39;bio&#39;</span><span class="hl-1">, </span><span class="hl-2">title:</span><span class="hl-1"> </span><span class="hl-3">&#39;Bio&#39;</span><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><span class="hl-2">name:</span><span class="hl-1"> </span><span class="hl-3">&#39;website&#39;</span><span class="hl-1">, </span><span class="hl-2">title:</span><span class="hl-1"> </span><span class="hl-3">&#39;Website&#39;</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">ProfileForm</span><span class="hl-1"> = ({ </span><span class="hl-2">userId</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-6">&lt;</span><span class="hl-7">One</span><span class="hl-1">&lt;</span><span class="hl-7">IProfile</span><span class="hl-1">&gt;</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">res</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">fetch</span><span class="hl-9">(</span><span class="hl-3">`/api/profile/</span><span class="hl-4">${</span><span class="hl-2">userId</span><span class="hl-4">}</span><span class="hl-3">`</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">res</span><span class="hl-9">.</span><span class="hl-5">json</span><span class="hl-9">();</span><br/><span class="hl-9"> }</span><span class="hl-4">}</span><br/><span class="hl-1"> </span><span class="hl-8">onChange</span><span class="hl-1">=</span><span class="hl-4">{</span><span class="hl-9">(</span><span class="hl-2">data</span><span class="hl-9">, </span><span class="hl-2">initial</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">if</span><span class="hl-9"> (</span><span class="hl-1">!</span><span class="hl-2">initial</span><span class="hl-9">) </span><span class="hl-5">saveProfile</span><span class="hl-9">(</span><span class="hl-2">userId</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-1"> </span><span class="hl-8">fallback</span><span class="hl-1">=</span><span class="hl-4">{</span><span class="hl-9">(</span><span class="hl-2">e</span><span class="hl-9">) </span><span class="hl-4">=&gt;</span><span class="hl-9"> </span><span class="hl-2">console</span><span class="hl-9">.</span><span class="hl-5">error</span><span class="hl-9">(</span><span class="hl-3">&#39;Failed to load profile&#39;</span><span class="hl-9">, </span><span class="hl-2">e</span><span class="hl-9">)</span><span class="hl-4">}</span><br/><span class="hl-1"> </span><span class="hl-6">/&gt;</span><br/><span class="hl-1">);</span>
</code><button type="button">Copy</button></pre>
<a id="injecting-custom-jsx" class="tsd-anchor"></a><h2 class="tsd-anchor-link">Injecting custom JSX<a href="#injecting-custom-jsx" 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>Use <code>FieldType.Component</code> to embed any React component directly inside the
form schema without breaking the layout grid:</p>
<pre><code class="tsx"><span class="hl-0">import</span><span class="hl-1"> { </span><span class="hl-2">TypedField</span><span class="hl-1">, </span><span class="hl-2">FieldType</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">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">Paper</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">fields:</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">Component</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-5">element</span><span class="hl-2">:</span><span class="hl-1"> (</span><span class="hl-2">props</span><span class="hl-1">) </span><span class="hl-4">=&gt;</span><span class="hl-1"> </span><span class="hl-6">&lt;</span><span class="hl-7">MyCustomWidget</span><span class="hl-1"> </span><span class="hl-4">{</span><span class="hl-1">...</span><span class="hl-2">props</span><span class="hl-4">}</span><span class="hl-1"> </span><span class="hl-6">/&gt;</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>
</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="#one-component-schema-driven-forms-with-auto-state"><span>One component: schema-<wbr/>driven forms with auto state</span></a><ul><li><a href="#installation"><span>Installation</span></a></li><li><a href="#minimal-working-example"><span>Minimal working example</span></a></li><li><a href="#key-props"><span>Key props</span></a></li><li><a href="#field-level-callbacks"><span>Field-<wbr/>level callbacks</span></a></li><li><a href="#layout-types"><span>Layout types</span></a></li><li><a href="#typed-variant-onetyped"><span>Typed variant: <wbr/>One<wbr/>Typed</span></a></li><li><a href="#async-data-loading"><span>Async data loading</span></a></li><li><a href="#injecting-custom-jsx"><span>Injecting custom JSX</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>