<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Pablo Berganza]]></title><description><![CDATA[Software developer, mainly using TypeScript and JavaScript. Creator of Felte.]]></description><link>https://hn.berganza.dev</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1645398492625/lEvzC-V7Q.png</url><title>Pablo Berganza</title><link>https://hn.berganza.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Fri, 17 Apr 2026 22:49:11 GMT</lastBuildDate><atom:link href="https://hn.berganza.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Shadow DOM, Firefox and contenteditable]]></title><description><![CDATA[This is more of a short note about some experiments when working with web components that I’m publishing as a reference for future me (or other people that experience the same issue).

I’ve been experimenting with web components in order to build a w...]]></description><link>https://hn.berganza.dev/shadow-dom-firefox-and-contenteditable</link><guid isPermaLink="true">https://hn.berganza.dev/shadow-dom-firefox-and-contenteditable</guid><category><![CDATA[Firefox]]></category><category><![CDATA[Web Components]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[Web Development]]></category><dc:creator><![CDATA[Pablo Berganza]]></dc:creator><pubDate>Wed, 16 Mar 2022 15:59:35 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1647446274351/hedA7xUTH.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p>This is more of a short note about some experiments when working with web components that I’m publishing as a reference for future me (or other people that experience the same issue).</p>
</blockquote>
<p>I’ve been experimenting with web components in order to build a wrapper for <a target="_blank" href="https://felte.dev">Felte</a>  that can easily be used with vanilla JS. One of Felte’s features is the ability to use custom field components that are not based on the browser’s native inputs (<code>input</code>, <code>textarea</code>, <code>select</code>). The example I show is a div with an attribute <code>[contenteditable=“true”]</code>. While testing this experiment I found some weird behaviour coming from Firefox: while I could perfectly click each field and type of it, if I tried to use the form only using the keyboard (tabbing to each field) the <em>focus</em> moved but trying to type would always result in the text being added to the first field I focused.</p>
<p>Another confusing behaviour is that, even if you can type on the element when <em>clicking</em> on the element itself, the care is not displayed at all. So there’s no visual cue indicating the user that the element itself is editable. Currently, there’s an <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1496769">open issue on bugzilla that seems to be exactly this</a>.</p>
<p>This behaviour is, of course, unacceptable. Specially since forms (and web applications in general) should be accessible to keyboard users. In order for <a target="_blank" href="https://codesandbox.io/s/github/pablo-abc/felte/tree/main/examples/lit/custom-field">the demo I was working on</a> to function correctly I went to look for an immediate solution. After some research I found that the solution that works more consistently for me is to <em>not</em> add <code>[contenteditable]</code> to the fields on render and, instead, add event listeners that dynamically add the attribute on focus and remove it on blur:</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">handleFocus</span>(<span class="hljs-params">e</span>) </span>{
  e.target.setAttribute(<span class="hljs-string">'contenteditable'</span>, <span class="hljs-string">''</span>);
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">handleBlur</span>(<span class="hljs-params">e</span>) </span>{
  e.target.removeAttribute(<span class="hljs-string">'contenteditable'</span>);
}

<span class="hljs-comment">// We query the shadowRoot of the element that contains</span>
<span class="hljs-comment">// our `contenteditable` fields</span>
element.shadowRoot
  .querySelectorAll(<span class="hljs-string">'div[role="textbox"]'</span>)
  .forEach(<span class="hljs-function">(<span class="hljs-params">el</span>) =&gt;</span> {
    el.addEventListener(<span class="hljs-string">'focusin'</span>, handleFocus);
    el.addEventListener(<span class="hljs-string">'focusout'</span>, handleBlur);
  });
</code></pre>
<p>Or better yet, in order to make it more easy to reuse, make a custom element that behaves like this:</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">handleFocus</span>(<span class="hljs-params">e</span>) </span>{
  e.target.setAttribute(<span class="hljs-string">'contenteditable'</span>, <span class="hljs-string">''</span>);
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">handleBlur</span>(<span class="hljs-params">e</span>) </span>{
  e.target.removeAttribute(<span class="hljs-string">'contenteditable'</span>);
}

<span class="hljs-keyword">export</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyField</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">HTMLElement</span> </span>{
  <span class="hljs-keyword">constructor</span>() {
    <span class="hljs-built_in">super</span>();
    <span class="hljs-comment">// Make the element focusable</span>
    <span class="hljs-built_in">this</span>.setAttribute(<span class="hljs-string">'tabindex'</span>, <span class="hljs-string">'0'</span>);
    <span class="hljs-comment">// Assign a role for assistive technologies</span>
    <span class="hljs-built_in">this</span>.setAttribute(<span class="hljs-string">'role'</span>, <span class="hljs-string">'textbox'</span>);
    <span class="hljs-comment">// Some default styles</span>
    <span class="hljs-built_in">this</span>.style.display = <span class="hljs-string">'block'</span>;
    <span class="hljs-built_in">this</span>.style.cursor = <span class="hljs-string">'text'</span>;
  }

  connectedCallback() {
    <span class="hljs-built_in">this</span>.addEventListener(<span class="hljs-string">'focusin'</span>, handleFocus);
    <span class="hljs-built_in">this</span>.addEventListener(<span class="hljs-string">'focusout'</span>, handleBlur);
  }

  disconnectedCallback() {
    <span class="hljs-built_in">this</span>.removeEventListener(<span class="hljs-string">'focusin'</span>, handleFocus);
    <span class="hljs-built_in">this</span>.removeEventListener(<span class="hljs-string">'focusout'</span>, handleBlur);
  }
}

customElements.define(<span class="hljs-string">'my-field'</span>, MyField);
</code></pre>
<p>This way you can use <code>&lt;my-field&gt;&lt;/my-field&gt;</code> as a <code>[contenteditable]</code> “div”!</p>
<p>Keep in mind that this article only worries about making focus work correctly on a <code>[contenteditable]</code> element. There's more things that you should consider when doing something like this which would depend on your use-case.</p>
]]></content:encoded></item><item><title><![CDATA[Creating a Chai like assertion library using proxies]]></title><description><![CDATA[For the past few weeks I’ve taken the (arguably pointless) work of migrating Felte from using Jest to uvu. This is a really tedious work by itself, but one of details that would have made this work even more tedious is that Jest prefers assertions to...]]></description><link>https://hn.berganza.dev/creating-a-chai-like-assertion-library-using-proxies</link><guid isPermaLink="true">https://hn.berganza.dev/creating-a-chai-like-assertion-library-using-proxies</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[Testing]]></category><dc:creator><![CDATA[Pablo Berganza]]></dc:creator><pubDate>Sat, 19 Feb 2022 22:40:50 GMT</pubDate><enclosure url="https://pablo.berganza.dev/assets/blog-pics/2022-02-19.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>For the past few weeks I’ve taken the (arguably pointless) work of migrating <a target="_blank" href="https://felte.dev">Felte</a> from using Jest to <a target="_blank" href="https://github.com/lukeed/uvu">uvu</a>. This is a really tedious work by itself, but one of details that would have made this work even more tedious is that Jest prefers assertions to the style of <code>expect(…).toBe*</code> while uvu gives you freedom to choose any assertion library, although there’s an official <code>uvu/assert</code> module that comes with assertions to the style of <code>assert.is(value, expected)</code>.</p>
<p>While this is fine and I could have perfectly moved all my tests to use said assertion style, I like the descriptive way Jest tests look like. As a quick way to maintain certain similarity I reached for <a target="_blank" href="https://chaijs.com">ChaiJS</a>, an assertion library that is mainly used with <a target="_blank" href="https://mochajs.org">mocha</a>. Chai offers <code>expect</code> like assertions that can arguably be more descriptive than Jest’s. Instead of writing <code>expect(…).toBe(true)</code>, you’d write <code>expect(…).to.be.true</code>. For the most part I managed to do a search and replace for this.</p>
<p>This setup works really good! But there’s some minor details: The assertion errors thrown by Chai are slightly different than those expected by uvu., so sometimes I’d get messages or extra details that are not so relevant to the test itself. Another issue is that I’d receive diffs comparing <code>undefined</code> to <code>undefined</code> when an assertion failed. As a proper developer with too much free time, I went ahead and decided to experiment with <a target="_blank" href="https://xkcd.com/927/">writing my own assertion library</a> built on top of uvu’s assertions that I called <a target="_blank" href="https://github.com/pablo-abc/uvu-expect">uvu-expect</a>. Here’s more or less how I did it.</p>
<h2 id="heading-the-expect-function">The “expect” function</h2>
<p>The main thing our assertion library needs is an <code>expect</code> function that should receive the value you’re planning to validate.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">expect</span>(<span class="hljs-params">value</span>) </span>{
  <span class="hljs-comment">// run your validations here</span>
}
</code></pre>
<p>If we wanted to keep a similar API to Jest, this could return an object with functions.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">expect</span>(<span class="hljs-params">value</span>) </span>{
  <span class="hljs-keyword">return</span> {
    toBe(expected) {
      <span class="hljs-keyword">if</span> (expected !== value) {
        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'Expected values to be strictly equal'</span>);
      }
    },
  };
}
</code></pre>
<p>But I actually really enjoyed Chai’s syntax. So I decided to use proxies to achieve something similar. We could start by allowing to chain arbitrary words after our <code>expect</code> call. I decided not to restrict the possible “chain” words to simplify development.</p>
<p><strong>Proxy</strong> is a JavaScript feature that allows you to "wrap" an object in order to intercept and modify its functionality. In our case we will use it to modify the behaviour when <em>accessing</em> our object's properties.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">expect</span>(<span class="hljs-params">value</span>) </span>{
  <span class="hljs-keyword">const</span> proxy = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Proxy</span>(
    <span class="hljs-comment">// The target we are adding the proxy on. For now it's empty.</span>
    {},
    {
      get() {
        <span class="hljs-comment">// Any property access returns the proxy once again.</span>
        <span class="hljs-keyword">return</span> proxy;
      },
    }
  );
  <span class="hljs-keyword">return</span> proxy;
}

expect().this.does.nothing.but.also.does.not.crash;
</code></pre>
<p>Next we will allow for <em>any</em> of these chain words to be functions.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">expect</span>(<span class="hljs-params">value</span>) </span>{
  <span class="hljs-keyword">const</span> proxy = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Proxy</span>(
    {},
    {
      get(_, outerProp) {
        <span class="hljs-comment">// Instead of returning the initial proxy, we return</span>
        <span class="hljs-comment">// a new proxy that wraps a function.</span>
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Proxy</span>(<span class="hljs-function">() =&gt;</span> proxy, {
          get(_, innerProp) {
            <span class="hljs-comment">// If the function does not get called, and a property gets</span>
            <span class="hljs-comment">// accessed directly, we access the same property</span>
            <span class="hljs-comment">// from our original proxy.</span>
            <span class="hljs-keyword">return</span> proxy[innerProp];
          },
        });
      },
    }
  );
  <span class="hljs-keyword">return</span> proxy;
}

expect().this.does.nothing().but.also.does.not.crash();
</code></pre>
<p>With this we already got the base for our syntax. We now need to be able to add some <em>meaning</em> to certain properties. For example, we might want to make <code>expect(…).to.be.null</code> to check whether a value is null or not.</p>
<h2 id="heading-adding-meaning-to-our-properties">Adding meaning to our properties</h2>
<p>We could perfectly check the <code>name</code> of the property being accessed and use that to run validations. For example, if we wanted to add a validation for checking if a value is <code>null</code>:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// For brevity, we're not going to use the code that handles functions.</span>
<span class="hljs-comment">// Only property access</span>
<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">expect</span>(<span class="hljs-params">value</span>) </span>{
  <span class="hljs-keyword">const</span> proxy = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Proxy</span>(
    {},
    {
      get(_, prop) {
        <span class="hljs-comment">// `prop` is the name of the propery being</span>
        <span class="hljs-comment">// accessed.</span>
        <span class="hljs-keyword">switch</span> (prop) {
          <span class="hljs-keyword">case</span> <span class="hljs-string">'null'</span>:
            <span class="hljs-keyword">if</span> (value !== <span class="hljs-literal">null</span>) {
              <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'Expected value to be null'</span>);
            }
            <span class="hljs-keyword">break</span>;
        }
        <span class="hljs-keyword">return</span> proxy;
      },
    }
  );
  <span class="hljs-keyword">return</span> proxy;
}

expect(<span class="hljs-literal">null</span>).to.be.null;
<span class="hljs-keyword">try</span> {
  expect(<span class="hljs-string">'not null'</span>).to.be.null;
} <span class="hljs-keyword">catch</span> (err) {
  <span class="hljs-built_in">console</span>.log(err.message); <span class="hljs-comment">// =&gt; "Expected value to be null"</span>
}
</code></pre>
<p>This can make our <code>expect</code> function hard to maintain, and adding more properties would not be so trivial. In order to make this more maintainable (and extensible) we’re going to handle this a bit differently.</p>
<h2 id="heading-defining-properties">Defining properties</h2>
<p>Instead of proxying an empty object, we will proxy an object that contains the properties we want to have meaning.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> properties = {
  <span class="hljs-comment">// ...</span>
};

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">expect</span>(<span class="hljs-params">value</span>) </span>{
  <span class="hljs-keyword">const</span> proxy = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Proxy</span>(properties, {
    get(target, outerProp) {
      <span class="hljs-comment">// `target` is our `properties` object</span>
      <span class="hljs-built_in">console</span>.log(target);
      <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Proxy</span>(<span class="hljs-function">() =&gt;</span> proxy, {
        get(_, innerProp) {
          <span class="hljs-keyword">return</span> proxy[innerProp];
        },
      });
    },
  });
  <span class="hljs-keyword">return</span> proxy;
}
</code></pre>
<p>I decided to define each property as an object that contains two functions: <code>onAccess</code> to be executed on property access, and <code>onCall</code> to be executed when calling the property as a function. For example, our property for <code>null</code> could look like:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> isNull = {
  onAccess(actual) {
    <span class="hljs-keyword">if</span> (actual !== <span class="hljs-literal">null</span>) {
      <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'Expected value to be null'</span>);
    }
  },
};
</code></pre>
<p>We can also define a property to check if two values are strictly equal:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> isEqual = {
  onCall(actual, expected) {
    <span class="hljs-keyword">if</span> (actual !== expected) {
      <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'Expected values to be strictly equal'</span>);
    }
  },
};
</code></pre>
<p>Then we can modify our <code>expect</code> function to call them when they’re accessed:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// We add the previously defined properties to</span>
<span class="hljs-comment">// our `properties` object</span>
<span class="hljs-keyword">const</span> properties = {
  <span class="hljs-attr">null</span>: isNull,
  <span class="hljs-attr">equal</span>: isEqual,
};

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">expect</span>(<span class="hljs-params">value</span>) </span>{
  <span class="hljs-keyword">const</span> proxy = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Proxy</span>(properties, {
    get(target, outerProp) {
      <span class="hljs-keyword">const</span> property = target[outerProp];
        <span class="hljs-comment">// We execute the `onAccess` handler when one is found</span>
      property?.onAccess?.(value);
      <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Proxy</span>(
        <span class="hljs-function">(<span class="hljs-params">...args</span>) =&gt;</span> {
            <span class="hljs-comment">// We execute the `onCall` handler when one is found</span>
          property?.onCall?.(value, ...args);
          <span class="hljs-keyword">return</span> proxy;
        },
        {
          get(_, innerProp) {
            <span class="hljs-keyword">return</span> proxy[innerProp];
          },
        }
      );
    },
  });
  <span class="hljs-keyword">return</span> proxy;
}

expect(<span class="hljs-literal">null</span>).to.be.null;
expect(<span class="hljs-string">'a'</span>).to.equal(<span class="hljs-string">'a'</span>);
</code></pre>
<p>We suddenly have a really basic assertion library! And it can be easily extended by adding properties to our <code>properties</code> object!</p>
<p>There’s one thing we’re still not able to do with our current implementation: negate assertions. We need a way to modify the behaviour of future assertions.</p>
<h2 id="heading-negating-assertions">Negating assertions</h2>
<p>In order to be able to achieve this, we need a way to communicate to our properties that the current assertions is being negated. For this we’re going to change a bit how we define our properties. Instead of expecting the <code>actual</code> value being validated as first argument, we’re going to receive a <code>context</code> object that will contain our <code>actual</code> value and a new <code>negated</code> property that will be a boolean indicating if the assertion is being negated. Our new properties for <code>equal</code> and <code>null</code> will then look like this:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> isNull = {
  onAccess(context) {
    <span class="hljs-keyword">if</span> (!context.negated &amp;&amp; context.actual !== <span class="hljs-literal">null</span>) {
      <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'Expected value to be null'</span>);
    }
    <span class="hljs-keyword">if</span> (context.negated &amp;&amp; context.actual === <span class="hljs-literal">null</span>) {
      <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'Expected value not to be null'</span>);
    }
  },
};

<span class="hljs-keyword">const</span> isEqual = {
  onCall(context, expected) {
    <span class="hljs-keyword">if</span> (!context.negated &amp;&amp; context.actual !== expected) {
      <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'Expected values to be strictly equal'</span>);
    }
    <span class="hljs-keyword">if</span> (context.negated &amp;&amp; context.actual === expected) {
      <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'Expected values not to be strictly equal'</span>);
    }
  },
};
</code></pre>
<p>And we can add a new property to negate our assertions:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> isNot = {
  onAccess(context) {
    <span class="hljs-comment">// We set `negated` to true so future assertions</span>
    <span class="hljs-comment">// will have knowledge of it.</span>
    context.negated = <span class="hljs-literal">true</span>;
  },
};
</code></pre>
<p>Then our expect function will call each handler with a <code>context</code> object instead of the actual value:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> properties = {
  <span class="hljs-attr">null</span>: isNull,
  <span class="hljs-attr">equal</span>: isEqual,
  <span class="hljs-attr">not</span>: isNot,
};

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">expect</span>(<span class="hljs-params">value</span>) </span>{
  <span class="hljs-comment">// Our context object</span>
  <span class="hljs-keyword">const</span> context = {
    <span class="hljs-attr">actual</span>: value,
    <span class="hljs-attr">negated</span>: <span class="hljs-literal">false</span>,
  };
  <span class="hljs-keyword">const</span> proxy = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Proxy</span>(properties, {
    get(target, outerProp) {
      <span class="hljs-keyword">const</span> property = target[outerProp];
      property?.onAccess?.(context);
      <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Proxy</span>(
        <span class="hljs-function">(<span class="hljs-params">...args</span>) =&gt;</span> {
          property?.onCall?.(context, ...args);
          <span class="hljs-keyword">return</span> proxy;
        },
        {
          get(_, innerProp) {
            <span class="hljs-keyword">return</span> proxy[innerProp];
          },
        }
      );
    },
  });
  <span class="hljs-keyword">return</span> proxy;
}

expect(<span class="hljs-string">'a'</span>).to.not.equal(<span class="hljs-string">'b'</span>);
</code></pre>
<p>This technique can be used to communicate more details about our assertions to future assertions.</p>
<h2 id="heading-do-not-throw-normal-errors">Do not throw normal Errors</h2>
<p>To make examples simpler, we throw normal errors (<code>throw new Error(…)</code>). Since this is to be used with a test runner, it’d be better to throw something like Node’s built-in <a target="_blank" href="https://nodejs.org/api/assert.html#class-assertassertionerror"><code>AssertionError</code></a> or, in the case of uvu, its own <code>Assertion</code> error. These would give way more information when assertions fail. And it can be picked by Node or test runners to show prettier messages and diffs!</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>This is a simplified explanation of how I made <a target="_blank" href="https://github.com/pablo-abc/uvu-expect">uvu-expect</a>. <code>uvu-expect</code> has way more features and validations such as:</p>
<ul>
<li><code>.resolves</code> and <code>.rejects</code> to assert on promises</li>
<li>Possibility to create plugins for it using an <code>extend</code> function. This is how I also created a plugin for it called <a target="_blank" href="https://github.com/pablo-abc/uvu-expect-dom">uvu-expect-dom</a> which offers similar validations to <code>@testing-library/jest-dom</code>.</li>
<li>Assertions on mock functions (compatible with <a target="_blank" href="https://sinonjs.org">sinonjs</a> and <a target="_blank" href="https://github.com/Aslemammad/tinyspy">tinyspy</a>).</li>
</ul>
<p>I aimed for it to have at least the features I used of Jest’s <code>expect</code>. You can read more about its features on its README! I documented everything about it there. Even how to create your own plugins for it.</p>
<p>It was a really fun side-project to build and explain. And it’s been working really well with our <a target="_blank" href="https://github.com/pablo-abc/felte/blob/main/packages/common/tests/utils.spec.ts">tests on Felte</a>.</p>
]]></content:encoded></item><item><title><![CDATA[Announcing Felte 1.0: A form library for Svelte, Solid and React]]></title><description><![CDATA[After more than a year of work, I am proud to announce the release of version 1.0.0 of Felte!
Felte is an extensible form management library for Svelte, Solid and (as of today) React. The main characteristic of it is that it does not require any sort...]]></description><link>https://hn.berganza.dev/announcing-felte-10-a-form-library-for-svelte-solid-and-react</link><guid isPermaLink="true">https://hn.berganza.dev/announcing-felte-10-a-form-library-for-svelte-solid-and-react</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[forms]]></category><dc:creator><![CDATA[Pablo Berganza]]></dc:creator><pubDate>Mon, 14 Feb 2022 03:08:34 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1645240024032/qV_V8kYaX.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>After more than a year of work, I am proud to announce the release of version 1.0.0 of <a target="_blank" href="https://felte.dev">Felte</a>!</p>
<p>Felte is an extensible form management library for <a target="_blank" href="https://svelte.dev">Svelte</a>, <a target="_blank" href="https://solidjs.com">Solid</a> and (as of today) <a target="_blank" href="https://reactjs.org">React</a>. The main characteristic of it is that it does not require any sort of  <code>Field</code> or <code>Form</code> components to work. In its most basic form it only requires you to provide it your HTML form element so it can subscribe to your user’s interaction with your form.</p>
<p>I originally started working on Felte wanting a form library for Svelte that would not make it complex to style my input components. As I worked more on it, it began growing into a much more flexible package that allowed you to validate your form using <a target="_blank" href="https://felte.dev/docs/svelte/validators">your preferred validation library</a> and <a target="_blank" href="https://felte.dev/docs/svelte/reporters">display your validation messages as you preferred</a>.  After the release of version 1.0.0 of SolidJS, I released a <a target="_blank" href="https://www.npmjs.com/package/@felte/solid">version for it as well</a> that shares most of its internals with the original <a target="_blank" href="https://www.npmjs.com/package/felte">Felte package</a>. And now, more than a year after the first commit, version 1.0.0 has been released alongside <a target="_blank" href="https://www.npmjs.com/package/@felte/react">a new version for React</a>! This includes many improvements in the core API, new features and a more consistent API between all three versions.</p>
<h2 id="heading-usage">Usage</h2>
<p>All three versions of Felte have a very similar API, and a similar concept: you call a function to set up your form. Then you give Felte a reference to your HTML form element. The variations in its API come mostly from how each framework handles reactivity. For example, with Svelte, Felte returns stores that contain your data which you can access by prefixing the stores with <code>$</code>. With Solid and React it returns functions that will let you subscribe to all of your form’s data or specific values of it.</p>
<p>On its most basic form, you only need to use <code>form</code>, a property returned from Felte that will let it subscribe to all interactions that happen in your form.</p>
<p>Here’s a basic example of how each version looks like:</p>
<h3 id="heading-svelte">Svelte</h3>
<p>The package for Svelte is available on npm as <a target="_blank" href="https://www.npmjs.com/package/felte"><code>felte</code></a>.</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
  <span class="hljs-keyword">import</span> { createForm } <span class="hljs-keyword">from</span> <span class="hljs-string">'felte'</span>

  <span class="hljs-keyword">const</span> { form } = createForm({
    <span class="hljs-attr">onSubmit</span>: <span class="hljs-keyword">async</span> (values) =&gt; {
      <span class="hljs-comment">/* call to an api */</span>
    },
  })
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>

<span class="hljs-comment">&lt;!-- `form` is an action --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">use:form</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">text</span> <span class="hljs-attr">name</span>=<span class="hljs-string">email</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">password</span> <span class="hljs-attr">name</span>=<span class="hljs-string">password</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">type</span>=<span class="hljs-string">submit</span>&gt;</span>Sign In<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span>
</code></pre>
<h3 id="heading-solid">Solid</h3>
<p>The package for Solid is available on npm as <a target="_blank" href="https://www.npmjs.com/package/@felte/solid"><code>@felte/solid</code></a>.</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">import</span> { createForm } <span class="hljs-keyword">from</span> <span class="hljs-string">'@felte/solid'</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Form</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> { form } = createForm({
    <span class="hljs-attr">onSubmit</span>: <span class="hljs-keyword">async</span> (values) =&gt; {
      <span class="hljs-comment">/* call to an api */</span>
    },
  })

    <span class="hljs-comment">// `form` is an action</span>
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">use:form</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"email"</span> /&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"password"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"password"</span> /&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"submit"</span>&gt;</span>Sign In<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span></span>
  );
}
</code></pre>
<h3 id="heading-react">React</h3>
<p>The package for React is available on npm as <a target="_blank" href="https://www.npmjs.com/package/@felte/react"><code>@felte/react</code></a>.</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">import</span> { useForm } <span class="hljs-keyword">from</span> <span class="hljs-string">'@felte/react'</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Form</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> { form } = useForm({
    <span class="hljs-attr">onSubmit</span>: <span class="hljs-keyword">async</span> (values) =&gt; {
      <span class="hljs-comment">/* call to an api */</span>
    },
  })

    <span class="hljs-comment">// `form` is a ref</span>
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">ref</span>=<span class="hljs-string">{form}</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"email"</span> /&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"password"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"password"</span> /&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"submit"</span>&gt;</span>Sign In<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span></span>
  );
}
</code></pre>
<h2 id="heading-new-features">New features</h2>
<p>Version 1 comes with a lot of improvements and features:</p>
<ul>
<li>Debounced validation is now supported. Previously we only supported asynchronous validation, but offered no way to debounce them. This meant using Felte’s validation for calls to an API would not be recommended unless you debounced it yourself, or did them only on submission.</li>
<li>Asynchronous and debounced validations might apply to only a few fields. Showing loaders for the field’s that are validating is a nice feature to have for your users. This is why Felte now offer’s a way to check if validations are currently running via the new <code>isValidating</code> store. And a way to check which is the last field your users interact with using the new <code>interacted</code> store.</li>
<li>Using custom form controls was not so straightforward. Requiring to use helper functions to update your stores. Felte now exports a new function: <code>createField</code> (<code>useField</code> for React) to be used with custom fields where you can’t directly provide a <code>name</code>, or with fields that don’t use native HTML controls at all (such as a <code>contenteditable</code> elements). It helps you make your custom fields “discoverable” to Felte.</li>
<li>Better support for field arrays with new helper functions to handle them: <code>addField</code>, <code>unsetField</code>, <code>moveField</code> and <code>swapFields</code>.</li>
<li>You no longer always need to have an <code>onSubmit</code> handler. If no <code>onSubmit</code> is declared, Felte will submit your values as either <code>application/x-www-form-urlencoded</code> or <code>multipart/form-data</code> using the <code>action</code>, <code>method</code> and <code>enctype</code> attributes of your <code>&lt;form&gt;</code> element.</li>
</ul>
<h2 id="heading-breaking-changes">Breaking changes</h2>
<p>This being a major version release, there are some breaking changes. If you were previously using Felte v0.x, you can check <a target="_blank" href="https://felte.dev/docs/svelte/migrating">the migration guide for Svelte</a>, or <a target="_blank" href="https://felte.dev/docs/solid/migrating">the migration guide for Solid</a>.</p>
<h2 id="heading-read-more">Read more</h2>
<p>I’ve gone back to update my original introductory posts about Felte, as well as added a new one about React to the series. You can check them out here:</p>
<ul>
<li><a target="_blank" href="https://hashnode.com/post/felte-an-extensible-form-library-for-svelte-ckzt7pyps01h99ss1cs049klk">Felte: an extensible form library for Svelte</a></li>
<li><a target="_blank" href="https://hashnode.com/post/felte-an-extensible-form-library-for-solid-ckzt88dws01ffd4s1ektk2y2s">Felte: an extensible form library for Solid</a></li>
<li><a target="_blank" href="https://hashnode.com/post/felte-an-extensible-form-library-for-react-ckzt8bqh801fqd4s1188z2ydb">Felte: an extensible form library for React</a></li>
</ul>
<h2 id="heading-final-words">Final words</h2>
<p>I’ve put a lot of work on this project, and I’m really grateful to the contributors that helped Felte grow as much as it did! I hope that this release can be useful to all of you!</p>
]]></content:encoded></item><item><title><![CDATA[Felte: an extensible form library for React]]></title><description><![CDATA[Arguably one of the most common problems front-end developers need to solve is form handling. Specially in modern web applications that require instant validation and other real-time interactions with the user.  To provide the best user experience as...]]></description><link>https://hn.berganza.dev/felte-an-extensible-form-library-for-react</link><guid isPermaLink="true">https://hn.berganza.dev/felte-an-extensible-form-library-for-react</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[forms]]></category><category><![CDATA[React]]></category><dc:creator><![CDATA[Pablo Berganza]]></dc:creator><pubDate>Mon, 14 Feb 2022 02:34:49 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1645238067201/CwqfsnIES.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Arguably one of the most common problems front-end developers need to solve is form handling. Specially in modern web applications that require instant validation and other real-time interactions with the user.  To provide the best user experience as possible, you’ll probably grab a third party form management library to help you.</p>
<p>In this post I am going to write about <a target="_blank" href="https://felte.dev">Felte</a>, a form management library for React I have been working on for the past year that aims to make the basics of form handling on the front-end as simple as possible, while still allowing for it to grow more complex as your requirements grow.</p>
<p>This is one of three blog posts related to Felte. This one is oriented towards Felte’s integration with <a target="_blank" href="https://reactjs.org">React</a>. The other two are oriented towards Felte’s integration with <a target="_blank" href="https://svelte.dev">Svelte</a> and <a target="_blank" href="https://solidjs.com">Solid</a>.</p>
<h2 id="heading-features">Features</h2>
<p>As mentioned above, Felte aims to make the basics of form reactivity as easy to handle as possible, while still allowing for more complex behaviours via configuration and extensibility. Its main features are:</p>
<ul>
<li>Single action to make your form reactive.</li>
<li>Use HTML5 native elements to create your form. (Only the <code>name</code> attribute is necessary).</li>
<li>Minimal re-renders. None if you don't need your form's data in your component.</li>
<li>Provides stores and helper functions to handle more complex use cases.</li>
<li>No assumptions on your validation strategy. Use any validation library you want or write your own strategy.</li>
<li>Handles addition and removal of form controls during runtime.</li>
<li>Official solutions for error reporting using <code>reporter</code> packages.</li>
<li>Supports validation with <a target="_blank" href="https://felte.dev/docs/react/validators#using-yup">yup</a>, <a target="_blank" href="https://felte.dev/docs/react/validators#using-zod">zod</a>, <a target="_blank" href="https://felte.dev/docs/react/validators#using-superstruct">superstruct</a> and <a target="_blank" href="https://felte.dev/docs/react/validators#using-vest">vest</a>.</li>
<li>Easily <a target="_blank" href="https://felte.dev/docs/react/extending-felte">extend its functionality</a>.</li>
</ul>
<h2 id="heading-how-does-it-look-like">How does it look like?</h2>
<p>In its most basic form, Felte only requires a single function to be imported:</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">import</span> { useForm } <span class="hljs-keyword">from</span> <span class="hljs-string">'@felte/react'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Form</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> { form } = useForm({
    <span class="hljs-attr">onSubmit</span>: <span class="hljs-function">(<span class="hljs-params">values</span>) =&gt;</span> {
      <span class="hljs-comment">// ...</span>
    },
  });

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">ref</span>=<span class="hljs-string">{form}</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"email"</span> /&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"password"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"password"</span> /&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"submit"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"Sign in"</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span></span>
  );
}
</code></pre>
<p>We set up the form by calling <code>useForm</code> with our <code>submit</code> handler. This function returns, among other utilities, an action that can be used on your form element. Now Felte will track all inputs with a <code>name</code> attribute. When submitting your form, the latest values in your inputs will be passed to your <code>onSubmit</code> function as an object. For our previous example, the shape of <code>values</code> will be:</p>
<pre><code class="lang-javascript">{
  <span class="hljs-attr">email</span>: <span class="hljs-string">''</span>,
  <span class="hljs-attr">password</span>: <span class="hljs-string">''</span>,
}
</code></pre>
<h2 id="heading-where-can-i-see-my-data">Where can I see my data?</h2>
<p>As you type, Felte will keep track of your user’s input in an observable that contains your form data in the same shape as the values you'd receive on your <code>onSubmit</code>. This observable is handled by Felte and its value can be obtained by calling the function <code>data</code> returned from <code>useForm</code>; no need to handle observables by yourself! We'll refer to these functions as <code>accessors</code> from now on. When this accessor is called without any arguments (<code>data()</code>), it returns all of the form's data as an object. This also "subscribes" your component to every change on your form, triggering a re-render everytime a value changes. An argument can be passed as first argument to select a specific field, either a selector function or a string path. By using an argument, your component will only "subscribe" to changes made to the specific value you've selected.</p>
<p>For example, this would log your user’s email to the console as they type it:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Within a component</span>
<span class="hljs-keyword">const</span> { form, data } = useForm({ <span class="hljs-comment">/* ... */</span> });

<span class="hljs-comment">// Passing a function as first argument</span>
<span class="hljs-built_in">console</span>.log(data(<span class="hljs-function">(<span class="hljs-params">$data</span>) =&gt;</span> $data.email));

<span class="hljs-comment">// Passing a string as first argument</span>
<span class="hljs-built_in">console</span>.log(data(<span class="hljs-string">'email'</span>));
</code></pre>
<blockquote>
<p>Accessors are <strong>NOT</strong> hooks, this means they can be called from wherever you need them in your code.</p>
</blockquote>
<h2 id="heading-i-might-need-some-validation-here">I might need some validation here</h2>
<p>Of course, another common requirement of forms is validation. If we want our app to feel snappy to the user, we will want some client side validation. <code>useForm</code>’s configuration object accepts a <code>validate</code> function (which can be asynchronous). It will receive the current value of your <code>data</code> as it changes, and it expects you to return an object with the same shape, containing your validation messages if the form is not valid, or nothing if your form is valid. Felte will keep track of these validation messages on an accessor that is returned from <code>useForm</code> as <code>errors</code>:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> { form, errors } = useForm({
  validate(values) {
    <span class="hljs-keyword">const</span> currentErrors = {};
    <span class="hljs-keyword">if</span> (!values.email) currentErrors.email = <span class="hljs-string">'Must not be empty'</span>;
    <span class="hljs-keyword">if</span> (!values.password) currentErrors.password = <span class="hljs-string">'Must not be empty'</span>;
    <span class="hljs-keyword">return</span> currentErrors;
  },
});

<span class="hljs-built_in">console</span>.log(errors(<span class="hljs-function">(<span class="hljs-params">$errors</span>) =&gt;</span> $errors.email));
</code></pre>
<p>More complex validation requirements might require third party validation libraries. Felte offers first party integrations  with some popular validation libraries through its extensibility features. These integrations are offered as separate packages. I will write write more about this in the next section regarding extensibility, but you can read more about these packages in our <a target="_blank" href="https://felte.dev/docs/react/validators">official documentation</a>.</p>
<h2 id="heading-handling-complex-scenarios-via-extensibility">Handling complex scenarios via extensibility</h2>
<p>Felte does not attempt to have the perfect solution on how to handle all scenarios regarding form management. This is why Felte offers an API to extend its functionality as your requirements grow more complex. You may have a preferred library you like to use, such as the really popular <a target="_blank" href="https://github.com/jquense/yup">yup</a>, or <a target="_blank" href="https://vestjs.dev/">Vest</a> (which was recently talked about during <a target="_blank" href="https://www.youtube.com/watch?v=X2PuiawaGV4">Svelte Summit</a>). Modifying Felte’s behaviour to handle these scenarios can be done via the <code>extend</code> option on <code>useForm</code>’s configuration object. More about this can be read in the <a target="_blank" href="https://felte.dev/docs/react/extending-felte">official documentation</a>. To keep things simple for the purposes of this blog post, I am only going to write about some of the existing packages we maintain to handle some common use cases:</p>
<h3 id="heading-validators-integrations-with-popular-validation-libraries">Validators: Integrations with popular validation libraries</h3>
<p>We are currently maintaining four packages to integrate Felte with some popular validation libraries: <code>yup</code>, <code>zod</code>, <code>superstruct</code> and most recently <code>vest</code>. Here we will use yup as an example, but you can read more about the rest <a target="_blank" href="https://felte.dev/docs/react/validators">here</a>.</p>
<p>The package to use <code>yup</code> is on npm under the name <code>@felte/validator-yup</code>. You will need to install it alongside <code>yup</code>:</p>
<pre><code class="lang-bash">npm install --save @felte/validator-yup yup

<span class="hljs-comment"># Or, if you use yarn</span>

yarn add @felte/validator-yup yup
</code></pre>
<p>This validator package exports a function called <code>validator</code> which you can call with your validation schema and pass its result to the <code>extend</code> option of <code>useForm</code>:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { validator } <span class="hljs-keyword">from</span> <span class="hljs-string">'@felte/validator-yup'</span>;
<span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> yup <span class="hljs-keyword">from</span> <span class="hljs-string">'yup'</span>;

<span class="hljs-keyword">const</span> schema = yup.object({
  <span class="hljs-attr">email</span>: yup.string().email().required(),
  <span class="hljs-attr">password</span>: yup.string().required(),
});

<span class="hljs-keyword">const</span> { form } = useForm({
  <span class="hljs-comment">// ...</span>
  <span class="hljs-attr">extend</span>: validator({ schema }), <span class="hljs-comment">// OR `extend: [validator({ schema })],`</span>
  <span class="hljs-comment">// ...</span>
});
</code></pre>
<h3 id="heading-reporters-displaying-validation-messages">Reporters: Displaying validation messages</h3>
<p>Displaying your validation messages can be done by directly using the <code>errors</code> accessor returned by <code>useForm</code>. Messages won’t be available on this accessor until the related field is interacted with.</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">import</span> { useForm } <span class="hljs-keyword">from</span> <span class="hljs-string">'@felte/react'</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Form</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> { form, errors } = useForm({ <span class="hljs-comment">/* ... */</span> });

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">ref</span>=<span class="hljs-string">{form}</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">htmlFor</span>=<span class="hljs-string">"email"</span>&gt;</span>Email:<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"email"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"email"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"email"</span> /&gt;</span>
      {!!errors('email') &amp;&amp; (
        <span class="hljs-tag">&lt;<span class="hljs-name">span</span>&gt;</span>{errors('email')}<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
      )}
      <span class="hljs-tag">&lt;<span class="hljs-name">button</span>&gt;</span>Submit<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span></span>
  );
}
</code></pre>
<blockquote>
<p>If a specific field has an error, Felte assigns an <code>aria-invalid=true</code> attribute to the appropriate input.</p>
</blockquote>
<p>But you might not like that specific syntax to handle your validation messages. Felte currently also has four accompanying packages that offer different alternatives on how to display your validation messages:</p>
<ul>
<li>Using a React component, which gives the most flexibility and would allow you to have access to your validation messages deep within the component tree without needing to pass the <code>errors</code> accessor around.</li>
<li>Modifying the DOM directly by adding and removing DOM elements.</li>
<li>Using Tippy.js to display your messages  in a tooltip.</li>
<li>Using the browser’s built-in constraint validation API, which can be less friendly to mobile users.</li>
</ul>
<p>For brevity, I am only going to cover the first package. But you can read more about the rest <a target="_blank" href="https://felte.dev/docs/react/reporters">in the documentation</a>.</p>
<p>Using a React component to get your validation messages can be done with the package <code>@felte/reporter-react</code>.  You’ll need to add it to your project using your favourite package manager:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># npm</span>
npm i -S @felte/reporter-react

<span class="hljs-comment"># yarn</span>
yarn add @felte/reporter-react
</code></pre>
<p>Then you’ll need to import both the <code>reporter</code> function to add to the <code>extend</code> property, and the <code>ValidationMessage</code> component which you will use to receive your validation messages:</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">import</span> { reporter, ValidationMessage } <span class="hljs-keyword">from</span> <span class="hljs-string">'@felte/reporter-react'</span>;
<span class="hljs-keyword">import</span> { useForm } <span class="hljs-keyword">from</span> <span class="hljs-string">'@felte/react'</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Form</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> { form } = useForm({
      <span class="hljs-comment">// ...</span>
      <span class="hljs-attr">extend</span>: reporter, <span class="hljs-comment">// or [reporter]</span>
      <span class="hljs-comment">// ...</span>
    },
  })

 <span class="hljs-comment">// We assume a single string will be passed as a validation message</span>
 <span class="hljs-comment">// This can be an array of strings depending on your validation strategy</span>
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">ref</span>=<span class="hljs-string">{form}</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"email"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"email"</span> /&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">ValidationMessage</span> <span class="hljs-attr">for</span>=<span class="hljs-string">"email"</span>&gt;</span>
        {(message) =&gt; <span class="hljs-tag">&lt;<span class="hljs-name">span</span>&gt;</span>{message}<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>}
      <span class="hljs-tag">&lt;/<span class="hljs-name">ValidationMessage</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"password"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"password"</span> /&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">ValidationMessage</span> <span class="hljs-attr">for</span>=<span class="hljs-string">"password"</span>&gt;</span>
        {(message) =&gt; <span class="hljs-tag">&lt;<span class="hljs-name">span</span>&gt;</span>{message}<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>}
      <span class="hljs-tag">&lt;/<span class="hljs-name">ValidationMessage</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"submit"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"Sign in"</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span></span>
  );
}
</code></pre>
<h2 id="heading-next-steps">Next steps</h2>
<p>You can check more about Felte in its <a target="_blank" href="https://felte.dev">official website</a> with some functional examples. There’s also a more complex example showcasing its usage with Tippy.js and Yup available on <a target="_blank" href="https://codesandbox.io/s/felte-react-demo-q2xxw?file=/src/App.js">CodeSandbox</a>.</p>
<h2 id="heading-finishing-thoughts">Finishing thoughts</h2>
<p>I hope this served as a good introduction to Felte, and that it is interesting enough for you to give it a try. Felte is already on a stable state and used by some people.  I am also open to help and suggestions so feel free to open an issue or make a pull request on <a target="_blank" href="https://github.com/pablo-abc/felte">GitHub</a>.</p>
]]></content:encoded></item><item><title><![CDATA[Felte: an extensible form library for Solid]]></title><description><![CDATA[Arguably one of the most common problems front-end developers need to solve is form handling. Specially in modern web applications that require instant validation and other real-time interactions with the user.  To provide the best user experience as...]]></description><link>https://hn.berganza.dev/felte-an-extensible-form-library-for-solid</link><guid isPermaLink="true">https://hn.berganza.dev/felte-an-extensible-form-library-for-solid</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[forms]]></category><category><![CDATA[solid]]></category><category><![CDATA[Web Development]]></category><dc:creator><![CDATA[Pablo Berganza]]></dc:creator><pubDate>Wed, 08 Dec 2021 02:31:52 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1645237887480/TGhTkhqnS.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Arguably one of the most common problems front-end developers need to solve is form handling. Specially in modern web applications that require instant validation and other real-time interactions with the user.  To provide the best user experience as possible, you’ll probably grab a third party form management library to help you.</p>
<p>In this post I am going to write about <a target="_blank" href="https://felte.dev">Felte</a>, a form management library for Solid I have been working on for the past year that aims to make the basics of form handling on the front-end as simple as possible, while still allowing for it to grow more complex as your requirements grow.</p>
<p>This is one of three blog posts related to Felte. This one is oriented towards Felte’s integration with <a target="_blank" href="https://solidjs.com">Solid</a>. The other two are oriented towards Felte’s integration with <a target="_blank" href="https://svelte.dev">Svelte</a> and <a target="_blank" href="https://reactjs.org">React</a>.</p>
<h2 id="heading-features">Features</h2>
<p>As mentioned above, Felte aims to make the basics of form reactivity as easy to handle as possible, while still allowing for more complex behaviours via configuration and extensibility. Its main features are:</p>
<ul>
<li>Single action to make your form reactive.</li>
<li>Use HTML5 native elements to create your form. (Only the <code>name</code> attribute is necessary).</li>
<li>Provides stores and helper functions to handle more complex use cases.</li>
<li>No assumptions on your validation strategy. Use any validation library you want or write your own strategy.</li>
<li>Handles addition and removal of form controls during runtime.</li>
<li>Official solutions for error reporting using <code>reporter</code> packages.</li>
<li>Supports validation with <a target="_blank" href="https://felte.dev/docs/solid/validators#using-yup">yup</a>, <a target="_blank" href="https://felte.dev/docs/solid/validators#using-zod">zod</a>, <a target="_blank" href="https://felte.dev/docs/solid/validators#using-superstruct">superstruct</a> and <a target="_blank" href="https://felte.dev/docs/solid/validators#using-vest">vest</a>.</li>
<li>Easily <a target="_blank" href="https://felte.dev/docs/solid/extending-felte">extend its functionality</a>.</li>
</ul>
<h2 id="heading-how-does-it-look-like">How does it look like?</h2>
<p>In its most basic form, Felte only requires a single function to be imported:</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">import</span> { createForm } <span class="hljs-keyword">from</span> <span class="hljs-string">'@felte/solid'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Form</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> { form } = createForm({
    <span class="hljs-attr">onSubmit</span>: <span class="hljs-function">(<span class="hljs-params">values</span>) =&gt;</span> {
      <span class="hljs-comment">// ...</span>
    },
  });

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">use:form</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"email"</span> /&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"password"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"password"</span> /&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"submit"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"Sign in"</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span></span>
  );
}
</code></pre>
<p>We set up the form by calling <code>createForm</code> with our <code>submit</code> handler. This function returns, among other utilities, an action that can be used on your form element. Now Felte will track all inputs with a <code>name</code> attribute. When submitting your form, the latest values in your inputs will be passed to your <code>onSubmit</code> function as an object. For our previous example, the shape of <code>values</code> will be:</p>
<pre><code class="lang-javascript">{
  <span class="hljs-attr">email</span>: <span class="hljs-string">''</span>,
  <span class="hljs-attr">password</span>: <span class="hljs-string">''</span>,
}
</code></pre>
<h2 id="heading-where-can-i-see-my-data">Where can I see my data?</h2>
<p>As you type, Felte will keep track of your user’s input in a signal that contains your form data in the same shape as the values you'd receive on your <code>onSubmit</code>. This signal is wrapped, allowing some extra functionality, and is returned by <code>createForm</code> as function called <code>data</code>. We'll refer to these functions as <code>accessors</code> from now on. When this accessor is called without any arguments (<code>data()</code>), it behaves like a regular signal that returns all of the form's data as an object. An argument can be passed as first argument to select a specific field, either a selector function or a string path.</p>
<p>For example, this would log your user’s email to the console as they type it:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Within a component</span>
<span class="hljs-keyword">const</span> { form, data } = createForm({ <span class="hljs-comment">/* ... */</span> });

createEffect(<span class="hljs-function">() =&gt;</span> {
  <span class="hljs-comment">// Passing a function as first argument</span>
  <span class="hljs-built_in">console</span>.log(data(<span class="hljs-function">(<span class="hljs-params">$data</span>) =&gt;</span> $data.email));

  <span class="hljs-comment">// Passing a string as first argument</span>
  <span class="hljs-built_in">console</span>.log(data(<span class="hljs-string">'email'</span>));
});
</code></pre>
<h2 id="heading-i-might-need-some-validation-here">I might need some validation here</h2>
<p>Of course, another common requirement of forms is validation. If we want our app to feel snappy to the user, we will want some client side validation. <code>createForm</code>’s configuration object accepts a <code>validate</code> function (which can be asynchronous). It will receive the current value of your <code>data</code> as it changes, and it expects you to return an object with the same shape, containing your validation messages if the form is not valid, or nothing if your form is valid. Felte will keep track of these validation messages on an accessor that is returned from <code>createForm</code> as <code>errors</code>:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> { form, errors } = createForm({
  validate(values) {
    <span class="hljs-keyword">const</span> currentErrors = {};
    <span class="hljs-keyword">if</span> (!values.email) currentErrors.email = <span class="hljs-string">'Must not be empty'</span>;
    <span class="hljs-keyword">if</span> (!values.password) currentErrors.password = <span class="hljs-string">'Must not be empty'</span>;
    <span class="hljs-keyword">return</span> currentErrors;
  },
});

createEffect(<span class="hljs-function">() =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(errors(<span class="hljs-function">(<span class="hljs-params">$errors</span>) =&gt;</span> $errors.email));
});
</code></pre>
<p>More complex validation requirements might require third party validation libraries. Felte offers first party integrations  with some popular validation libraries through its extensibility features. These integrations are offered as separate packages. I will write write more about this in the next section regarding extensibility, but you can read more about these packages in our <a target="_blank" href="https://felte.dev/docs/solid/validators">official documentation</a>.</p>
<h2 id="heading-handling-complex-scenarios-via-extensibility">Handling complex scenarios via extensibility</h2>
<p>Felte does not attempt to have the perfect solution on how to handle all scenarios regarding form management. This is why Felte offers an API to extend its functionality as your requirements grow more complex. You may have a preferred library you like to use, such as the really popular <a target="_blank" href="https://github.com/jquense/yup">yup</a>, or <a target="_blank" href="https://vestjs.dev/">Vest</a> (which was recently talked about during <a target="_blank" href="https://www.youtube.com/watch?v=X2PuiawaGV4">Svelte Summit</a>). Modifying Felte’s behaviour to handle these scenarios can be done via the <code>extend</code> option on <code>createForm</code>’s configuration object. More about this can be read in the <a target="_blank" href="https://felte.dev/docs/solid/extending-felte">official documentation</a>. To keep things simple for the purposes of this blog post, I am only going to write about some of the existing packages we maintain to handle some common use cases:</p>
<h3 id="heading-validators-integrations-with-popular-validation-libraries">Validators: Integrations with popular validation libraries</h3>
<p>We are currently maintaining four packages to integrate Felte with some popular validation libraries: <code>yup</code>, <code>zod</code>, <code>superstruct</code> and most recently <code>vest</code>. Here we will use yup as an example, but you can read more about the rest <a target="_blank" href="https://felte.dev/docs/solid/validators">here</a>.</p>
<p>The package to use <code>yup</code> is on npm under the name <code>@felte/validator-yup</code>. You will need to install it alongside <code>yup</code>:</p>
<pre><code class="lang-bash">npm install --save @felte/validator-yup yup

<span class="hljs-comment"># Or, if you use yarn</span>

yarn add @felte/validator-yup yup
</code></pre>
<p>This validator package exports a function called <code>validator</code> which you can call with your validation schema and pass its result to the <code>extend</code> option of <code>createForm</code>:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { validator } <span class="hljs-keyword">from</span> <span class="hljs-string">'@felte/validator-yup'</span>;
<span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> yup <span class="hljs-keyword">from</span> <span class="hljs-string">'yup'</span>;

<span class="hljs-keyword">const</span> schema = yup.object({
  <span class="hljs-attr">email</span>: yup.string().email().required(),
  <span class="hljs-attr">password</span>: yup.string().required(),
});

<span class="hljs-keyword">const</span> { form } = createForm({
  <span class="hljs-comment">// ...</span>
  <span class="hljs-attr">extend</span>: validator({ schema }), <span class="hljs-comment">// OR `extend: [validator({ schema })],`</span>
  <span class="hljs-comment">// ...</span>
});
</code></pre>
<h3 id="heading-reporters-displaying-validation-messages">Reporters: Displaying validation messages</h3>
<p>Displaying your validation messages can be done by directly using the <code>errors</code> accessor returned by <code>createForm</code>. Messages won’t be available on this accessor until the related field is interacted with.</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">import</span> { Show } <span class="hljs-keyword">from</span> <span class="hljs-string">'solid-js'</span>;
<span class="hljs-keyword">import</span> { createForm } <span class="hljs-keyword">from</span> <span class="hljs-string">'@felte/solid'</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Form</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> { form, errors } = createForm({ <span class="hljs-comment">/* ... */</span> });

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">use:form</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">for</span>=<span class="hljs-string">"email"</span>&gt;</span>Email:<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"email"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"email"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"email"</span> /&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Show</span> <span class="hljs-attr">when</span>=<span class="hljs-string">{errors(</span>'<span class="hljs-attr">email</span>')}&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">span</span>&gt;</span>{errors('email')}<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">Show</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">button</span>&gt;</span>Submit<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span></span>
  );
}
</code></pre>
<blockquote>
<p>If a specific field has an error, Felte assigns an <code>aria-invalid=true</code> attribute to the appropriate input.</p>
</blockquote>
<p>But you might not like that specific syntax to handle your validation messages. Felte currently also has four accompanying packages that offer different alternatives on how to display your validation messages:</p>
<ul>
<li>Using a Solid component, which gives the most flexibility and would allow you to have access to your validation messages deep within the component tree without needing to pass the <code>errors</code> accessor around.</li>
<li>Modifying the DOM directly by adding and removing DOM elements.</li>
<li>Using Tippy.js to display your messages  in a tooltip.</li>
<li>Using the browser’s built-in constraint validation API, which can be less friendly to mobile users.</li>
</ul>
<p>For brevity, I am only going to cover the first package. But you can read more about the rest <a target="_blank" href="https://felte.dev/docs/solid/reporters">in the documentation</a>.</p>
<p>Using a Solid component to get your validation messages can be done with the package <code>@felte/reporter-solid</code>.  You’ll need to add it to your project using your favourite package manager:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># npm</span>
npm i -S @felte/reporter-solid

<span class="hljs-comment"># yarn</span>
yarn add @felte/reporter-solid
</code></pre>
<p>Then you’ll need to import both the <code>reporter</code> function to add to the <code>extend</code> property, and the <code>ValidationMessage</code> component which you will use to receive your validation messages:</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">import</span> { reporter, ValidationMessage } <span class="hljs-keyword">from</span> <span class="hljs-string">'@felte/reporter-solid'</span>;
<span class="hljs-keyword">import</span> { createForm } <span class="hljs-keyword">from</span> <span class="hljs-string">'@felte/solid'</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Form</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> { form } = createForm({
      <span class="hljs-comment">// ...</span>
      <span class="hljs-attr">extend</span>: reporter, <span class="hljs-comment">// or [reporter]</span>
      <span class="hljs-comment">// ...</span>
    },
  })

 <span class="hljs-comment">// We assume a single string will be passed as a validation message</span>
 <span class="hljs-comment">// This can be an array of strings depending on your validation strategy</span>
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">use:form</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"email"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"email"</span> /&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">ValidationMessage</span> <span class="hljs-attr">for</span>=<span class="hljs-string">"email"</span>&gt;</span>
        {(message) =&gt; <span class="hljs-tag">&lt;<span class="hljs-name">span</span>&gt;</span>{message}<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>}
      <span class="hljs-tag">&lt;/<span class="hljs-name">ValidationMessage</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"password"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"password"</span> /&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">ValidationMessage</span> <span class="hljs-attr">for</span>=<span class="hljs-string">"password"</span>&gt;</span>
        {(message) =&gt; <span class="hljs-tag">&lt;<span class="hljs-name">span</span>&gt;</span>{message}<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>}
      <span class="hljs-tag">&lt;/<span class="hljs-name">ValidationMessage</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"submit"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"Sign in"</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span></span>
  );
}
</code></pre>
<h2 id="heading-next-steps">Next steps</h2>
<p>You can check more about Felte in its <a target="_blank" href="https://felte.dev">official website</a> with some functional examples. There’s also a more complex example showcasing its usage with Tippy.js and Yup available on <a target="_blank" href="https://codesandbox.io/s/felte-v1-demo-solidjs-rt0cm?file=/src/main.tsx">CodeSandbox</a>.</p>
<h2 id="heading-finishing-thoughts">Finishing thoughts</h2>
<p>I hope this served as a good introduction to Felte, and that it is interesting enough for you to give it a try. Felte is already on a stable state and used by some people.  I am also open to help and suggestions so feel free to open an issue or make a pull request on <a target="_blank" href="https://github.com/pablo-abc/felte">GitHub</a>.</p>
]]></content:encoded></item><item><title><![CDATA[Felte: an extensible form library for Svelte]]></title><description><![CDATA[Arguably one of the most common problems front-end developers need to solve is form handling. Specially in modern web applications that require instant validation and other real-time interactions with the user.  To provide the best user experience as...]]></description><link>https://hn.berganza.dev/felte-an-extensible-form-library-for-svelte</link><guid isPermaLink="true">https://hn.berganza.dev/felte-an-extensible-form-library-for-svelte</guid><category><![CDATA[Svelte]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[forms]]></category><category><![CDATA[Web Development]]></category><dc:creator><![CDATA[Pablo Berganza]]></dc:creator><pubDate>Wed, 08 Dec 2021 02:16:17 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1645236970577/pSTn4rn1C.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Arguably one of the most common problems front-end developers need to solve is form handling. Specially in modern web applications that require instant validation and other real-time interactions with the user.  To provide the best user experience as possible, you’ll probably grab a third party form management library to help you.</p>
<p>In this post I am going to write about <a target="_blank" href="https://felte.dev">Felte</a>, a form management library for Svelte I have been working on for the past year that aims to make the basics of form handling on the front-end as simple as possible, while still allowing for it to grow more complex as your requirements grow.</p>
<p>This is one of three blog posts related to Felte. This one is oriented towards Felte’s integration with <a target="_blank" href="https://svelte.dev">Svelte</a>. The other two are oriented towards Felte’s integration with <a target="_blank" href="https://solidjs.com">Solid</a> and <a target="_blank" href="https://reactjs.org">React</a>.</p>
<h2 id="heading-features">Features</h2>
<p>As mentioned above, Felte aims to make the basics of form reactivity as easy to handle as possible, while still allowing for more complex behaviours via configuration and extensibility. Its main features are:</p>
<ul>
<li>Single action to make your form reactive.</li>
<li>Use HTML5 native elements to create your form. (Only the <code>name</code> attribute is necessary).</li>
<li>Provides stores and helper functions to handle more complex use cases.</li>
<li>No assumptions on your validation strategy. Use any validation library you want or write your own strategy.</li>
<li>Handles addition and removal of form controls during runtime.</li>
<li>Official solutions for error reporting using <code>reporter</code> packages.</li>
<li>Supports validation with <a target="_blank" href="https://felte.dev/docs/svelte/validators#using-yup">yup</a>, <a target="_blank" href="https://felte.dev/docs/svelte/validators#using-zod">zod</a>, <a target="_blank" href="https://felte.dev/docs/svelte/validators#using-superstruct">superstruct</a> and <a target="_blank" href="https://felte.dev/docs/svelte/validators#using-vest">vest</a>.</li>
<li>Easily <a target="_blank" href="https://felte.dev/docs/svelte/extending-felte">extend its functionality</a>.</li>
</ul>
<h2 id="heading-how-does-it-look-like">How does it look like?</h2>
<p>In its most basic form, Felte only requires a single function to be imported:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
  <span class="hljs-keyword">import</span> { createForm } <span class="hljs-keyword">from</span> <span class="hljs-string">'felte'</span>

  <span class="hljs-keyword">const</span> { form } = createForm({
    <span class="hljs-attr">onSubmit</span>: <span class="hljs-keyword">async</span> (values) =&gt; {
      <span class="hljs-comment">/* call to an api */</span>
    },
  })
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">use:form</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">text</span> <span class="hljs-attr">name</span>=<span class="hljs-string">email</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">password</span> <span class="hljs-attr">name</span>=<span class="hljs-string">password</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">submit</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"Sign in"</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span>
</code></pre>
<p>We set up the form by calling <code>createForm</code> with our <code>submit</code> handler. This function returns, among other utilities, an action that can be used on your form element. Now Felte will track all inputs with a <code>name</code> attribute. When submitting your form, the latest values in your inputs will be passed to your <code>onSubmit</code> function as an object. For our previous example, the shape of <code>values</code> will be:</p>
<pre><code class="lang-javascript">{
  <span class="hljs-attr">email</span>: <span class="hljs-string">''</span>,
  <span class="hljs-attr">password</span>: <span class="hljs-string">''</span>,
}
</code></pre>
<h2 id="heading-where-can-i-see-my-data">Where can I see my data?</h2>
<p>As you type, Felte will keep track of your user’s input in a regular writable Svelte store. This store is returned by <code>createForm</code> as <code>data</code>, following the same shape as the values you’d receive on your <code>onSubmit</code> function.</p>
<p>For example, this would log your user’s email to the console as they type it:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> { form, data } = createForm({ <span class="hljs-comment">/* ... */</span> });

<span class="hljs-comment">// We use a reactive statement to log everytime our data store changes.</span>
<span class="hljs-comment">// We access the value of our store by prefixing it with `$`.</span>
$: <span class="hljs-built_in">console</span>.log($data.email);
</code></pre>
<h2 id="heading-i-might-need-some-validation-here">I might need some validation here</h2>
<p>Of course, another common requirement of forms is validation. If we want our app to feel snappy to the user, we will want some client side validation. <code>createForm</code>’s configuration object accepts a <code>validate</code> function (which can be asynchronous). It will receive the current value of your <code>data</code> store as it changes, and it expects you to return an object with the same shape as your <code>data</code> store containing your validation messages if the form is not valid, or nothing if your form is valid. Felte will keep track of these validation messages on a writable store that is returned from <code>createForm</code> as <code>errors</code>:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> { form, errors } = createForm({
  validate(values) {
    <span class="hljs-keyword">const</span> currentErrors = {};
    <span class="hljs-keyword">if</span> (!values.email) currentErrors.email = <span class="hljs-string">'Must not be empty'</span>;
    <span class="hljs-keyword">if</span> (!values.password) currentErrors.password = <span class="hljs-string">'Must not be empty'</span>;
    <span class="hljs-keyword">return</span> currentErrors;
  },
});

$: <span class="hljs-built_in">console</span>.log($errors);
</code></pre>
<p>More complex validation requirements might require third party validation libraries. Felte offers first party integrations with some popular validation libraries through its extensibility features. These integrations are offered as separate packages. I will write write more about this in the next section regarding extensibility, but you can read more about these packages in our <a target="_blank" href="https://felte.dev/docs/svelte/validators">official documentation</a>.</p>
<h2 id="heading-handling-complex-scenarios-via-extensibility">Handling complex scenarios via extensibility</h2>
<p>Felte does not attempt to have the perfect solution on how to handle all scenarios regarding form management. This is why Felte offers an API to extend its functionality as your requirements grow more complex. You may have a preferred library you like to use, such as the really popular <a target="_blank" href="https://github.com/jquense/yup">yup</a>, or <a target="_blank" href="https://vestjs.dev/">Vest</a> (which was recently talked about during <a target="_blank" href="https://www.youtube.com/watch?v=X2PuiawaGV4">Svelte Summit</a>). Or you might find it tedious to display your validation messages using <code>if</code> statements. Modifying Felte’s behaviour to handle these scenarios can be done via the <code>extend</code> option on <code>createForm</code>’s configuration object. More about this can be read in the <a target="_blank" href="https://felte.dev/docs/svelte/extending-felte">official documentation</a>. To keep things simple for the purposes of this blog post, I am only going to write about the existing packages we maintain to handle some common use cases:</p>
<h3 id="heading-validators-integrations-with-popular-validation-libraries">Validators: Integrations with popular validation libraries</h3>
<p>We are currently maintaining four packages to integrate Felte with some popular validation libraries: <code>yup</code>, <code>zod</code>, <code>superstruct</code> and most recently <code>vest</code>. Here I will use yup as an example, but you can read more about the rest <a target="_blank" href="https://felte.dev/docs/svelte/validators">here</a>.</p>
<p>The package to use <code>yup</code> is on npm under the name <code>@felte/validator-yup</code>. You will need to install it alongside <code>yup</code>:</p>
<pre><code class="lang-bash">npm install --save @felte/validator-yup yup

<span class="hljs-comment"># Or, if you use yarn</span>

yarn add @felte/validator-yup yup
</code></pre>
<p>This validator package exports a function called <code>validator</code> which you can call with your validation schema and pass its result to the <code>extend</code> option of <code>createForm</code>:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { validator } <span class="hljs-keyword">from</span> <span class="hljs-string">'@felte/validator-yup'</span>;
<span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> yup <span class="hljs-keyword">from</span> <span class="hljs-string">'yup'</span>;

<span class="hljs-keyword">const</span> schema = yup.object({
  <span class="hljs-attr">email</span>: yup.string().email().required(),
  <span class="hljs-attr">password</span>: yup.string().required(),
});

<span class="hljs-keyword">const</span> { form } = createForm({
  <span class="hljs-comment">// ...</span>
  <span class="hljs-attr">extend</span>: validator({ schema }), <span class="hljs-comment">// OR `extend: [validator({ schema })],`</span>
  <span class="hljs-comment">// ...</span>
});
</code></pre>
<h3 id="heading-reporters-displaying-validation-messages">Reporters: Displaying validation messages</h3>
<p>Displaying your validation messages can be done by directly accessing the <code>errors</code> store returned by <code>createForm</code>. Messages won’t be available on this store until the related field is interacted with.</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
  <span class="hljs-keyword">const</span> { form, errors } = createForm({ <span class="hljs-comment">/* ... */</span> });
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">use:form</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">for</span>=<span class="hljs-string">email</span>&gt;</span>Email:<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">name</span>=<span class="hljs-string">email</span> <span class="hljs-attr">id</span>=<span class="hljs-string">email</span> <span class="hljs-attr">type</span>=<span class="hljs-string">email</span>&gt;</span>
  {#if $errors.email}
    <span class="hljs-tag">&lt;<span class="hljs-name">span</span>&gt;</span>{$errors.email}<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
  {/if}
  <span class="hljs-tag">&lt;<span class="hljs-name">button</span>&gt;</span>Submit<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span>
</code></pre>
<blockquote>
<p>If a specific field has an error, Felte assigns an <code>aria-invalid=true</code> attribute to the appropriate input.</p>
</blockquote>
<p>But you might not like that specific syntax to handle your validation messages. Felte currently also has four accompanying packages that offer different alternatives on how to display your validation messages:</p>
<ul>
<li>Using a Svelte component, which gives the most flexibility and would allow you to have access to your validation messages deep within the component tree without needing to pass the <code>errors</code> store around.</li>
<li>Modifying the DOM directly by adding and removing DOM elements.</li>
<li>Using Tippy.js to display your messages  in a tooltip.</li>
<li>Using the browser’s built-in constraint validation API, which can be less friendly to mobile users.</li>
</ul>
<p>For brevity, I am only going to write about the first package. But you can read more about the rest <a target="_blank" href="https://felte.dev/docs/svelte/reporters">in the documentation</a>.</p>
<p>Using a Svelte component to get your validation messages can be done with the package <code>@felte/reporter-svelte</code>.  You’ll need to add it to your project using your favourite package manager:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># npm</span>
npm i -S @felte/reporter-svelte

<span class="hljs-comment"># yarn</span>
yarn add @felte/reporter-svelte
</code></pre>
<p>Then you’ll need to import both the <code>svelteReporter</code> function to add to the <code>extend</code> property, and the <code>ValidationMessage</code> component which you will use to receive your validation messages:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
  <span class="hljs-keyword">import</span> { svelteReporter, ValidationMessage } <span class="hljs-keyword">from</span> <span class="hljs-string">'@felte/reporter-svelte'</span>;
  <span class="hljs-keyword">import</span> { createForm } <span class="hljs-keyword">from</span> <span class="hljs-string">'felte'</span>;

  <span class="hljs-keyword">const</span> { form } = createForm({
      <span class="hljs-comment">// ...</span>
      <span class="hljs-attr">extend</span>: svelteReporter,
      <span class="hljs-comment">// ...</span>
    },
  })
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">use:form</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"email"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"email"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">ValidationMessage</span> <span class="hljs-attr">for</span>=<span class="hljs-string">"email"</span> <span class="hljs-attr">let:messages</span>=<span class="hljs-string">{message}</span>&gt;</span>
    <span class="hljs-comment">&lt;!-- We assume a single string will be passed as a validation message --&gt;</span>
    <span class="hljs-comment">&lt;!-- This can be an array of strings depending on your validation strategy --&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">span</span>&gt;</span>{message}<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
    <span class="hljs-comment">&lt;!-- Shown when there's no validation messages --&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">slot</span>=<span class="hljs-string">"placeholder"</span>&gt;</span>Please type a valid email.<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">ValidationMessage</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"password"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"password"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">ValidationMessage</span> <span class="hljs-attr">for</span>=<span class="hljs-string">"password"</span> <span class="hljs-attr">let:messages</span>=<span class="hljs-string">{message}</span>&gt;</span>
    <span class="hljs-comment">&lt;!-- If not slot is used, you'll need to handle empty messages --&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">span</span>&gt;</span>{message || ''}<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">ValidationMessage</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"submit"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"Sign in"</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span>
</code></pre>
<h2 id="heading-next-steps">Next steps</h2>
<p>You can check more about Felte in its <a target="_blank" href="https://felte.dev">official website</a> with some functional examples. There’s also a more complex example showcasing its usage with Tippy.js and Yup available on <a target="_blank" href="https://codesandbox.io/s/felte-v1-demo-svelte-0egr6?file=/App.svelte">CodeSandbox</a>.</p>
<h2 id="heading-finishing-thoughts">Finishing thoughts</h2>
<p>I hope this served as a good introduction to Felte, and that it is interesting enough for you to give it a try. Felte is already on a stable state and used by some people.  I am also open to help and suggestions so feel free to open an issue or make a pull request on <a target="_blank" href="https://github.com/pablo-abc/felte">GitHub</a>.</p>
]]></content:encoded></item></channel></rss>