<?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[Type of Web]]></title><description><![CDATA[Insights, tutorials, and resources on web development. TypeScript, Next.js, React, and modern web technologies. Empower your coding skills with expert articles ]]></description><link>https://typeofweb.com</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1701033430183/68I-Sq3Wp.png</url><title>Type of Web</title><link>https://typeofweb.com</link></image><generator>RSS for Node</generator><lastBuildDate>Mon, 13 Apr 2026 21:08:12 GMT</lastBuildDate><atom:link href="https://typeofweb.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Do reinvent the wheel.]]></title><description><![CDATA[Throughout the years, I have been told numerous times not to reinvent the wheel. In principle, this is sound advice. But a piece of advice repeated thousands of times becomes a mental rule – harmful and prejudicial.
We've taken it too far
It has led ...]]></description><link>https://typeofweb.com/do-reinvent-the-wheel</link><guid isPermaLink="true">https://typeofweb.com/do-reinvent-the-wheel</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[framework]]></category><category><![CDATA[library]]></category><category><![CDATA[architecture]]></category><dc:creator><![CDATA[Michał Miszczyszyn]]></dc:creator><pubDate>Sat, 22 Jun 2024 18:14:44 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1719228112858/b988a15d-c551-4b44-97ea-67ef2aaa8bcb.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Throughout the years, I have been told numerous times <strong>not to reinvent the wheel</strong>. In principle, this is sound advice. But a piece of advice repeated thousands of times becomes a mental rule – <strong>harmful and prejudicial</strong>.</p>
<h2 id="heading-weve-taken-it-too-far">We've taken it too far</h2>
<p>It has led us to the point where developers avoid writing custom code at all costs and instead <strong>spend months researching libraries</strong>. Yes, you read that right, months. True story 🤯 Funnily enough, the library that they picked, nurtured, and made half of the app reliant on got abandoned not more than a few months later.</p>
<p>In the meantime, their main competitor developed an in-house solution, released it to production, and started marketing around it. In theory, picking a library ought to give you a head start. <strong>In reality, though, that's often not the case.</strong></p>
<p>I believe the <strong>mindless chanting of "do not reinvent the wheel"</strong> turned many workplaces and projects into toxic environments. Developers are afraid of putting themselves on the spot, being judged, and possibly failing. They'd rather shift the center of gravity and all attention toward the libraries and their authors.</p>
<h2 id="heading-the-time-saving-fallacy">The time-saving fallacy</h2>
<p>„But isn't that good?” you may ask. Great question; I'm glad you asked. The answer is: it depends. Using libraries is supposed to save you a ton of time. Unfortunately, once the library is picked, the developers usually need to spend countless hours trying to bend it to their needs. <strong>The library does 90% of the job</strong>, which is great, but the <strong>remaining 10% requires the developers to become MacGyvers</strong> and implement a quick and dirty duct tape solution.</p>
<p>Not to mention the time and money costs of relying on external dependencies: bugs that go unfixed for years, projects that are abandoned, and breaking changes in releases. Maintaining a codebase with more than a few external dependencies seems daunting.</p>
<h2 id="heading-the-external-dependencies">The external dependencies</h2>
<p>I am not immune. Just recently, we were faced with the task of implementing a search engine for <a target="_blank" href="https://yournextstore.com">yournextstore.com</a>. I tried one library, which kind of worked. Then I tried another, which was significantly faster but lacked one of the features that we needed. <strong>Oh, and it had 1231 features we didn't need.</strong></p>
<p>It took us a month of meandering to ask ourselves: <strong>How hard would it be to implement search on our own?</strong> I wrote down the requirements: full-text in-memory search with prefix search, suffix search, and scoring, but no fuzzing. I started coding. It was done in less than a day. It worked 10x to 20x faster than the library we picked previously.</p>
<h2 id="heading-the-dont-get-me-wrong-paragraph">The "don't get me wrong" paragraph</h2>
<p>Don't get me wrong. I'm not trying to convince you to abandon all dependencies and rewrite <em>everything</em> internally. It's all about the balance.</p>
<p>If you've decided to build your own framework, I suggest you reconsider. It's unlikely you'll match the quality and velocity of other popular solutions, and you'll make the learning curve cliff-like for new hires.</p>
<p><strong>But a tooltip? A datagrid? A carousel? Even a simple search engine?</strong> Those are the examples where going completely custom really shines. Not only are those problems actually easy to solve, thanks to the modern Open Web. But by zeroing in on your specific use cases, you'll be able to trim tens or hundreds of kilobytes off of the bundle compared to the libraries.</p>
<p>Not every problem needs a pre-made solution. Sometimes, rolling your own can save you headaches down the line. <strong>Reinventing the wheel isn’t always bad</strong>. In fact, it might just be the best decision you make.</p>
<p><strong>Please, by all means, sometimes do reinvent the wheel.</strong></p>
]]></content:encoded></item><item><title><![CDATA[Mastering Next.js Prefetching: Enhance Navigation with the SuperLink Component]]></title><description><![CDATA[Next.js has a component that enables prefetching and client-side navigation between routes: Link. It's one of the primitives upon which you build applications. Yet, there are a lot of misconceptions about how prefetching actually works. To add to the...]]></description><link>https://typeofweb.com/nextjs-prefetch-onmouseenter</link><guid isPermaLink="true">https://typeofweb.com/nextjs-prefetch-onmouseenter</guid><category><![CDATA[Next.js]]></category><category><![CDATA[React]]></category><dc:creator><![CDATA[Michał Miszczyszyn]]></dc:creator><pubDate>Sun, 26 May 2024 12:41:33 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1716726456075/d3ad4320-3502-436a-bc96-bc824f945783.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Next.js has a component that enables prefetching and client-side navigation between routes: <code>Link</code>. It's one of the primitives upon which you build applications. Yet, there are a lot of misconceptions about how prefetching actually works. To add to the confusion, its behavior has been changing since Next.js 13 beta. <strong>How does the prefetch actually work? And can we make it more flexible?</strong></p>
<h2 id="heading-the-link-component-in-the-pages-router">The Link Component in the Pages Router</h2>
<p>The <code>Link</code> component from <code>next/link</code> accepts an optional prop <code>prefetch</code>. Back in Next.js 12 or now <strong>in Pages Router</strong>, <code>prefetch</code> took only two values: <code>true</code> or <code>false</code> (and it was <code>true</code> by default when omitted). However, these values don't really mean what you might think they do.</p>
<ul>
<li><p><code>true</code> – the route is prefetched when the link enters the viewport</p>
</li>
<li><p><code>false</code> – the route is <strong>not</strong> prefetched when the link enters the viewport; however, the <strong>route will be prefetched on hover</strong></p>
</li>
</ul>
<p>That last behavior was commonly perceived as confusing because the semantics of the <code>false</code> value didn't obviously convey the mechanics. Also, it was impossible to disable the prefetching completely.</p>
<h2 id="heading-the-link-component-in-the-app-router">The Link Component in the App Router</h2>
<p>This changed with the introduction of App Router in Next.js 13. However, the exact mechanisms of the automatic prefetching were modified between Next 13 and Next 15, and finally, <a target="_blank" href="https://github.com/vercel/next.js/commit/5d21c6cba13606892bc4740b82e2b7ac682905dc">a third possibility was introduced</a>. The workings of Link are now also different, depending on <strong>whether it points to a static or dynamic page.</strong> The <code>prefetch</code> prop is (still) optional and takes the following values:</p>
<ul>
<li><p><code>true</code> – the full route is prefetched when the link enters the viewport, both for static and dynamic pages</p>
</li>
<li><p><code>false</code> – prefetching is completely disabled</p>
</li>
<li><p><code>null</code> – <strong>new default value</strong> – the behavior depends on whether the route is static or dynamic:</p>
<ul>
<li><p>static – the full route will be prefetched when the link enters the viewport</p>
</li>
<li><p>dynamic – only a partial route down to the nearest segment with a <code>loading.tsx</code> boundary will be prefetched; <strong>if there's no such route, nothing is prefetched</strong></p>
</li>
</ul>
</li>
</ul>
<p>Now, prefetching is more powerful and adjustable. However, it still lacks some flexibility. Let's fix that!</p>
<h2 id="heading-superlink-wrapper-for-link">SuperLink: Wrapper for Link</h2>
<p>We can extend the capabilities of the <code>Link</code> and control prefetching by introducing a custom component that wraps around the <code>Link</code>.</p>
<p>The plan is to wrap around it, override <code>prefetch</code>, and programmatically prefetch on certain interactions. My idea is to do it when a user hovers over the element. <strong>Often, the time between hover and click is long enough for the prefetch to complete, and then navigating to the route is instantaneous.</strong> When we're done with that, we'll also consider other ways of interacting with the web app, and improve accessibility of our solution. Let's get to it!</p>
<h2 id="heading-implementing-superlink-with-onmouseenter">Implementing SuperLink with onMouseEnter</h2>
<p>Let's start by wrapping around <code>next/link</code>. We're using <strong>React 19</strong>, so we don't need the whole <code>forwardRef</code> shenanigans, and we can simply accept <code>ref</code> as props via <code>ComponentPropsWithRef</code>:</p>
<pre><code class="lang-typescript"><span class="hljs-string">"use client"</span>;

<span class="hljs-keyword">import</span> Link <span class="hljs-keyword">from</span> <span class="hljs-string">"next/link"</span>;
<span class="hljs-keyword">import</span> { <span class="hljs-keyword">type</span> ComponentPropsWithRef } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> SuperLink = <span class="hljs-function">(<span class="hljs-params">props: ComponentPropsWithRef&lt;<span class="hljs-keyword">typeof</span> Link&gt;</span>) =&gt;</span> {
  <span class="hljs-keyword">return</span> &lt;Link {...props} /&gt;;
};
</code></pre>
<p>Now, let's override the <code>prefetch</code> prop and use <code>router</code> to programmatically prefetch instead. We'll also need to handle the fact that <code>href</code> can be a string or a URLObject:</p>
<pre><code class="lang-typescript"><span class="hljs-string">"use client"</span>;

<span class="hljs-keyword">import</span> Link <span class="hljs-keyword">from</span> <span class="hljs-string">"next/link"</span>;
<span class="hljs-keyword">import</span> { useRouter } <span class="hljs-keyword">from</span> <span class="hljs-string">"next/navigation"</span>;
<span class="hljs-keyword">import</span> { <span class="hljs-keyword">type</span> ComponentPropsWithRef } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> SuperLink = <span class="hljs-function">(<span class="hljs-params">props: ComponentPropsWithRef&lt;<span class="hljs-keyword">typeof</span> Link&gt;</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> router = useRouter();
  <span class="hljs-keyword">const</span> strHref = <span class="hljs-keyword">typeof</span> props.href === <span class="hljs-string">"string"</span> ? props.href : props.href.href;
  <span class="hljs-keyword">return</span> (
    &lt;Link
      {...props}
      prefetch={<span class="hljs-literal">false</span>}
      onMouseEnter={<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
        <span class="hljs-keyword">if</span> (strHref) {
          <span class="hljs-built_in">void</span> router.prefetch(strHref);
        }
        <span class="hljs-keyword">return</span> props.onMouseEnter?.(e);
      }}
    /&gt;
  );
};
</code></pre>
<p>Now, prefetching happens on <code>mouseenter</code>, which should be enough to make the navigation smoother than no prefetching at all. It also works for both static and dynamic routes.</p>
<h2 id="heading-unnecessary-requests">Unnecessary requests</h2>
<p>Some people on <s>Twitter</s><strong>𝕏</strong> argue that this is a poor choice because moving your cursors across the page will trigger multiple prefetches. It's true. <strong>However, with just</strong><code>prefetch={true}</code><strong>, even more requests will be made</strong>. So, I'd say our current implementation is an improvement anyway. On rare occasions, you might want to change the event or condition for which the <code>prefetch</code> is called. It's a tradeoff.</p>
<h2 id="heading-accessibility">Accessibility</h2>
<p>Our current solution completely ignores users navigating the application with keyboards and touch screens. We should improve it and work on accessibility, too!</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">👏</div>
<div data-node-type="callout-text">Shout out to <a target="_blank" href="https://x.com/ImLunaHey">ImLunaHey</a>, <a target="_blank" href="https://x.com/RedCardinal">RedCardinal</a>, and <a target="_blank" href="https://x.com/JohnPhamous">JohnPhamous</a> for reminding me of how important accessibility is.</div>
</div>

<p>Will adding handlers for <code>onPointerEnter</code>, <code>onTouchStart</code> and <code>onFocus</code> do the job? I'm no accessibility expert, but it certainly seems to be working:</p>
<pre><code class="lang-typescript"><span class="hljs-string">"use client"</span>;

<span class="hljs-keyword">import</span> Link <span class="hljs-keyword">from</span> <span class="hljs-string">"next/link"</span>;
<span class="hljs-keyword">import</span> { useRouter } <span class="hljs-keyword">from</span> <span class="hljs-string">"next/navigation"</span>;
<span class="hljs-keyword">import</span> { <span class="hljs-keyword">type</span> ComponentPropsWithRef } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> SuperLink = <span class="hljs-function">(<span class="hljs-params">props: ComponentPropsWithRef&lt;<span class="hljs-keyword">typeof</span> Link&gt;</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> router = useRouter();
  <span class="hljs-keyword">const</span> strHref = <span class="hljs-keyword">typeof</span> props.href === <span class="hljs-string">"string"</span> ? props.href : props.href.href;

  <span class="hljs-keyword">const</span> conditionalPrefetch = <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">if</span> (strHref) {
      <span class="hljs-built_in">void</span> router.prefetch(strHref);
    }
  };

  <span class="hljs-keyword">return</span> (
    &lt;Link
      {...props}
      prefetch={<span class="hljs-literal">false</span>}
      onMouseEnter={<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
        conditionalPrefetch();
        <span class="hljs-keyword">return</span> props.onMouseEnter?.(e);
      }}
      onPointerEnter={<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
        conditionalPrefetch();
        <span class="hljs-keyword">return</span> props.onPointerEnter?.(e);
      }}
      onTouchStart={<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
        conditionalPrefetch();
        <span class="hljs-keyword">return</span> props.onTouchStart?.(e);
      }}
      onFocus={<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
        conditionalPrefetch();
        <span class="hljs-keyword">return</span> props.onFocus?.(e);
      }}
    /&gt;
  );
};
</code></pre>
<p>Now, again, you might argue that calling prefetch on <code>focus</code> will trigger many unnecessary requests. To get to the third link on the page with your keyboard, you need to also focus the first and second ones, and, in effect, you'll prefetch them, too. <strong>I agree this is not optimal and can be improved. Looking forward to your suggestions on how.</strong></p>
<h2 id="heading-prefetch-on-fast-internet-only">Prefetch on Fast Internet Only</h2>
<p>Here's another idea: only trigger <code>prefetch</code> when the user has a stable and fast internet connection. How can we do that? Using <code>navigator.connection</code>. Let's try that.</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">Using <code>navigator.connection</code> requires installing and configuring TypeScript types from <code>network-information-types</code> package.</div>
</div>

<p>I've omitted the rest of the code for brevity, but here's the gist of the idea:</p>
<pre><code class="lang-typescript"><span class="hljs-string">"use client"</span>;

<span class="hljs-keyword">import</span> Link <span class="hljs-keyword">from</span> <span class="hljs-string">"next/link"</span>;
<span class="hljs-keyword">import</span> { useRouter } <span class="hljs-keyword">from</span> <span class="hljs-string">"next/navigation"</span>;
<span class="hljs-keyword">import</span> { <span class="hljs-keyword">type</span> ComponentPropsWithRef } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> SuperLink = <span class="hljs-function">(<span class="hljs-params">props: ComponentPropsWithRef&lt;<span class="hljs-keyword">typeof</span> Link&gt;</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> router = useRouter();
  <span class="hljs-keyword">const</span> strHref = <span class="hljs-keyword">typeof</span> props.href === <span class="hljs-string">"string"</span> ? props.href : props.href.href;
  <span class="hljs-keyword">return</span> (
    &lt;Link
      {...props}
      prefetch={<span class="hljs-literal">false</span>}
      onMouseEnter={<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
        <span class="hljs-keyword">const</span> hasFastInternet =
          !navigator.connection || navigator.connection.effectiveType === <span class="hljs-string">"4g"</span>;
        <span class="hljs-keyword">if</span> (strHref &amp;&amp; hasFastInternet) {
          <span class="hljs-built_in">void</span> router.prefetch(strHref);
        }
        <span class="hljs-keyword">return</span> props.onMouseEnter?.(e);
      }}
    /&gt;
  );
};
</code></pre>
<h2 id="heading-summary">Summary</h2>
<p>It's not ideal, but remember the Pareto rule: We've <strong>done 80% of the job in 20% of the time.</strong> I'm open to suggestions on how this can be improved. Maybe with a debounce of some sort? Post your code in the comments!</p>
]]></content:encoded></item><item><title><![CDATA[Property-based testing in TypeScript]]></title><description><![CDATA[There is no doubt writing tests brings value. I believe every professional should write tests for their code, and no software should ever be released without tests. We may discuss whether metrics such as code coverage are useful or not. We may argue ...]]></description><link>https://typeofweb.com/property-based-testing-in-typescript</link><guid isPermaLink="true">https://typeofweb.com/property-based-testing-in-typescript</guid><category><![CDATA[TypeScript]]></category><category><![CDATA[Testing]]></category><dc:creator><![CDATA[Michał Miszczyszyn]]></dc:creator><pubDate>Mon, 20 May 2024 16:22:52 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1716222089322/8f06e2c3-340d-4519-aef6-910814e68512.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>There is no doubt writing tests brings value. I believe every professional should write tests for their code, and no software should ever be released without tests. We may discuss whether metrics such as code coverage are useful or not. We may argue that integration tests bring more value than unit tests. But I think it's undebatable that with tests, we have <strong>at least some degree of certainty that our code is correct</strong>.</p>
<h2 id="heading-classic-tests">Classic tests</h2>
<p>Typically, regardless of framework, language, or kind of testing, a test consists of three main parts:</p>
<ol>
<li><p>Conditions / Assumptions (<strong>Given</strong>)</p>
</li>
<li><p>Changes / Actions (<strong>When</strong>)</p>
</li>
<li><p>Observations / Assertions (<strong>Then</strong>)</p>
</li>
</ol>
<p>The exact names depend on the testing paradigm and community, but the notion is the same: We make assumptions, then perform operations, and finally verify whether the code behaves expectedly.</p>
<h2 id="heading-the-problem-with-tests">The problem with tests</h2>
<p>Having said all that, I would argue that there's a problem with how tests are usually created: all three steps need to be handled by humans. <strong>And humans make mistakes</strong>.</p>
<p>Another issue is that this approach to testing requires us to think of every possible scenario. Every step that can be taken in the app, every possible value of variables, <strong>and their combination</strong>… It's a lot to handle for our minds. And as the number of features in the codebase increases, the complexity of testing grows exponentially. Testing everything is infeasible.</p>
<p>And while there are techniques that help us deal with this complexity, <strong>how many times have you released to prod without any bugs</strong>? Let me take a guess: 0 times.</p>
<h2 id="heading-property-based-testing">Property-based testing</h2>
<p>Now, here's a silly idea: <strong>what if inputs to tests and actions taken in tests were created automatically by a machine</strong>? That's exactly where property-based testing comes into play!</p>
<p>This idea, like a lot of good ideas, comes from functional programming (Haskell, Quickcheck). Later, it was refined in Elixir. But the same concept exists in multiple different languages: C++, Rust, Python, TypeScript… I'll focus on the latter.</p>
<p>Property-based testing is a concept where:</p>
<ul>
<li><p>inputs are <strong>randomized</strong></p>
</li>
<li><p>inputs are <strong>constrained</strong></p>
</li>
<li><p>we think of <strong>properties</strong></p>
</li>
<li><p>we have <strong>no specific assertions</strong></p>
</li>
<li><p>we <strong>test properties</strong></p>
</li>
</ul>
<h2 id="heading-the-simplest-example">The simplest example</h2>
<p>Let's take a quick look at the simplest example. It's repeated in probably thousands of articles about property-based testing. We have a function <code>sort</code> that takes a list of numbers, sorts it, and returns. Ignore the fact that most languages have such a function in their standard library.</p>
<p>We could easily write unit tests for our <code>sort</code>. For example:</p>
<ol>
<li><p>Given a list (3, 5, 1)</p>
</li>
<li><p>When it's sorted</p>
</li>
<li><p>The result is (1, 3, 5)</p>
</li>
</ol>
<p>Or in code:</p>
<pre><code class="lang-typescript">it(<span class="hljs-string">"sorts numbers"</span>, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> input = [<span class="hljs-number">3</span>, <span class="hljs-number">5</span>, <span class="hljs-number">1</span>];
  <span class="hljs-keyword">const</span> result = sort(input);
  expect(result).toEqual([<span class="hljs-number">1</span>, <span class="hljs-number">3</span>, <span class="hljs-number">5</span>]);
});
</code></pre>
<p>We could add a few more tests for edge cases, too: sorted list, empty list, list where all elements are equal… However, unit testing is often called <strong>example-based testing</strong>. We only test cases we think of, so our <strong>test is only as good as our imagination</strong>.</p>
<blockquote>
<p>QA Engineer walks into a bar, and they order a beer. Order 0 beers. Order 999999 beers. Order -1 beers. Order a lizard. Order a <code>ueicbksjdhd</code>.<br />✅ Unit tests pass<br />A real customer walks into the bar and asks where the bathroom is.<br />The bar goes up in flames.</p>
</blockquote>
<p>Now, property-based testing takes a different approach. We think of properties and tell the framework: "<strong>Our sorting function behaves such that given <em>any</em> list of numbers…</strong>". Let's list a few properties:</p>
<ol>
<li><p>When a list is sorted, each element is greater or equal to the previous one.</p>
</li>
<li><p>The lengths of the original list and the sorted list are equal.</p>
</li>
<li><p>Each element from the original list is also in the sorted list.</p>
</li>
</ol>
<p><strong>We do not think of specific cases.</strong> Instead, we think of properties that can be applied to <em>any</em> list of numbers. It's way more difficult to think about tests this way, but it's also more beneficial. When we're done designing the properties, the framework will <strong>generate hundreds or thousands of tests</strong> for us.</p>
<h2 id="heading-enter-fast-check">Enter fast-check</h2>
<p>The framework for property-based testing in TypeScript is called <a target="_blank" href="https://fast-check.dev/">fast-check</a>. What does the above example look like in code?</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> assert <span class="hljs-keyword">from</span> <span class="hljs-string">"node:assert"</span>;
<span class="hljs-keyword">import</span> { it } <span class="hljs-keyword">from</span> <span class="hljs-string">"node:test"</span>;
<span class="hljs-keyword">import</span> fc <span class="hljs-keyword">from</span> <span class="hljs-string">"fast-check"</span>;

it(<span class="hljs-string">"When a list is sorted, each element is greater or equal to the previous one."</span>, <span class="hljs-function">() =&gt;</span>
  fc.assert(
    fc.property(fc.array(fc.integer()), <span class="hljs-function">(<span class="hljs-params">arr</span>) =&gt;</span> {
      <span class="hljs-keyword">const</span> sorted = sort(arr);
      assert(sorted.every(<span class="hljs-function">(<span class="hljs-params">v, i</span>) =&gt;</span> i === <span class="hljs-number">0</span> || v &gt;= sorted[i - <span class="hljs-number">1</span>]));
    })
  ));
</code></pre>
<p>What this code does is tell <code>fast-check</code> that we're testing a property that requires a list of integers (<code>fc.array(fc.integer())</code>). Fast-check will generate hundreds of randomized inputs on which our assertion will be run.</p>
<h2 id="heading-shrinking-and-counterexamples">Shrinking and counterexamples</h2>
<p>But the truly interesting thing happens when one of our tests fails. I've introduced a bug to my sorting function to demonstrate this. We get an error message saying <code>Error: Property failed after 6 tests</code>, then an undecipherable string (<code>seed</code> and <code>path</code>), and finally <code>Counterexample: [[23,22]] / Shrunk 526 time(s)</code>. What does all that mean?</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1716219203184/ec13fc42-3a09-47f3-bb64-427c4827c3a7.png" alt class="image--center mx-auto" /></p>
<p>Fast-check generated a bunch of random arrays and found a case where the test failed. The arrays generated could've been huge. Wouldn't it be nice if Fast-check could somehow produce a minimal reproducible example instead? This is exactly what's going on here! <strong>The framework started looking for a smaller input for which the test fails</strong>. This process is called <a target="_blank" href="https://github.com/dubzzz/fast-check/blob/b824abf8eaa0daae372f299ae59cfe8ba1dc7b83/packages/fast-check/documentation/HowItWorks.md#shrinkers">shrinking</a>.</p>
<p>Fast-check started with a large array, shrunk it 526 times, and produced a counterexample: an array of just two elements <code>[23, 22]</code>. Isn't that just wonderful?</p>
<h2 id="heading-fixing-bugs">Fixing bugs</h2>
<p>You might be tempted to fix the bug, run the tests again, and move on with your life. But how do you <strong>actually</strong> know that the bug is fixed? Inputs generated by fast-check are randomized, so <strong>there's the off chance of not generating a failing case</strong> anymore!</p>
<p>Therefore, the first thing you must do whenever you discover a bug with fast-check is to <strong>write a unit test for that specific scenario</strong>. That's where another feature of <code>fast-check</code> comes in handy: <code>examples</code>. After a slight refactoring:</p>
<pre><code class="lang-typescript">it(<span class="hljs-string">"When a list is sorted, each element is greater or equal to the previous one."</span>, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> eachGreaterThanPreviousProperty = fc.property(
    fc.array(fc.integer()),
    <span class="hljs-function">(<span class="hljs-params">arr</span>) =&gt;</span> {
      <span class="hljs-keyword">const</span> sorted = sort(arr);
      assert(sorted.every(<span class="hljs-function">(<span class="hljs-params">v, i</span>) =&gt;</span> i === <span class="hljs-number">0</span> || v &gt;= sorted[i - <span class="hljs-number">1</span>]));
    }
  );

  fc.assert(eachGreaterThanPreviousProperty, { examples: [[[<span class="hljs-number">23</span>, <span class="hljs-number">22</span>]]] });
  fc.assert(eachGreaterThanPreviousProperty);
});
</code></pre>
<h2 id="heading-summary">Summary</h2>
<p>I highly recommend playing with fast-check and trying to write a few tests with it. However, in practice, <strong>property-based unit tests often become more complex than the underlying tested implementation itself</strong>. I treat it as a fun exercise to change the way we reason about the problem.</p>
<p>The next step? Property-based end-to-end testing. This is where the framework really shines, and the cost vs. benefit ratio is much more profitable. <strong>I'll focus on that topic in my next article</strong>, so follow my blog if you don't want to miss it!</p>
]]></content:encoded></item><item><title><![CDATA[XY Problem – or when we don't know what we're asking about]]></title><description><![CDATA[Certainly, everyone has faced the XY Problem at some point in their lives, perhaps even without realizing it. Some might even be "committing" it right now. The XY Problem happens when we seek a solution rather than discussing the issue itself. Why is...]]></description><link>https://typeofweb.com/xy-problem-or-when-we-dont-know-what-were-asking-about</link><guid isPermaLink="true">https://typeofweb.com/xy-problem-or-when-we-dont-know-what-were-asking-about</guid><category><![CDATA[Philosophy]]></category><category><![CDATA[Computer Science]]></category><category><![CDATA[management]]></category><category><![CDATA[learning]]></category><dc:creator><![CDATA[Michał Miszczyszyn]]></dc:creator><pubDate>Thu, 29 Feb 2024 18:51:18 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1709580574797/c5a3cd4c-466d-4db6-9284-acd891b2e285.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Certainly, everyone has faced the XY Problem at some point in their lives, perhaps even without realizing it. Some might even be "committing" it right now. <strong>The XY Problem happens when we seek a solution rather than discussing the issue itself</strong>. Why is this problematic?</p>
<h2 id="heading-xy-problem-a-scenario">XY Problem: A Scenario</h2>
<p>I've been helping many people on various forums, chats, and discord servers for years, and I encounter the XY Problem almost daily. What does it typically look like?</p>
<ul>
<li><p>Q: How can I use library Y to do X?</p>
</li>
<li><p>A1: (usually, there are ten different suggestions, each more complex than the last)</p>
</li>
<li><p>Q: Unfortunately, it doesn't work (then more problems arise)</p>
</li>
<li><p>A2: Why do you want to use library Y? It would be easier to achieve X this way.</p>
</li>
<li><p>Q: Thanks! It works.</p>
</li>
</ul>
<h2 id="heading-problem-xy-the-mechanism">Problem XY: The Mechanism</h2>
<ol>
<li><p>The user wants to solve problem X.</p>
</li>
<li><p>The user needs to figure out how to solve X but believes solving Y might be the key.</p>
</li>
<li><p>The user doesn't know how to solve Y, either.</p>
</li>
<li><p>The user asks for help with Y.</p>
</li>
<li><p>Others attempt to assist but need clarification on the focus on Y.</p>
</li>
<li><p>After much discussion and time lost, it's finally understood that the user is trying to solve the problem X, and Y is unrelated or unsuitable.</p>
</li>
</ol>
<h2 id="heading-problem-xy-examples">Problem XY: Examples</h2>
<h3 id="heading-first-example">First Example</h3>
<p>A user wants to <a target="_blank" href="https://mywiki.wooledge.org/XyProblem">get the file extension based on its name</a>. Instead of asking this directly, they begin by trying to solve a problem that doesn't exist:</p>
<ul>
<li><p>Q: How can I get the last three characters from a file name?</p>
</li>
<li><p>A1: <code>filename.slice(-3);</code></p>
</li>
<li><p>Q: Thanks! But unfortunately, this doesn't work for jpeg files 😢</p>
</li>
<li><p>A2: Are you trying to get the file extension?</p>
</li>
<li><p>Q: Yes.</p>
</li>
<li><p>A2: <code>filename.split('.').pop()</code></p>
</li>
</ul>
<h3 id="heading-example-2">Example 2</h3>
<p>A user is converting CSV to JSON using a specific library but isn't happy with the data format – it comes out as an array of rows, not an array of objects. Instead of asking how to use the library for CSV parsing, the user thinks they should change an array of arrays into an array of objects and asks only about that. They're focusing on a problem that doesn't exist:</p>
<ul>
<li><p>Q: I have several arrays and want to turn each into an object using keys from the first array. For example, <code>[['a', 'b'], [1,2], [3,4]]</code> into <code>[{a: 1, b: 2}, {a: 3, b: 4}]</code></p>
</li>
<li><p>A1: That may be tricky. You could use the <code>lodash</code> library, which has a function for this.</p>
</li>
<li><p>A2: Maybe <code>Object.fromEntries(…)</code> could work?</p>
</li>
<li><p>A3: You can use a simple loop for this; try this: <em>(code example provided)</em></p>
</li>
<li><p>Q: Thanks, but that seems too complicated. I thought converting CSV to JSON was straightforward, but I keep hitting snags…</p>
</li>
<li><p>A4: Hold on. Are you converting CSV to JSON? What library are you using?</p>
</li>
<li><p>Q: PapaParser</p>
</li>
<li><p>A4: PapaParser has a <code>headers: true</code> option, giving you the objects you're looking for instead of an array of arrays. You don't need to do anything manually.</p>
</li>
</ul>
<h2 id="heading-where-does-it-come-from">Where does it come from?</h2>
<p>Several factors contribute to the XY Problem. First, the person asking the question is often too invested. <strong>They've spent so much time trying to solve problem Y that stepping back to consider problem X seems daunting.</strong> They might insist, <strong>"I already have the whole system; I just need this one small part,"</strong> but as the CSV example shows, they might have built more than necessary.</p>
<p>Another reason is the <a target="_blank" href="https://www.perlmonks.org/?node_id=542341"><strong>desire to do things correctly</strong></a>. It might sound surprising, but our idealistic views on solving problems can blind us to simpler solutions.</p>
<p>Additionally, questioners sometimes feel <strong>guilty</strong>. They hesitate to ask about the broader issue X, opting instead to focus on solution Y. They believe this approach <strong>saves the time of those helping them</strong>, showing up with a <em>specific</em> problem in search of a <em>specific</em> solution, either out of politeness or guilt.</p>
<p>The XY Problem isn't just a tech issue; it's universal, affecting all areas of life, including medicine. Often, people visit doctors with a self-diagnosis, asking about that instead of explaining their actual symptoms.</p>
<h2 id="heading-the-issue-of-understanding-and-compassion-in-replies">The Issue of Understanding and Compassion in Replies</h2>
<p><a target="_blank" href="https://artyom.me/yx">Some argue</a> that what I've described isn't the heart of the XY Problem. They think the real issue lies with the person responding, not the one asking the question. I don't entirely agree, but it's worth mentioning. The XY Problem can lead to a lack of compassion and understanding in those who answer.</p>
<p>Imagine you're asking about a problem you believe is important. A few people attempt to help, but then someone tells you, <strong>"You don't actually have problem Y. What you're really asking about is X." How does that make you feel?</strong> This lack of compassion from the person answering might make you defensive. You might want to say, "I know what I'm talking about." That could be true, or maybe not, but the conversation will continue with feelings of guilt ("I'm wrong, they're right") or defensiveness ("they're wrong, I know what I was asking").</p>
<p>Another example occurs when the person responding sees themselves as an expert, disregards what the other person is looking for, and instead pushes their preferred solution. For instance, in programming, if you inquire about loops, someone will likely insist you have an XY problem, and you should instead try making your code more functional. It's not the XY Problem I mentioned earlier in the article — <strong>it's the responder's narcissism</strong>.</p>
<p>It's beneficial to offer different solutions when you want to help someone and think they might need help understanding their needs or are making errors. <strong>However, it's important to remember that the person asking is occasionally right</strong>.</p>
<p>The article <a target="_blank" href="https://slatestarcodex.com/2016/02/24/two-attitudes-in-psychiatry/">Two Attitudes in Psychiatry</a> further explores this contrast.</p>
<h2 id="heading-how-to-ask-questions">How to Ask Questions?</h2>
<p>Eric S. Raymond, in his publication <a target="_blank" href="http://www.catb.org/~esr/faqs/smart-questions.html">How To Ask Questions The Smart Way</a>, discusses the XY problem with an example:</p>
<ul>
<li><p>Q: How can I use X to do Y?</p>
</li>
<li><p>A: If what you want is to do Y, you should ask that question without pre-supposing the use of a method that may not be appropriate. Questions of this form often indicate a person who is not merely ignorant about X, but confused about what problem Y they are solving and too fixated on the details of their particular situation. It is generally best to ignore such people until they define their problem better.</p>
</li>
</ul>
<p>My advice? <strong>Provide more context in your question</strong>.</p>
<p>Instead of asking, "How can I get the last three characters from a file name?" you should ask, "How can I get the last three characters from a file name? I need the file extension."</p>
<p>Instead of saying, "I have several arrays, and I want to convert each of them into an object whose keys come from the first array," you should clarify by saying, "I have several arrays, and I want to convert each of them into an object whose keys come from the first array. These arrays come from parsing CSV files using the PapaParse library."</p>
<p>Adding more detail can make things much easier for you and others. Often, there's a better, faster, and easier solution to your problems.</p>
]]></content:encoded></item><item><title><![CDATA[Polymorphic components in React]]></title><description><![CDATA[Every now and then, I encounter a situation where I need a link that looks like a button. Or a button that looks like a link. Isn't it a perfect use case for a polymorphic component? In this article, I'll argue why polymorphic components should be av...]]></description><link>https://typeofweb.com/polymorphic-components-in-react</link><guid isPermaLink="true">https://typeofweb.com/polymorphic-components-in-react</guid><category><![CDATA[Next.js]]></category><category><![CDATA[React]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[JavaScript]]></category><dc:creator><![CDATA[Michał Miszczyszyn]]></dc:creator><pubDate>Sat, 17 Feb 2024 19:13:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1708118404169/49abf69c-bea9-4a17-9dd4-721239cfb68a.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Every now and then, I encounter a situation where I need a link that looks like a button. Or a button that looks like a link. Isn't it a perfect use case for a polymorphic component? In this article, I'll argue <strong>why polymorphic components should be avoided</strong>. I'll also present a pattern that can be used instead.</p>
<h2 id="heading-what-are-polymorphic-components">What are polymorphic components?</h2>
<p>You've probably heard of them. Seen them. Used them. Maybe even tried building one. Simplifying, <strong>polymorphic components render as different elements based on props passed to them</strong>. A typical example is <code>LinkButton</code> that takes an <code>as</code> prop and can either be a <code>button</code> or <code>a</code>:</p>
<pre><code class="lang-ts">&lt;LinkButton
    <span class="hljs-keyword">as</span>=<span class="hljs-string">"a"</span>
    href=<span class="hljs-string">"/"</span>
/&gt;

&lt;LinkButton
    <span class="hljs-keyword">as</span>=<span class="hljs-string">"button"</span>
    onClick={<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
        <span class="hljs-built_in">console</span>.log(e);
    }}
/&gt;
</code></pre>
<p>Mind how the <code>as</code> prop influences other props as well. In this case, when passing <code>as="a"</code>, you'll also need to add <code>href</code>. And when passing <code>as="button"</code>, <code>href</code> will be forbidden. Looks nice.</p>
<h2 id="heading-polymorphic-components-in-typescript">Polymorphic components in TypeScript</h2>
<p>Implementing polymorphic components in TypeScript requires a wee bit of trickery. You can read all about it in this excellent piece by Matt Pocock: <a target="_blank" href="https://www.totaltypescript.com/polymorphic-link-button-components-in-react-and-typescript">Polymorphic Link/Button Components in React &amp; TypeScript</a>. Doesn't look so bad! But…</p>
<h2 id="heading-accidental-and-true-duplication">Accidental and true duplication</h2>
<p>In "Clean Architecture" by Robert C. Martin, the author mentions a concept that deeply resonated with me. <strong>There are two kinds of duplications: true (bad) duplication and false (accidental) duplication.</strong></p>
<blockquote>
<p>True duplication is when the same code or logic is repeated in multiple places.</p>
<p>Accidental duplication is when the same code or logic is repeated in multiple places.</p>
</blockquote>
<p>Just by looking at the code <em>in situ</em>, <strong>it's impossible to say which kind of duplication we're dealing with</strong>. That's why it's so tricky. Only when the codebase starts evolving will we see that accidentally similar pieces of code are changing in a different manner and for various reasons. In contrast, <strong>truly duplicated code always changes in the exact same way at the same time</strong>. That's why it's so important not to avoid duplication at all costs. I suggest waiting and observing which pieces of code will benefit from deduplicating.</p>
<p>What does it have to do with anything? My experience tells me that <strong>polymorphic components are mostly used to deduplicate accidentally similar pieces of code</strong>.</p>
<h2 id="heading-diverging-polymorphic-components">Diverging polymorphic components</h2>
<p>Consider the <code>LinkButton</code> example above. What happens when we need to add <code>disabled</code> state support for both <code>a</code> and <code>button</code>? <strong>The underlying implementations will differ significantly</strong>. In the case of a button, it'll be as simple as adding a <code>disabled</code> attribute to the HTML element. However, for <code>a</code> to be "disabled", we'll need to add an <code>onClick</code> handler whose sole responsibility is to <code>preventDefault</code>. Moreover, the <code>aria-disabled</code> attribute should be added as well. That forces us to write dirty conditional code. Not only will it cause trouble with maintenance in the future, but it'll be instantly annoying when it comes to getting the types right. Try it for yourself!</p>
<p>Even worse, we're mixing responsibilities! <strong>There's no clear separation between the UI and logic</strong>. We can't simply reuse <code>LinkButton</code>'s looks for other elements without modifying it.</p>
<p><strong>What happens when we use the same pattern for more complex cases</strong> if it's a pain in such a simple scenario? Think of <code>&lt;Container as=…&gt;</code> or <code>&lt;Card as=…&gt;</code> – commonly found in different codebases. Yikes.</p>
<h2 id="heading-enter-aschild">Enter <code>asChild</code></h2>
<p>The <code>asChild</code> pattern solves all the mentioned problems. It clearly separates responsibilities. Components built with <code>asChild</code> in mind are <strong>easily reusable for their looks regardless of the component's role</strong>. Let's see it in action:</p>
<pre><code class="lang-ts">&lt;UIButton asChild&gt;
    &lt;a href=<span class="hljs-string">"/"</span>&gt;Go back home&lt;/a&gt;
&lt;/UIButton&gt;
</code></pre>
<pre><code class="lang-ts">&lt;UIButton asChild&gt;
    &lt;button onClick={…}&gt;Do something&lt;/button&gt;
&lt;/UIButton&gt;
</code></pre>
<p>Smooth, right?</p>
<p>The <code>UIButton</code> component is <strong>completely agnostic of the element inside</strong> – an <code>a</code>, <code>button</code>, <code>div</code> or any other. Moreover, no additional HTML tags are rendered.</p>
<h2 id="heading-aschild-and-slot-implemented"><code>asChild</code> and <code>Slot</code> Implemented</h2>
<p>Often, the <code>asChild</code> is optional. When you omit it, a default element is rendered instead of relying on the child. See Radix, for example. However, it stems from my experience that <strong>it's a good practice to make the child element required</strong>. I <strong>never render the default element</strong> and always expect the developers to use <code>children</code>. In this case, the <code>asChild</code> prop is not really required. It's just a pattern. A clear way of indicating that a given component needs to be used in a certain way and doesn't render anything on its own.</p>
<p>Either way, the true magic happens inside the <code>Slot</code> component:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> clsx <span class="hljs-keyword">from</span> <span class="hljs-string">"clsx"</span>;
<span class="hljs-keyword">import</span> { isValidElement, cloneElement, Children, <span class="hljs-keyword">type</span> HTMLAttributes, <span class="hljs-keyword">type</span> ReactElement } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> Slot = <span class="hljs-function">(<span class="hljs-params">{
    children,
    ...props
}: HTMLAttributes&lt;HTMLElement&gt; &amp; {
    children: ReactElement;
}</span>) =&gt;</span> {
    <span class="hljs-keyword">if</span> (isValidElement&lt;HTMLAttributes&lt;HTMLElement&gt;&gt;(children)) {
        <span class="hljs-keyword">return</span> cloneElement(children, {
            ...props,
            ...children.props,
            className: clsx(children.props.className, props.className),
        });
    }
    <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">TypeError</span>(<span class="hljs-string">`Single element child is required in Slot`</span>);
};
</code></pre>
<p>What's going on here? Let's go through the code step by step. We declare a <code>Slot</code> component that takes all props that any element could take. Additionally, we require <code>children</code> of type <code>ReactElement</code> – meaning that <strong>exactly one element needs to be passed to it</strong>.</p>
<p>Then, we do a runtime validation of whether the provided <code>children</code> is, in fact, a valid React element. If it is, we clone that element, merge props passed to Slot and the element, and return it. We also take extra care of the <code>className</code> using the <code>clsx</code> library. You might want to use <code>tailwind-merge</code> instead if that's your jam.</p>
<p>Then, the <code>Slot</code> is reused in different <code>UI…</code> components in the following manner:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> { clsx } <span class="hljs-keyword">from</span> <span class="hljs-string">"clsx"</span>;
<span class="hljs-keyword">import</span> { <span class="hljs-keyword">type</span> ReactElement, <span class="hljs-keyword">type</span> ButtonHTMLAttributes } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { Slot } <span class="hljs-keyword">from</span> <span class="hljs-string">"./Slot"</span>;

<span class="hljs-keyword">interface</span> UIButtonProps
    <span class="hljs-keyword">extends</span> ButtonHTMLAttributes&lt;HTMLButtonElement&gt; {
    asChild: <span class="hljs-literal">true</span>;
    children: ReactElement;
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> UIButton = <span class="hljs-function">(<span class="hljs-params">{
    asChild: _,
    ...props
}: UIButtonProps</span>) =&gt;</span> {
    <span class="hljs-keyword">return</span> (
        &lt;Slot
            {...props}
            className={clsx(
                <span class="hljs-string">"your classes to make it look nice"</span>,
                props.className,
            )}
        /&gt;
    );
};
</code></pre>
<p>We ignore the <code>asChild</code> and pass all remaining props to <code>Slot</code>. We also add classes responsible for how the element is supposed to look and merge them with the parent class names. And this is it!</p>
<h2 id="heading-summary">Summary</h2>
<p><strong>No conditional logic, straightforward separation of concerns, easy maintenance.</strong> There's now only a single reason to modify the <code>UI…</code> components: when we need to change how they look.</p>
<p>The added benefit is how simple it has become to build a Storybook with such components. <strong>No need to mock any logic or providers.</strong> Just use the <code>UI…</code> components with bare-bone elements, and you're done!</p>
<p>I can't imagine building interfaces without the <code>asChild</code> pattern anymore. Do you like it? Let me know in the comments.</p>
]]></content:encoded></item><item><title><![CDATA[Serendipity, or how we've organised a tech conference for 250 people in just 2 months]]></title><description><![CDATA[My favourite word recently is "serendipity".

Serendipity is the luck some people have in creating interesting or valuable things by chance.

How could three people who barely knew each other and were from different cities pull off a conference in tw...]]></description><link>https://typeofweb.com/serendipity-or-how-we-organised-a-tech-conference-for-250-people-in-just-2-months</link><guid isPermaLink="true">https://typeofweb.com/serendipity-or-how-we-organised-a-tech-conference-for-250-people-in-just-2-months</guid><category><![CDATA[conference]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[events]]></category><category><![CDATA[management]]></category><category><![CDATA[TypeScript]]></category><dc:creator><![CDATA[Michał Miszczyszyn]]></dc:creator><pubDate>Wed, 07 Feb 2024 19:20:39 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1707333538510/3b07fbc9-3055-44c1-90e3-7647ce56539e.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>My favourite word recently is "serendipity".</p>
<blockquote>
<p>Serendipity is the luck some people have in creating interesting or valuable things by chance.</p>
</blockquote>
<p>How could three people who barely knew each other and were from different cities pull off a conference in two months?</p>
<p><a target="_blank" href="https://kongresnextjs.pl">Kongres Next.js conference</a> is over. We've welcomed nearly 250 people and had 14 excellent talks in Warsaw on February 2, 2024. You can <a target="_blank" href="https://kongresnextjs.pl/">find the agenda here</a> and read our <a target="_blank" href="https://kongresnextjs.pl/news/transparency-report-2024">Transparency Report</a> about tickets, speakers, costs and income.</p>
<p>I also invite you to read <a target="_blank" href="https://www.aleksandra.codes/kongres-recap">Aleksandra's recap of the event</a>, where she uniquely pinpoints all the good and bad things about the event. But my article is slightly different; I wanted to tell a story about trust, friendship, collaboration, and randomness.</p>
<hr />
<h2 id="heading-zaiste">Zaiste</h2>
<p>So, serendipity. The origin story behind Kongres Next.js goes back a few years back when a Twitter user DM'ed me and praised one of the Open Source projects I was working on. That user was Jakub "Zaiste" Neander. We didn't know each other. To be honest, I've never even heard of him, despite his quite popular tech YouTube channel <a target="_blank" href="https://www.youtube.com/@zaisteprogramming">Zaiste Programming</a>.</p>
<p>At that time, I was at my peak popularity in Poland after just releasing a book about TypeScript. I got tons of messages from people daily, and frankly, I was a bit tired of it. Yet, for some reason, I was in a perfect mood that day, so I quickly replied to Zaiste. What I thought was yet another irrelevant message from a fan turned out to become a collaboration that would go on for years. It was just completely random. Or was it?</p>
<h2 id="heading-aleksandra">Aleksandra</h2>
<p>Being deeply involved in the TypeScript community, I quickly learned about Aleksandra Sikora. First, I found her blog. Then, I found her public initiatives. Without any exaggeration, Aleksandra is one of the most influential people involved in TypeScript on the Globe. I was astonished at how well Aleksandra was known worldwide, yet not at all in Poland. She was, to me, what Ariana Grande is to teenagers: an untouchable star.</p>
<p>However, one day, I just randomly reached out to her. I can only fantasize how many messages she gets every day from random geeks like me, so you can imagine my surprise when she replied almost instantly. And was she friendly and approachable! My hit-or-miss one-in-a-thousand DM eventuated into her being my guest in Breakfast with Programming (pol. <em>Śniadanie z Programowaniem</em>) YouTube talk show. Later, we met IRL during a TypeScript Wrocław meetup that the company I was working for was sponsoring (what are the odds?!). We quickly became not only colleagues but, I guess, friends.</p>
<h2 id="heading-meetup-nextjs">Meetup Next.js</h2>
<p>Jakub and I started collaborating on a <a target="_blank" href="https://nextjsmasters.pl">Next.js cohort-based online course</a>. We've produced and sold four homemade editions, and when Next.js 13 was released, we decided to go full professional: start from scratch, focus on App Router, rerecord everything, and work with a well-qualified video-producing company, <a target="_blank" href="https://brave.courses/">Brave Courses</a>.</p>
<p>A bit of context: Zaiste has 10 new ideas every second, to most of which I instantly say bluntly "<em>NO</em>". One day, he just randomly said, "<em>Let's start this edition of the course with a meetup</em>". My thoughts were, "<em>No, this is stupid, it's not enough time, it doesn't make sense, I'd rather not</em>", so I indifferently answered, "<em>Yeah, let's do that</em>", hoping he'd just forget about it in a few hours. Well, spoiler alert, he didn't.</p>
<p>It was almost the end of November 2023, and we were meeting with some friends in Warsaw. I randomly remembered attending a conference in a small and cosy cinema in 2016. We called them. It was available to rent on the day Zaiste wanted to organise the meetup, and it was surprisingly affordable. We've booked it immediately and got the balls rolling. How random?</p>
<h2 id="heading-kongres-nextjs">Kongres Next.js</h2>
<p>The clock was ticking; we had just around 60 days to organise and prepare everything with the holiday season in the middle. We were brainstorming about good speakers we could've invited, and I instantly thought of Aleksandra. She rejected my request to speak at the event, but she offered so much more: she'd help us organise it and reach out to many renowned speakers from Europe that she personally knew. We also changed the idea from a local meetup to something way more ambitious: an international conference.</p>
<p>To make matters even more complex, the conference was supposed to happen in Warsaw, as we thought it was the best tech hub in Poland, but none of us live there – we all had about a 4-hour drive to the venue.</p>
<p>We've built the website in a few hours, guessed proper ticket prices, integrated Stripe… Not more than two days later, we were ready to start the presale, and Aleksandra officially joined the conference board. Everything that happened during the next two months was either by sheer luck, through our friends' help, or by YOLO'ing it.</p>
<p>5 days before the event, I thought, "Oh no, we forgot about the badges 😱". So Ola and I designed them in Figma in one hour. I ordered some recycled paper, and my wife printed and cut them to the proper sizes.</p>
<p>3 days before the event, I thought, "Wait, we don't have lanyards for the badges 🫠". Panicking, I made a few calls and found a company in Warsaw that would deliver 300 eco-paper lanyards to our hotel the day before the conference.</p>
<h2 id="heading-trust-and-friendship">Trust and friendship</h2>
<p>I'm talking a lot about what I've done; what did Jakub and Aleksandra do? Well, a lot of other things! Took care of speakers, venue, lunch… But the most beautiful aspect of our cooperation was our complete trust in each other. Despite my anxiety and being a total control freak, I felt I didn't need to check on them, ask every other day whether things have progressed, and that they're doing the best they can. I didn't even need to know precisely what they were up to because I could've blindly trusted them. Each of us was able to make calls independently, and we consulted on more significant decisions. Without any pretentiousness, I think it was the most perfect teamwork I have ever experienced.</p>
<h2 id="heading-summary">Summary</h2>
<p>How did the event go? Not without problems, obviously. But all in all, it was great. The atmosphere of coziness, unbelievable meetup vibes, 14 incredible talks, fantastic networking, and a lively after-party.</p>
<p>Is there a moral to this story? It's truly shocking how many random things had to coincide for this event to happen. So, I guess the moral of this story is that the best things in life happen by accident. Serendipity.</p>
]]></content:encoded></item><item><title><![CDATA[Relative perception of time: a mathematical model]]></title><description><![CDATA[Many people think that as we age, time appears to fly by faster. We have countless memories from our early years, and later on, it gets tough to tell one year apart from another. But is this just how we perceive things? Could there be an explanation ...]]></description><link>https://typeofweb.com/relative-perception-of-time-a-mathematical-model</link><guid isPermaLink="true">https://typeofweb.com/relative-perception-of-time-a-mathematical-model</guid><category><![CDATA[psychology]]></category><category><![CDATA[Mathematics]]></category><category><![CDATA[Maths]]></category><dc:creator><![CDATA[Michał Miszczyszyn]]></dc:creator><pubDate>Mon, 08 Jan 2024 12:51:16 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1704718222984/83c4d282-7aac-4608-bc2d-8afc3339c72c.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Many people think that as we age, time appears to fly by faster. We have countless memories from our early years, and later on, it gets tough to tell one year apart from another. But is this just how we perceive things? Could there be an explanation for this mind-boggling phenomenon?</p>
<h2 id="heading-event-density-and-time-perception"><strong>Event Density and Time Perception</strong></h2>
<p>When I discussed this with several people, they claimed that as children, they had many more interesting and new experiences, while today, life seems dull. This idea is fascinating, but it doesn't entirely reveal why childhood events would impact our perception of time in later years!</p>
<p>Many people also suggest that the time we perceive is relative to the time we've experienced. For example, a year perceived by a 4-year-old child is a whopping 25% of their entire life, while for an adult, it would be less than 5%.</p>
<h2 id="heading-mathematical-model-of-relative-time">Mathematical Model of Relative Time</h2>
<p>Let's rephrase the above statement to facilitate writing it as an equation: <strong>The perceived speed of time passing decreases with age.</strong></p>
<p>In scientific publications, I discovered a proposal that this relationship was proportional to the absolute passage of time (objectively measured, e.g., in years) [Doob (1971), Janet (1877)]. However, according to our assumptions, <strong>we experience relative time</strong>, not objective time! Let's attempt to express this mathematically.</p>
<p>Assume that objective time is denoted by <code>O</code>, and subjective time (perceived by us, relative) is represented as <code>S</code>.</p>
<p><code>dO</code> and <code>dS</code> are infinitesimal time periods – <code>dS</code> signifies the passage of time <code>dO</code> as experienced by us.</p>
<p>As previously mentioned, the perceived passage of time decreases with the amount of time lived (also subjective!), so <code>dS</code> decreases as <code>S</code> increases. Consequently:</p>
<p>$$dS = dO\frac{K}{S}$$</p><p>where <code>K</code> is some constant that we'll get rid of shortly. Additionally, we assume that at the moment of birth, both <code>O</code> and <code>S</code> are equal to 0:</p>
<p>$$O_0=S_0=0$$</p><p>The remaining task is to transform the equation and perform integration:</p>
<p>$$\begin{aligned} dS &amp;= dO \cdot \frac{K}{S} \\\\ SdS &amp;= K dO \\\\ \int_0^S SdS &amp;= K \int_0^O dO \\\\ \frac{1}{2} S^2 &amp;= KO \\\\ S^2 &amp;= 2KO \\\\ S = \sqrt{2KO} &amp;\ \ \ \ \ S \geq 0;\ \ O \geq 0 \\\\ \\\\ dS &amp;= dO \frac{K}{S} \\\\ dS &amp;= dO \frac{K}{\sqrt{2KO}} \\\\ dS &amp;= dO \frac{\sqrt{K^2}}{\sqrt{2KO}} \\\\ dS &amp;= dO \sqrt{\frac{K^2}{2KO}} \\\\ dS &amp;= \sqrt{\frac{K}{2O}}\ dO \\\\ \end{aligned}$$</p><p>Now, let's assume the same time period <code>dO</code> is perceived at two different moments in life, <code>O1</code> and <code>O2</code>, and determine the proportion between the relative perceptions:</p>
<p>$$\begin{aligned} dS &amp;= \sqrt{\frac{K}{2O}} dO \\\\ \\\\ dS_1 &amp;= \sqrt{\frac{K}{2O_1}} dO \\\\ dS_2 &amp;= \sqrt{\frac{K}{2O_2}} dO \\\\ \\\\ \frac{dS_1}{dS_2} &amp;= \frac{\sqrt{\frac{K}{2O_1}} dO}{\sqrt{\frac{K}{2O_2}} dO} \\\\ \frac{dS_1}{dS_2} &amp;= \frac{\sqrt{\frac{K}{2O_1}}}{\sqrt{\frac{K}{2O_2}}} \\\\ \frac{dS_1}{dS_2} &amp;= \sqrt{\frac{O_2}{O_1}} \\\\ \end{aligned}$$</p><p>Isn't it fascinating that, for an average person, their subjective perception of the same time period shifts in a way that's <strong>inversely proportional to the square root of their life's length?</strong></p>
<h2 id="heading-application">Application</h2>
<p>Now let's consider how the perception of a year in one's life changes for two people who are 10 and 40 years old. By substituting into the derived formula, we obtain:</p>
<p>$$\sqrt{\frac{40}{10}} = \sqrt{4} = 2$$</p><p>This means that <strong>for a 40-year-old, a year will pass twice as fast as a 10-year-old</strong>!</p>
<h2 id="heading-relative-perception-of-time"><strong>Relative perception of time</strong></h2>
<p>We can also take it a step further – let's consider what part of our life (relatively) we have already experienced. If we go back to one of the equations for a moment…</p>
<p>$$S = \sqrt{2KO} \ \ \ \ \ S \geq 0;\ \ O \geq 0$$</p><p>Let's denote the moment of death as <code>Sd</code> and <code>Od</code> (perceived relatively and objectively, respectively), substitute into the equation above, and divide by the same equation:</p>
<p>$$\begin{aligned} S_d &amp;= \sqrt{2KO_d} \\\\ S &amp;= \sqrt{2KO} \\\\ \\ \frac{S}{S_d} &amp;= \frac{\sqrt{2KO}}{\sqrt{2KO_d}} \\\\ \frac{S}{S_d} &amp;= \sqrt{\frac{O}{O_d}} \end{aligned}$$</p><p>To simplify the calculations, let's first assume that a person lives for 100 years (<code>Od=100</code>), and the person being analyzed is 25 years old (<code>O=25</code>):</p>
<p>$$\sqrt{\frac{25}{100}} = \frac{5}{10} = 0,5$$</p><p>This means that <strong>25-year-olds have already experienced 50% of their life, at least in their perception</strong>, even though they still have 75% of their life measured in objective years ahead of them.</p>
<p>Now let's analyze me. Assuming that the average life expectancy in Europe is 80 years, and I am 31, then:</p>
<p>$$\sqrt{\frac{31}{80}} = 62\%$$</p><p>Well, most of my life is already behind me 🙃</p>
<h2 id="heading-relative-time-vs-objective-time">Relative time vs. objective time</h2>
<p>Fortunately, as rightly pointed out, <strong>the subjective time we have left will never be less than half of the objective time until death</strong>. Let's try to prove it!</p>
<p>If the relative and objective times until death are given by the equations:</p>
<p>$$\begin{aligned} 1 - \sqrt{\frac{O}{O_d}} \\\\ 1 - \frac{O}{O_d} \end{aligned}$$</p><p>Let's start with the two previously discussed examples:</p>
<p>$$\begin{aligned} 1 - \sqrt{\frac{25}{100}} &amp;= 0.5 \\\\ 1 - \frac{25}{100} &amp;= 0.75 \end{aligned}$$</p><p>If we divide one by the other, it turns out that the result is greater than half:</p>
<p>$$\begin{aligned} \frac{0.5}{0.75} &amp;= 0.\overline{6} \end{aligned}$$</p><p>Similarly, for the second example:</p>
<p>$$\begin{aligned} 1 - \sqrt{\frac{31}{80}} &amp;\approx 0.3775050201 \\\\ 1 - \frac{31}{80} &amp;= 0.6125 \\\\ \frac{0.4193027447}{0.6627906977} &amp;\approx 0.6163347267 \end{aligned}$$</p><p>The theorem seems to hold true. Let's try to generalize:</p>
<p>$$\begin{aligned} \frac{1 - \sqrt{\frac{AGE}{DEATH}}}{1 - \frac{AGE}{DEATH}} \geq \frac{1}{2} \end{aligned}$$</p><p><code>AGE</code> is the variable here, and <code>DEATH</code> is the constant. In that case, we can substitute:</p>
<p>$$\begin{aligned} x = \frac{AGE}{DEATH}\ \ \ \ \ 0 \leq x \leq 1 \end{aligned}$$</p><p>$$\frac{1 - \sqrt{x}}{1 - x} &gt; 1/2 \ \ \ \ \ \ \ \ \ \cdot \frac{1 + \sqrt{x}}{1 + \sqrt{x}}$$</p><p>$$\frac{(1 - \sqrt{x})(1 + \sqrt{x})}{(1 - x)(1 + \sqrt{x})}$$</p><p>$$\frac{1 - x}{(1-x)(1+\sqrt{x})}$$</p><p>$$\frac{1}{1 + \sqrt{x}}$$</p><p>$$\frac{1}{1 + \sqrt{x}} \geq 1/2$$</p><p>$$1 + \sqrt{x} \leq 2$$</p><p>$$\sqrt{x} \leq 1$$</p><p>$$0 \leq x \leq 1$$</p><p>$$\therefore\ \ \ 0 \leq \sqrt{x} \leq 1$$</p><p>$$\blacksquare\ QED$$</p><h2 id="heading-summary">Summary</h2>
<p>Interestingly, the above theories and calculations have also been preliminarily confirmed experimentally [Robert Lemlich – Subjective acceleration of time with aging, 1975]. How much of your life have you already experienced in relative units?</p>
<p>Did you enjoy this post? Let us know in the comments.</p>
]]></content:encoded></item><item><title><![CDATA[Implementing Optimistic Updates in Next.js using React 18's `useOptimistic` Hook]]></title><description><![CDATA[In this article, I'll guide you through the concept of optimistic updates in Next.js using React 18. We will build an interactive tweet-like component and enhance the user experience with the help of the useOptimistic hook. Let's get started!
What ar...]]></description><link>https://typeofweb.com/implementing-optimistic-updates-in-nextjs-using-react-18s-useoptimistic-hook</link><guid isPermaLink="true">https://typeofweb.com/implementing-optimistic-updates-in-nextjs-using-react-18s-useoptimistic-hook</guid><category><![CDATA[Next.js]]></category><category><![CDATA[React]]></category><category><![CDATA[TypeScript]]></category><dc:creator><![CDATA[Michał Miszczyszyn]]></dc:creator><pubDate>Wed, 20 Dec 2023 18:46:15 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1703160028464/a2155226-bf4f-424f-b2a2-2dffa373e916.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In this article, I'll guide you through the concept of optimistic updates in Next.js using React 18. We will build an interactive tweet-like component and enhance the user experience with the help of the <code>useOptimistic</code> hook. Let's get started!</p>
<h2 id="heading-what-are-optimistic-updates"><strong>What are Optimistic Updates?</strong></h2>
<p>The term "optimistic updates" refers to an application behaviour in which we don't need to wait for asynchronous operations to complete before seeing their results. It's a little trick – the operation is performed in the background while the interface is updated immediately.</p>
<h3 id="heading-optimistic-updates-and-ux"><strong>Optimistic Updates and UX</strong></h3>
<p>Let's assume we're building a social media platform. When a user likes a post, they have to wait a second or two for the server to confirm that the like was successful. This results in a poor user experience (UX). Can we improve it somehow?</p>
<p>Yes! We can apply Optimistic Updates and immediately update the like counter after the user clicks the button. In the background, we'll send the request to the server. It makes the post appear as if the server operation was immediately successful without waiting for its completion.</p>
<h3 id="heading-optimistic-updates-when-theyre-successful"><strong>Optimistic Updates, when they're successful</strong></h3>
<p>Additionally, when the server update is ultimately successful, we could also inform the user about it. Depending on the specific use case, app, and design, we can do it in several ways:</p>
<ul>
<li><p>While the asynchronous operation is in progress, we display a tiny spinner (animation). Either near the "like" button or, for example, in the lower right corner of the screen. When the operation is successful, the spinner simply disappears.</p>
</li>
<li><p>We perform an optimistic update, but the view slightly differs from the final one. For instance, the like counter increases immediately, but it's slightly greyed out until the response from the server arrives.</p>
</li>
<li><p>After the server query is completed, we display a message to the user. We can do this in different manners, depending on the importance of the operation being performed. Toasts are often used in this case.</p>
</li>
</ul>
<h3 id="heading-optimistic-updates-when-they-fail"><strong>Optimistic Updates, when they fail</strong></h3>
<p>Most user actions will be successful. However, we cannot completely ignore the possibility that liking a post might fail for some reason. It could be a temporary loss of the Internet connection or an intermittent server outage. What then?</p>
<p>A lot depends on the seriousness of the situation and the operation being performed by the user. Going back to the example of likes, we can simply undo the optimistic update and decrease the like counter. However, for more important actions like sending an email or deleting a document, we should consider displaying a notification and informing the user about the consequences or the need to take action.</p>
<h2 id="heading-reactjs-implementation"><strong>React.js Implementation</strong></h2>
<h3 id="heading-useoptimistic-in-reactjs-and-nextjs-14"><code>useOptimistic</code> <strong>in React.js and Next.js 14</strong></h3>
<p>In early May 2023, the React team released experimental support for the built-in hook called <code>useOptimisticState</code>, which was later renamed to a simpler <code>useOptimistic</code>. Its use cases include implementing, well, optimistic updates, but not just that. Essentially, <code>useOptimistic</code> allows for the implementation of any "pending" state, that doesn't necessarily have to be related to sending requests to a server.</p>
<p>Today, the hook is available in canary and experimental React releases as well as Next.js 13 and 14. This is where we'll be using it.</p>
<h3 id="heading-lets-get-started-with-nextjs-14"><strong>Let's get started with Next.js 14</strong></h3>
<p>We're beginning by creating a new project in Next.js 14:</p>
<pre><code class="lang-bash">pnpm create next-app use-optimistic
</code></pre>
<p>Now we just need to answer a few simple questions and we're good to go!</p>
<h3 id="heading-naive-implementation"><strong>Naïve Implementation</strong></h3>
<p>Let's start with a naïve implementation. Let's assume that there are three functions used for operating on likes:</p>
<ol>
<li><p><code>getLikes()</code></p>
</li>
<li><p><code>like()</code></p>
</li>
<li><p><code>dislike()</code></p>
</li>
</ol>
<p>In the main component, we fetch the number of likes and information on whether we have already liked the current post or not:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> { likes, isLiked } = <span class="hljs-keyword">await</span> getLikes();
</code></pre>
<p>Next, we pass this information to the <code>LikesCounter</code> component:</p>
<pre><code class="lang-typescript">&lt;LikesCounter
  likes={likes}
  isLiked={isLiked}
  onLiked={<span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-string">"use server"</span>;
    revalidatePath(<span class="hljs-string">"/"</span>);
  }}
/&gt;
</code></pre>
<p>The simplest implementation of this component is just a basic form with a button:</p>
<pre><code class="lang-typescript">&lt;form action={<span class="hljs-keyword">async</span> () =&gt; {
  <span class="hljs-keyword">if</span> (isLiked) {
    <span class="hljs-keyword">await</span> dislike();
  } <span class="hljs-keyword">else</span> {
    <span class="hljs-keyword">await</span> like();
  }
  <span class="hljs-keyword">await</span> onLiked();
}}&gt;
&lt;button <span class="hljs-keyword">type</span>=<span class="hljs-string">"submit"</span> aria-label={isLiked ? <span class="hljs-string">"Dislike"</span> : <span class="hljs-string">"Like"</span>}&gt;
  {likes}
&lt;/button&gt;
</code></pre>
<p>Unfortunately, as anticipated, this doesn't work very well. After clicking the button, it takes a few seconds before the counter updates. Moreover, we should also handle an intermediate state – waiting for the response from the API – and during this time, ignore further button clicks.</p>
<h3 id="heading-implementation-with-useoptimistic"><strong>Implementation with</strong> <code>useOptimistic</code></h3>
<p><code>useOptimistic</code> is a hook that takes two arguments: the initial state (e.g. from the server) and a reducer – a function that takes the state and an action and returns a new state. <code>useOptimistic</code> returns a tuple with the optimistic state and a function to update it.</p>
<p>An example usage looks like this:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> [optimisticLikes, setOptimisticLikes] = useOptimistic(
  likes,
  <span class="hljs-function">(<span class="hljs-params">likes, action: <span class="hljs-string">"LIKE"</span> | <span class="hljs-string">"DISLIKE"</span></span>) =&gt;</span> {
    <span class="hljs-keyword">if</span> (action === <span class="hljs-string">"LIKE"</span>) {
      <span class="hljs-keyword">return</span> likes + <span class="hljs-number">1</span>;
    } <span class="hljs-keyword">else</span> {
      <span class="hljs-keyword">return</span> likes - <span class="hljs-number">1</span>;
    }
  },
);
</code></pre>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">We're doing a bit of <em>ars gratia artis</em> for educational purposes – after all, we have only two actions, so the entire reducer could be vastly simplified. We could even be tempted to just pass <code>1</code> and <code>-1</code> to it instead of <code>action</code>. Bear with me.</div>
</div>

<p>Now we need to modify the function for updating likes. Before liking a post, we call <code>setOptimisticLikes("LIKE")</code>, and if it was already liked, we use <code>setOptimisticLikes("DISLIKE")</code>. Additionally, instead of <code>likes</code>, we use <code>optimisticLikes</code> for displaying. The entire code listing is shown below, although it's worth noting that not everything works as it should just yet…</p>
<pre><code class="lang-typescript">&lt;form action={<span class="hljs-keyword">async</span> () =&gt; {
  <span class="hljs-keyword">if</span> (isLiked) {
    setOptimisticLikes(<span class="hljs-string">"DISLIKE"</span>);
    <span class="hljs-keyword">await</span> dislike();
  } <span class="hljs-keyword">else</span> {
    setOptimisticLikes(<span class="hljs-string">"LIKE"</span>);
    <span class="hljs-keyword">await</span> like();
  }
  <span class="hljs-keyword">await</span> onLiked();
}}&gt;
&lt;button <span class="hljs-keyword">type</span>=<span class="hljs-string">"submit"</span> aria-label={isLiked ? <span class="hljs-string">"Dislike"</span> : <span class="hljs-string">"Like"</span>}&gt;
  {optimisticLikes}
&lt;/button&gt;
</code></pre>
<p>Rapidly clicking the button now causes the counter to decrease: 20, 19, 18, 17, 16, 15, 14… Additionally, we never update the value of <code>isLiked</code>. We need to modify our state and reducer slightly to fix that:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> [optimisticState, setOptimisticLikes] = useOptimistic(
  { likes, isLiked },
  <span class="hljs-function">(<span class="hljs-params">state, action: <span class="hljs-string">"LIKE"</span> | <span class="hljs-string">"DISLIKE"</span></span>) =&gt;</span> {
    <span class="hljs-keyword">if</span> (action === <span class="hljs-string">"LIKE"</span>) {
      <span class="hljs-keyword">return</span> { likes: state.likes + <span class="hljs-number">1</span>, isLiked: <span class="hljs-literal">true</span> };
    } <span class="hljs-keyword">else</span> {
      <span class="hljs-keyword">return</span> { likes: state.likes - <span class="hljs-number">1</span>, isLiked: <span class="hljs-literal">false</span> };
    }
  },
);
</code></pre>
<p>Now, instead of a simple state, we have an object with two properties: <code>isLiked</code> and <code>likes</code> – which correspond to those coming from the server. Another important change is updating <code>isLiked</code> and using it in the components below:</p>
<pre><code class="lang-typescript">&lt;form action={<span class="hljs-keyword">async</span> () =&gt; {
  <span class="hljs-keyword">if</span> (optimisticState.isLiked) {
    setOptimisticLikes(<span class="hljs-string">"DISLIKE"</span>);
    <span class="hljs-keyword">await</span> dislike();
  } <span class="hljs-keyword">else</span> {
    setOptimisticLikes(<span class="hljs-string">"LIKE"</span>);
    <span class="hljs-keyword">await</span> like();
  }
  <span class="hljs-keyword">await</span> onLiked();
}}&gt;
  &lt;button
    <span class="hljs-keyword">type</span>=<span class="hljs-string">"submit"</span>
    aria-label={optimisticState.isLiked ? <span class="hljs-string">"Dislike"</span> : <span class="hljs-string">"Like"</span>}&gt;
    {optimisticState.likes}
  &lt;/button&gt;
&lt;/form&gt;
</code></pre>
<p>Now, quickly clicking the button causes the counter to toggle up and down – just as we would expect!</p>
<h3 id="heading-actions-queueing"><strong>Actions Queueing</strong></h3>
<p>You might now be wondering, "If I click the button quickly 10 times, what will be the final state on the server side?" Don't worry, React.js and Next got you covered and take care of the correctness for you. Consecutive clicks cause an immediate interface change (optimistically), but the server requests are queued and sent one by one. There's no risk that rapid button clicking will result in requests being sent out of order.</p>
<p>We can observe this behaviour precisely in the Network tab in devtools. We see a beautiful waterfall of requests – they are not executed in parallel but one after another.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1703094814493/9580f8f2-3357-493f-8901-d79ef1f76096.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-state-rebasing"><strong>State rebasing</strong></h3>
<p>An interesting and yet underrated behaviour of <code>useOptimistic</code> is recalculating (rebasing) of the optimistic state when the source state changes. Imagine a scenario: a post has 20 likes, and we like it. The counter optimistically changes to 21 immediately. However, while our request is being executed to the server, an update to the number of likes arrives: it turns out there are now 44. What should the optimistic interface display?</p>
<p>In many simple implementations, it would show 44. However, I expect 45 (the number from the server + our like "in flight"). Fortunately, React.js saves the day again and takes care of this for us. We don't even have to do anything!</p>
<h2 id="heading-summary"><strong>Summary</strong></h2>
<p>Undoubtedly, many of you have implemented optimistic updates on your own, but the complexity of this problem and the numerous edge cases to consider make it a challenging task. Fortunately, <code>useOptimistic</code> is built into React.js and significantly simplifies the implementation of applications that adhere to good UX principles.</p>
<h2 id="heading-sources">Sources</h2>
<ol>
<li><p><a target="_blank" href="https://github.com/HyperFunctor/optimistic-updates">https://github.com/HyperFunctor/optimistic-updates</a></p>
</li>
<li><p><a target="_blank" href="https://kongresnextjs.pl/">https://kongresnextjs.pl/</a></p>
</li>
<li><p><a target="_blank" href="https://www.nextjsmasters.pl/">https://www.nextjsmasters.pl/</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/facebook/react/pull/26740">https://github.com/facebook/react/pull/26740</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/facebook/react/pull/26772">https://github.com/facebook/react/pull/26772</a></p>
</li>
<li><p><a target="_blank" href="https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations#optimistic-updates">https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations#optimistic-updates</a></p>
</li>
</ol>
]]></content:encoded></item><item><title><![CDATA[Essential Guide to Planning and Executing a Conference]]></title><description><![CDATA[Are you considering organizing a conference or event? Worried about whether you've thought of everything? Will everything be perfect? You're not alone! After five years of organizing meet.js Gdańsk meetings, which attract over 150 people each time, a...]]></description><link>https://typeofweb.com/essential-guide-to-planning-and-executing-a-conference</link><guid isPermaLink="true">https://typeofweb.com/essential-guide-to-planning-and-executing-a-conference</guid><category><![CDATA[conference]]></category><category><![CDATA[events]]></category><category><![CDATA[Meetup]]></category><category><![CDATA[guide]]></category><dc:creator><![CDATA[Michał Miszczyszyn]]></dc:creator><pubDate>Tue, 12 Dec 2023 16:09:15 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1702398654176/66a16f84-ff72-4ae1-95f4-40fd3ae0da70.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Are you considering organizing a conference or event? Worried about whether you've thought of everything? Will everything be perfect? You're not alone! After five years of organizing meet.js Gdańsk meetings, which attract over 150 people each time, and after two editions of meet.js Summit Gdańsk, with a total of nearly 1000 participants, as well as attending countless conferences worldwide, I want to share my experiences. Here's the essential guide to planning and executing a conference.</p>
<h2 id="heading-organizing-conferences"><strong>Organizing Conferences</strong></h2>
<p>Organizing meetings and conferences is no easy task. There are countless details to remember! Even if you do everything 100% right, a lot depends on others – suppliers, service providers, sponsors…</p>
<p>In this post, I have gathered a summary of my experiences from the past 5 years. The entry is as practical as possible – it serves as a simple <em>checklist</em> for conference organizers. <strong>What should you keep in mind and consider when organizing events?</strong></p>
<h2 id="heading-general">General</h2>
<ul>
<li><p>You need <strong>at least six months</strong> to organize any event of this size.</p>
</li>
<li><p><strong>Consider the purpose of your conference</strong>. Are you focusing on networking or knowledge?</p>
</li>
<li><p>Make sure that <strong>every participant</strong> at the conference feels comfortable.</p>
</li>
<li><p><strong>Implement and enforce a Code of Conduct.</strong> It's more important than you might think.</p>
</li>
<li><p>The Code of Conduct must include <strong>specific steps</strong> you will take in case of violations, as well as a clear and straightforward way to report issues.</p>
</li>
<li><p>At times, it can be difficult to identify all the issues, and groupthink may prove to be quite detrimental. <strong>Choose co-organizers who are different from you</strong>, and who will sometimes disagree with you. This way, you'll identify problems that you might not have noticed otherwise.</p>
</li>
</ul>
<h2 id="heading-finances">Finances</h2>
<ul>
<li><p><strong>Operate with net amounts only</strong> – everywhere and always. Some services have different VAT rates, so if you work with gross amounts, you might end up at a loss.</p>
</li>
<li><p>It's ideal to issue all invoices for sponsors within the same accounting period (month or quarter) as the conference. By doing so, you won't pay huge income tax, as you'll spend the money before the next accounting period, simplifying your financial management.</p>
</li>
<li><p>To confirm agreements, send sponsors pro forma invoices. These resemble invoices but are not actual invoices :)</p>
</li>
</ul>
<ul>
<li><p>Begin <strong>collecting contacts and quotes from various companies early</strong>: print houses, videographers, photographers — so you won't be caught off guard by prices later on.</p>
</li>
<li><p>Avoid promising things you don't have the funds for yet. For instance, only announce a second night's accommodation for speakers, breakfast, or complimentary alcohol at the after-party when you're certain you can afford it.</p>
</li>
<li><p><strong>A website is crucial</strong> – the sooner it's up and running, the better – even without highly detailed information.</p>
</li>
<li><p>You can attract sponsors even before taking any other steps, based on the strength of your brand (in my case – meet.js or Type of Web).</p>
</li>
<li><p>You can sell the first batch of tickets even before the agenda is made available.</p>
</li>
<li><p>Always gather at least one <strong>counteroffer</strong> before making a purchase. Frequently, you'll discover that another company offers a lower price, and you'll also generate great ideas and learn what you truly want during discussions with different providers.</p>
</li>
<li><p>Keep in mind - it never hurts to ask for a discount. Negotiate as much as possible.</p>
</li>
<li><p><strong>If you have specific expectations, make sure to clearly communicate them</strong>. Rather than relying on verbal communication, put your expectations in writing, such as in an email, or ideally, in a contract.</p>
</li>
<li><p>Avoid making arrangements over the phone, as words can be fleeting and easily forgotten or misinterpreted. <strong>After each conversation, send an email summarizing the agreements</strong>.</p>
</li>
</ul>
<h2 id="heading-printouts">Printouts</h2>
<ul>
<li><p>Badges</p>
<ul>
<li><p><strong>Order personalized badges with participants' names</strong>.</p>
</li>
<li><p>Ask about the <strong>printing deadline</strong>. Typically, you should provide the print house with a list of participants at least a week before the conference.</p>
</li>
<li><p>Don't forget about badges for speakers, assistants, and organizers.</p>
</li>
<li><p>Print some blank badges for last-minute guests to write their names on.</p>
</li>
<li><p>The agenda on the back side <strong>should be upside down</strong> to make it easier to read.</p>
</li>
<li><p>Verify whether the badges will be pre-inserted into their holders. This is a lot of work, so plan accordingly.</p>
</li>
</ul>
</li>
<li><p>Lanyards</p>
<ul>
<li>Order extra lanyards to ensure you can freely distribute them to attendees without any stress.</li>
</ul>
</li>
<li><p>Bags and T-shirts</p>
<ul>
<li><p><strong>Bags are fantastic</strong> because they can hold flyers and sponsor gadgets.</p>
</li>
<li><p>Although conference T-shirts may not be widely worn, they are a pleasant and affordable addition – worth considering.</p>
</li>
<li><p>If you decide to order T-shirts, remember to provide a selection of different sizes <strong>and cuts</strong>.</p>
</li>
</ul>
</li>
</ul>
<h2 id="heading-venue">Venue</h2>
<ul>
<li><p>Verify the availability of equipment (screen, sound, etc.)</p>
</li>
<li><p>Will there be someone to manage audio/video, or are you only provided with a projector and speaker?</p>
</li>
<li><p>Verify if presentations can be played using HDMI.</p>
</li>
<li><p>Determine the format and resolution — 4:3/16:9, 800x600, or perhaps HD, etc. <strong>Inform the speakers about this!</strong></p>
</li>
<li><p>Verify the actual capacity (for instance, a room may have 417 seats on paper, but 2 are for disabled individuals and 24 for technical staff, leaving only 391 available).</p>
</li>
<li><p>Ensure there is space for coffee and lunch; identify the specific location; accommodating 400 people is no small task.</p>
</li>
<li><p>Determine EXACTLY how much space will be available for sponsors. Today you have 10 sponsors, but will there be room for 11? 12? 13? Plan to ensure they all fit.</p>
</li>
<li><p>Prepare a display board to be shown on the projector during breaks between presentations. Include sponsor logos.</p>
</li>
<li><p>Create a map of the venue for sponsors, including dimensions and booth locations.</p>
</li>
<li><p>Carefully consider how many people are needed to help manage everything. At least:</p>
<ul>
<li><p>3 for registration</p>
</li>
<li><p>2 for the room</p>
</li>
<li><p>1 to assist speakers</p>
</li>
<li><p>1 to assist sponsors</p>
</li>
</ul>
</li>
</ul>
<h2 id="heading-sponsors">Sponsors</h2>
<ul>
<li><p>Prepare a variety of sponsorship packages; suggested:</p>
<ul>
<li><p>main</p>
</li>
<li><p>afterparty</p>
</li>
<li><p>just a booth</p>
</li>
<li><p>without a booth (an affordable option)</p>
</li>
</ul>
</li>
<li><p>Describe in detail what is included in each package.</p>
</li>
<li><p>Accurately measure, count, draw, and describe the space and dimensions of their booths to prevent misunderstandings.</p>
</li>
<li><p>Following each phone call, send an email summarizing the conversation.</p>
</li>
<li><p>Never respond to the question "Will the booth fit?" without knowing its dimensions. It may seem obvious, but in practice, a casual "it will definitely fit" mentioned over the phone can lead to numerous issues later on.</p>
</li>
<li><p>Create and expand a <strong>contact list of sponsors</strong> that can be used in the future.</p>
</li>
<li><p><strong>Ask speakers</strong> if the companies they work for would like to sponsor the event.</p>
</li>
<li><p>Remember to provide parking spaces (free of charge!) for sponsors.</p>
</li>
<li><p>Print (or order from a print house) signs with directions: auditorium, cloakroom, lunch, restroom, etc…</p>
</li>
</ul>
<h2 id="heading-catering">Catering</h2>
<ul>
<li><p>If you plan to provide catering for participants, be aware that a specific venue often has one company holding exclusive rights to such services. To avoid surprises, inquire about the situation before signing a contract.</p>
</li>
<li><p>If you don't want to use on-site catering services, you can organize lunch, for example, in the form of food trucks outside the venue or at a restaurant across the street.</p>
</li>
<li><p>If possible, provide a coffee break or breakfast before the first presentation.</p>
</li>
<li><p>Even if you don't offer free coffee breaks or meals to participants, make sure there is a store, cafe, or restaurant on-site or close by.</p>
</li>
<li><p>When negotiating prices, inquire about various options – for example, coffee is often priced for continuous availability throughout the entire event. If you request a quote for coffee only during breaks between presentations, you might be pleasantly surprised.</p>
</li>
</ul>
<h2 id="heading-after-party">After party</h2>
<ul>
<li><p>Determine your budget.</p>
</li>
<li><p>Free food and drinks are nice, but they're <strong>not required</strong> :)</p>
</li>
<li><p>Try to estimate as accurately as possible how many people will attend. Based on my experience, typically ¼ to ⅓ of conference attendees join the party.</p>
</li>
<li><p>The location should be close to the conference venue. Ideally, it should be within walking distance.</p>
</li>
<li><p>Is there music?</p>
</li>
<li><p>Are there other attractions?</p>
</li>
<li><p>Will the place be exclusive for your event?</p>
</li>
<li><p>How much food is needed?</p>
</li>
<li><p>Can a drink limit be set, after which attendees pay for their own drinks?</p>
</li>
<li><p>Request at least two proposals – a "luxurious" option and a "budget-friendly" one.</p>
</li>
</ul>
<h2 id="heading-speakers">Speakers</h2>
<ul>
<li><p>Call For Papers? If so, plan it well in advance.</p>
</li>
<li><p>Ensure a maximally <strong>objective selection process</strong>. Those who choose presentations from CFP <strong>should not have access</strong> to the presenter's name or photo.</p>
</li>
<li><p>Encourage participation from individuals with no prior experience. They often create the most engaging presentations!</p>
</li>
<li><p>If you're hosting a Call For Papers, refrain from inviting "stars," as it may discourage beginners.</p>
</li>
<li><p>Consider allocating time for <em>lightning talks</em> and encourage beginners to participate. This is often the <strong>most interesting content</strong> at conferences.</p>
</li>
<li><p>Request that speakers submit their slides a few days prior to the conference to ensure everyone is well-prepared.</p>
</li>
<li><p>Ensure that the slides <strong>do not contain hate speech</strong>. I wish I was joking. I attended a conference where a speaker displayed a swastika on a slide, and another one where the entire presentation revolved around poking fun at stereotypes about male and female programmers. You'll want to avoid such situations.</p>
</li>
<li><p>Are you planning a dinner for the speakers? If so, where, when, and at what expense?</p>
</li>
</ul>
<h2 id="heading-ticket-sales">Ticket Sales</h2>
<ul>
<li><p>I won't recommend any specific ticketing system. Explore various options and choose one that helps you minimize any legal formalities.</p>
</li>
<li><p>Consider your target audience and prevalent payment systems in their countries. For instance, over <strong>40% of Polish participants pay using Blik</strong>.</p>
</li>
<li><p>Activating an account with third-party payment providers can take quite a long time. You'll need to prepare contracts, terms and conditions, privacy policies, and who knows what else. It requires a great deal of patience.</p>
</li>
<li><p>Keep in mind that, ultimately, your account will receive the <strong>ticket price minus 5% for various commissions</strong>.</p>
</li>
</ul>
<h2 id="heading-miscellaneous">Miscellaneous</h2>
<ul>
<li><p>Purchase extension cords, adhesive tape, markers, and pens. Don't forget to bring some paper.</p>
</li>
<li><p>Arrive at the conference venue early and ensure everything is in order :).</p>
</li>
</ul>
<h2 id="heading-conference-organizers-guide">Conference Organizer's Guide</h2>
<p>The primary objective of this post is to assist those who wish to organize a large event. However, as evident, <strong>organizing a conference is certainly no easy task</strong>. I hope that this post has provided insight into what you're getting into and demonstrated how <strong>time-consuming and complex</strong> organizing something on a larger scale can be.</p>
<p>Organizing a top-notch conference is undeniably a massive challenge, and even the smallest mistake can affect your reputation. However, it also provides an incredible adrenaline rush and a sense of satisfaction when everything goes according to plan.</p>
<p><strong>While the list above is comprehensive, it is by no means exhaustive.</strong> I have entirely left out the subject of event marketing.</p>
<p>I encourage you to comment! If you have any thoughts to share or additional insights, please feel free to write about them in the comments.</p>
]]></content:encoded></item><item><title><![CDATA[Why I won't use Remix]]></title><description><![CDATA[A few weeks ago, the tech world was abuzz with Kent C. Dodds' article with the attention-grabbing title "Why I won't use Next.js." In it, Kent criticizes Next.js and uses Remix as an example of a framework that solves every problem. Well, it's not en...]]></description><link>https://typeofweb.com/why-i-wont-use-remix</link><guid isPermaLink="true">https://typeofweb.com/why-i-wont-use-remix</guid><category><![CDATA[Next.js]]></category><category><![CDATA[Remix]]></category><category><![CDATA[React]]></category><dc:creator><![CDATA[Michał Miszczyszyn]]></dc:creator><pubDate>Sun, 26 Nov 2023 21:03:13 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1701032663468/7c86e2f2-b6e9-4d28-9305-48d3c1c4917e.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>A few weeks ago, the tech world was abuzz with Kent C. Dodds' article with the attention-grabbing title "Why I won't use Next.js." In it, Kent criticizes Next.js and uses Remix as an example of a framework that solves every problem. Well, it's not entirely accurate... Below you'll find my response to each of the points brought up by Kent.</p>
<h2 id="heading-independence">Independence</h2>
<blockquote>
<p>OpenNext exists because Next.js is difficult to deploy anywhere but Vercel.</p>
</blockquote>
<p>The sentence could be true if it read "OpenNext exists because deploying Next.js on AWS with serverless is challenging." And that's true; it's quite complex. Complex to the extent that Remix also doesn't provide a ready-made AWS serverless template either, contrary to what Kent seems to suggest in his article.</p>
<p>OpenNext uses SST (Serverless Stack) – a project designed to simplify the deployment of applications on AWS. It is not specific or targeted only at Next.js; quite the opposite: the SST website mentions Next.js, Astro, SvelteKit, SolidSite… and Remix.</p>
<p>Now, does Vercel have an interest in making it easier to use Next.js on other hosting solutions, even though they offer their paid services? Of course not. Nonetheless, in the Next.js repository, you can find examples of how to do that <a target="_blank" href="https://github.com/vercel/next.js/tree/canary/examples/with-docker-compose">1</a> <a target="_blank" href="https://github.com/vercel/next.js/tree/canary/examples/with-docker-multi-env">2</a> <a target="_blank" href="https://github.com/vercel/next.js/tree/canary/examples/with-docker">3</a>, and the documentation even describes how to implement your <a target="_blank" href="https://nextjs.org/docs/app/api-reference/next-config-js/incrementalCacheHandlerPath">own Data Cache</a>.</p>
<h2 id="heading-nextjs-is-eating-react">Next.js is eating React</h2>
<blockquote>
<p>Ever since then, the React team has felt much less collaborative.</p>
</blockquote>
<p>This section in Kent's is very subjective. From my perspective, not much has changed except for the velocity - the development and implementation of new things in React have accelerated significantly over the past year. But it still happens <strong>publicly, through an RFC process</strong>, new features land in the <code>canary</code>, and only much, much later in the stable version.</p>
<p>The issue for the Remix team might be that <strong>it wasn't them, but the Next.js team that was the first to utilize the new features of React</strong>. We're talking about React Server Components and Server Actions – things that aren't specific to Next.js but are part of React itself, and essentially any meta-framework can make use of them. Remix intentionally and <a target="_blank" href="https://remix.run/blog/react-server-components#remix-can-take-full-advantage-of-rsc">deliberately does not use them</a>.</p>
<h2 id="heading-experimenting-on-my-users">Experimenting on my users</h2>
<blockquote>
<p>Features that Next.js is shipping as stable are in the canary release of React.</p>
</blockquote>
<p>Yes, exactly! However, the term <code>canary</code> in React has a slightly <a target="_blank" href="https://react.dev/blog/2023/05/03/react-canaries">different meaning than in other projects</a>. The React maintainers state that by "canary," they mean a version of React.js that is ready for adoption in meta-frameworks. The unstable version of React is labelled with the <code>experimental</code> tag. I hope this clarification addresses the doubts.</p>
<h2 id="heading-too-much-magic">Too much magic</h2>
<p>Contrary to what the article seems to suggest, Next.js has fully embraced the Web APIs. Forms are just regular <code>form</code> elements, API Routes use <code>Request</code> and <code>Response</code>, there's a strong emphasis on <code>fetch</code>, and so on... <strong>Definitely has less magic than in previous versions.</strong></p>
<p>In contrast, Remix is heading in <a target="_blank" href="https://twitter.com/ryanflorence/status/1686757173202997249">the opposite direction</a>:</p>
<ul>
<li><p>instead of standard <code>async/server components</code> they have <code>loader</code></p>
</li>
<li><p>instead of standard <code>server actions</code> and <code>"use server"</code> they have <code>action</code></p>
</li>
<li><p>instead of standard <code>"use client"</code> they have <code>code splitting</code></p>
</li>
<li><p>instead of standard <code>async components</code> they have <code>defer</code> and SSR <code>ErrorBoundary</code></p>
</li>
<li><p>instead of standard <code>&lt;form action&gt;</code> they have <code>Form</code></p>
</li>
<li><p>instead of standard <code>useFormStatus</code>, <code>useOptimistic</code> they have <code>useNavigation</code>/<code>useFetcher</code></p>
</li>
</ul>
<p>And for now, they don't plan on changing that.</p>
<p>What's outrageous for many people is that Next.js overrides the <code>fetch</code> function and adds its cache handling to it. Kent compares this to overwriting prototypes of built-in objects by MooTools, which, seems to me, is <strong>a complete misunderstanding of the problem.</strong></p>
<p>Adding or overwriting properties in prototypes is indeed problematic for compatibility and the functioning of code originating from different sources. However, wrapping a global function in your code and invoking the original function underneath doesn't create too many issues for end users. Interestingly, Next.js <strong>isn't setting a precedent here</strong>. For example, Angular overwrites practically <a target="_blank" href="https://github.com/angular/angular/blob/HEAD/packages/zone.js/STANDARD-APIS.md#browser">all global functions</a>. As for other interesting examples, Cloudflare, a company involved in developing Web APIs, also overwrites <code>fetch</code> in their workers like Next.js to add their cache handling.</p>
<p>Is it perfect? Some might argue it's far from it and I think I agree. But does it work? Yeah, it does. Does it break your code? No; at least not if implemented correctly.</p>
<h2 id="heading-complexity">Complexity</h2>
<p>The author claims that Next.js is becoming too complex, and as examples of this, he mentions the APIs added to React.js and not Next. <a target="_blank" href="https://twitter.com/dan_abramov/status/1649214795571134465">Dan Abramov commented on this quite nicely</a>, so I guess there's not much more to add.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://twitter.com/dan_abramov/status/1649214795571134465">https://twitter.com/dan_abramov/status/1649214795571134465</a></div>
<p> </p>
<h2 id="heading-stability">Stability</h2>
<p>If I understand the intention correctly, the thesis of this paragraph is: "Remix had only 2 versions, so it's more stable than Next.js, which had 14 versions." <strong>This is not only absurd but also entirely untrue.</strong> Let's take a closer look.</p>
<p>Next.js has been in development since November 2016, celebrating its 7th birthday just recently. The first release of Next.js was... <code>1.0.1</code>. To follow SemVer, from that point on, every modification of Next.js's public API had to result in a major version bump, meaning 1 to 2, 2 to 3, and so on... Also, note that version 13 was bumped to 14 solely due to the requirement for a higher Node.js version; there were no other breaking changes.</p>
<p>Remix adopted a different versioning strategy. The first public release marked as <code>0.8.0</code> was released in 2021. Versions starting with <code>0.</code>, according to SemVer, can introduce <strong>breaking changes</strong> without the need to bump the major version, so <code>0.8.0</code> becomes <code>0.9.0</code>, not <code>1.0.0</code>. I've spent some time going through Remix's entire changelog and found <strong>12 breaking changes</strong>. Had the Remix team adopted the same versioning model, they would be at version 13 after two years, just like Next.js is after seven. Makes you think, doesn't it?</p>
<p>Additionally, the article seems to suggest that Next.js doesn't provide a migration path from old versions to new ones – but that might just be my mistaken impression. Anyway, migrating from Next.js 12 to 13 took 3 minutes – the changes are minimal, and the new router is entirely optional. The previous routing method will be supported for a long time, for many years. On the other hand, migrating from 13 to 14 took us <a target="_blank" href="https://twitter.com/zaiste/status/1717595898312642786">literally 30 seconds</a> in a production project.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://twitter.com/zaiste/status/1717595898312642786">https://twitter.com/zaiste/status/1717595898312642786</a></div>
<p> </p>
<h2 id="heading-summary">Summary</h2>
<p>A year ago, <a target="_blank" href="https://www.youtube.com/live/XKeN9WsUAzM">Zaiste and I recorded a video "Remix Marketing Exposed" (in Polish)</a>, in which we dissected the Next.js and Remix comparison published by the Remix team. We demonstrated that the content therein is <strong>far from being objective and we pointed out multiple biased examples</strong> used to present Remix as better, faster, <s>stronger</s>.</p>
<p>Kent C. Dodds' article is certainly more balanced, however, I still managed to find some inaccuracies in it. I hope this helps you build a more balanced and educated opinion about both Next.js and Remix.</p>
<p>Do you agree? Do you think I've made any mistakes in my assessment? Let me know your thoughts in the comments!</p>
<p><em>PS</em> Kent is selling his Remix course on a website written in Next.js. That's just inconsistent, isn't it?</p>
<h2 id="heading-links">Links</h2>
<ol>
<li><p><a target="_blank" href="http://github.com/vercel/next.js/tree/canary/examples/with-docker-compose">github.com/vercel/next.js/tree/canary/examples/with-docker-compose</a></p>
</li>
<li><p><a target="_blank" href="http://github.com/vercel/next.js/tree/canary/examples/with-docker-multi-env">github.com/vercel/next.js/tree/canary/examples/with-docker-multi-env</a></p>
</li>
<li><p><a target="_blank" href="http://github.com/vercel/next.js/tree/canary/examples/with-docker">github.com/vercel/next.js/tree/canary/examples/with-docker</a></p>
</li>
<li><p><a target="_blank" href="http://nextjs.org/docs/app/api-reference/next-config-js/incrementalCacheHandlerPath">nextjs.org/docs/app/api-reference/next-config-js/incrementalCacheHandlerPath</a></p>
</li>
<li><p><a target="_blank" href="http://remix.run/blog/react-server-components#remix-can-take-full-advantage-of-rsc">remix.run/blog/react-server-components#remix-can-take-full-advantage-of-rsc</a></p>
</li>
<li><p><a target="_blank" href="http://twitter.com/ryanflorence/status/1686757173202997249">twitter.com/ryanflorence/status/1686757173202997249</a></p>
</li>
<li><p><a target="_blank" href="http://github.com/angular/angular/blob/HEAD/packages/zone.js/STANDARD-APIS.md#browser">github.com/angular/angular/blob/HEAD/packages/zone.js/STANDARD-APIS.md#browser</a></p>
</li>
<li><p><a target="_blank" href="http://twitter.com/zaiste/status/1717595898312642786">twitter.com/zaiste/status/1717595898312642786</a></p>
</li>
<li><p><a target="_blank" href="http://react.dev/blog/2023/05/03/react-canaries">react.dev/blog/2023/05/03/react-canaries</a></p>
</li>
<li><p><a target="_blank" href="http://youtube.com/live/XKeN9WsUAzM">youtube.com/live/XKeN9WsUAzM</a></p>
</li>
</ol>
]]></content:encoded></item></channel></rss>