<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Daniel Weinmann</title>
    <description>The latest articles on DEV Community by Daniel Weinmann (@danielweinmann).</description>
    <link>https://dev.to/danielweinmann</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1234207%2F31c75286-48c7-4088-8d2e-0784cf59a5da.png</url>
      <title>DEV Community: Daniel Weinmann</title>
      <link>https://dev.to/danielweinmann</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/danielweinmann"/>
    <language>en</language>
    <item>
      <title>Introducing Flashboard: Instant Admin Panels for PostgreSQL</title>
      <dc:creator>Daniel Weinmann</dc:creator>
      <pubDate>Mon, 02 Sep 2024 18:05:27 +0000</pubDate>
      <link>https://dev.to/seasonedcc/introducing-flashboard-instant-admin-panels-for-postgresql-2ki4</link>
      <guid>https://dev.to/seasonedcc/introducing-flashboard-instant-admin-panels-for-postgresql-2ki4</guid>
      <description>&lt;p&gt;Have you noticed that building admin panels and internal tools is often neglected in favor of user-facing features? There's always too much to build and too little dev time, so it's understandable.&lt;/p&gt;

&lt;p&gt;But at &lt;a href="https://www.seasoned.cc/" rel="noopener noreferrer"&gt;Seasoned&lt;/a&gt;, we hate offering a subpar experience to our clients' internal team while providing an amazing UX to the final users. Yet, we also refuse to make our clients pay for a custom admin panel when the end-user is the priority.&lt;/p&gt;

&lt;p&gt;That's how &lt;a href="https://www.getflashboard.com/" rel="noopener noreferrer"&gt;Flashboard&lt;/a&gt; was born. We made building admin panels instantaneous so we don't have to choose between internal teams and the end user. And now that we have it, we decided to share it with you all!&lt;/p&gt;

&lt;p&gt;Flashboard is available today for anyone to use. Connect to your &lt;a href="https://www.postgresql.org/" rel="noopener noreferrer"&gt;PostgreSQL&lt;/a&gt; database, select the tables and fields you want to manage, and that's it! You're up and running within seconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check out this quick demo:
&lt;/h2&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/WHVtf1v0XK4"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  I like what I see, but is it safe?
&lt;/h2&gt;

&lt;p&gt;Yes! Your database credentials are encrypted with a key only you own. Even our team can't access them, let alone potential attackers.&lt;/p&gt;

&lt;h2&gt;
  
  
  OK, but can I test it without connecting to my real database?
&lt;/h2&gt;

&lt;p&gt;Yes, you can. There's an option to "Create a demo panel" to play around with a demo database.&lt;/p&gt;

&lt;h2&gt;
  
  
  Nice, how much does it cost?
&lt;/h2&gt;

&lt;p&gt;It's free! 🎁 &lt;/p&gt;

&lt;p&gt;And we plan to maintain the free plan in the long run. We'll explore charging for advanced features soon, but right now everything is free.&lt;/p&gt;

&lt;p&gt;Play around with it at &lt;a href="https://www.getflashboard.com/" rel="noopener noreferrer"&gt;getflashboard.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>database</category>
      <category>productivity</category>
      <category>admin</category>
    </item>
    <item>
      <title>Molecules of business logic from tiny atoms</title>
      <dc:creator>Daniel Weinmann</dc:creator>
      <pubDate>Tue, 25 Jun 2024 17:32:07 +0000</pubDate>
      <link>https://dev.to/seasonedcc/molecules-of-business-logic-from-tiny-atoms-36mh</link>
      <guid>https://dev.to/seasonedcc/molecules-of-business-logic-from-tiny-atoms-36mh</guid>
      <description>&lt;p&gt;Some people asked me how to design their business logic after reading my article on &lt;a href="https://dev.to/seasonedcc/keep-your-business-code-separate-from-the-rest-4ek2"&gt;keeping your business code separate from the rest&lt;/a&gt;. Here's an example of how we do it.&lt;/p&gt;

&lt;p&gt;Imagine we have an online shop with a page for our Magical T-Shirt. On that page, we want to show all the product variants (small, medium, large) plus product information, like name. Your customer has applied a coupon code to their cart, so we must also show the discounted price.&lt;/p&gt;

&lt;h2&gt;
  
  
  Functions with a verb in their name
&lt;/h2&gt;

&lt;p&gt;At &lt;a href="https://www.seasoned.cc/" rel="noopener noreferrer"&gt;Seasoned&lt;/a&gt;, we prefer functions with verbs in their name to classes and objects. So the first thing we'll do is create a function called &lt;code&gt;getProductPageData&lt;/code&gt; that gathers all the information we need.&lt;/p&gt;

&lt;p&gt;It will look completely different by the end of this article, but this is how it starts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getProductPageData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;couponCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// This would come from your database&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Magical T-Shirt&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// This would come from your database&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;variants&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;small&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Small&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;8.99&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;medium&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Medium&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;10.99&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;large&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Large&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;12.99&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="c1"&gt;// This would come from your database&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;coupon&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;couponCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;discount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;coupon&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;variants&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;variants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;priceWithDiscount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;coupon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;discount&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;})),&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  A folder dedicated to business logic
&lt;/h2&gt;

&lt;p&gt;This code lives in a &lt;code&gt;business&lt;/code&gt; folder and it should not have any knowledge about the framework you're using. Ideally, it should work as well for a mobile app as it does for a website. All the code dealing with platform-specific APIs or framework-specific implementation should live elsewhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dealing with errors
&lt;/h2&gt;

&lt;p&gt;Our &lt;code&gt;getProductPageData&lt;/code&gt; function could fail. For instance, every query we make to our database can throw an error. So far, our code does nothing to deal with that.&lt;/p&gt;

&lt;p&gt;Instead of adding multiple &lt;code&gt;try/catch&lt;/code&gt; statements, we'll establish a pattern to deal with exceptions. For that, let me introduce you to &lt;a href="https://github.com/seasonedcc/composable-functions" rel="noopener noreferrer"&gt;Composable Functions&lt;/a&gt;, a library we created to make function composition easy and safe.&lt;/p&gt;

&lt;p&gt;Composable functions do much more, but they help us deal with errors by doing the &lt;code&gt;try/catch&lt;/code&gt; for us and returning the result in a type-safe way. Imagine this silly function call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getLength&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;

&lt;span class="nf"&gt;getLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;// TypeError: Cannot read properties of null (reading 'length')&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you wrap it with &lt;code&gt;composable&lt;/code&gt;, it will never throw an error. Instead, it will return the result with the exception, like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getLength&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;composable&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;// result equals&lt;/span&gt;
&lt;span class="c1"&gt;// {&lt;/span&gt;
&lt;span class="c1"&gt;//   success: false,&lt;/span&gt;
&lt;span class="c1"&gt;//   errors: [&amp;lt;Error object&amp;gt;]&lt;/span&gt;
&lt;span class="c1"&gt;// }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If it succeeds, it will return the result of the function inside &lt;code&gt;data&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getLength&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;composable&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;abc&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;// result equals&lt;/span&gt;
&lt;span class="c1"&gt;// {&lt;/span&gt;
&lt;span class="c1"&gt;//   success: true,&lt;/span&gt;
&lt;span class="c1"&gt;//   data: 3,&lt;/span&gt;
&lt;span class="c1"&gt;//   errors: []&lt;/span&gt;
&lt;span class="c1"&gt;// }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This pattern forces us to remember to deal with errors. To access &lt;code&gt;result.data&lt;/code&gt;, we must first check if &lt;code&gt;result.success&lt;/code&gt; is true. Otherwise, TypeScript will give us an error.&lt;/p&gt;

&lt;p&gt;This is how our &lt;code&gt;getProductPageData&lt;/code&gt; function looks like when wrapped with &lt;code&gt;composable&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getProductPageData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;composable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;couponCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// This would come from your database&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Magical T-Shirt&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// This would come from your database&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;variants&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;small&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Small&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;8.99&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;medium&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Medium&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;10.99&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;large&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Large&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;12.99&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="c1"&gt;// This would come from your database&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;coupon&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;couponCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;discount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;coupon&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;variants&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;variants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;priceWithDiscount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;coupon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;discount&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="p"&gt;})),&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Extracting smaller functions
&lt;/h2&gt;

&lt;p&gt;Over time, as we get to know our needs for reusing specific parts of code, we start to extract smaller functions. However, be careful not to extract them too early, as &lt;a href="https://sandimetz.com/blog/2016/1/20/the-wrong-abstraction" rel="noopener noreferrer"&gt;the wrong abstraction is worse than code duplication&lt;/a&gt;. In other words, &lt;a href="https://kentcdodds.com/blog/aha-programming" rel="noopener noreferrer"&gt;avoid hasty abstractions&lt;/a&gt; and only extract smaller functions when the right abstraction emerges.&lt;/p&gt;

&lt;p&gt;In our example, let's imagine we have to get product info in multiple places of our codebase. We also need to list a product's variants, get coupon info, and apply a discount to a list of variants on several occasions for different business purposes.&lt;/p&gt;

&lt;p&gt;With that, the following tiny atoms of business logic emerged:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getProduct&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;composable&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;productId&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// This would come from your database&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Magical T-Shirt&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getVariants&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;composable&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;productId&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// This would come from your database&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;small&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Small&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;8.99&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;medium&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Medium&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;10.99&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;large&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Large&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;12.99&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;// Usually, this type would come from your database lib&lt;/span&gt;
&lt;span class="c1"&gt;// You don't need to understand it now, but it's the type for&lt;/span&gt;
&lt;span class="c1"&gt;// an individual variant.&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Variant&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;UnpackData&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;getVariants&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getCoupon&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;composable&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;couponCode&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;couponCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// This would come from your database&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;couponCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;discount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;applyDiscountToVariants&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;composable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;variants&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;discount&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;variants&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Variant&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt; &lt;span class="nl"&gt;discount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="nx"&gt;variants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;priceWithDiscount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;discount&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;})),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Aside&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;— Why are we using a single object as the argument for our small functions? Why is it &lt;code&gt;{ productId }: { productId: string }&lt;/code&gt; instead of simply &lt;code&gt;productId: string&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;— Thank you for noticing! We'll get to that soon :)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Using our atoms
&lt;/h2&gt;

&lt;p&gt;Now let's rewrite &lt;code&gt;getProductPageData&lt;/code&gt; to use these atomic functions instead of duplicating their code. An initial version looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getProductPageData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;composable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;couponCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Notice that composable functions are always async&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;productResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getProduct&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;productId&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;productResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;success&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Could not find product&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;productResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;variantsResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getVariants&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;productId&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;variantsResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;success&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Could not find variants&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;variants&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;variantsResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;couponResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getCoupon&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;couponCode&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;couponResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;success&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Could not find coupon&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;coupon&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;couponResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;coupon&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;variants&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Well, it's not looking that good yet. The good part is that composable functions force us to deal with error cases, so we &lt;em&gt;had&lt;/em&gt; to make all the checks above. But there is a better way. If we rewrite the function to use &lt;a href="https://github.com/seasonedcc/composable-functions/blob/main/API.md#fromsuccess" rel="noopener noreferrer"&gt;fromSuccess&lt;/a&gt;, it looks much cleaner:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getProductPageData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;composable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;couponCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fromSuccess&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;getProduct&lt;/span&gt;&lt;span class="p"&gt;)({&lt;/span&gt; &lt;span class="nx"&gt;productId&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;variants&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fromSuccess&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;getVariants&lt;/span&gt;&lt;span class="p"&gt;)({&lt;/span&gt; &lt;span class="nx"&gt;productId&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;coupon&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fromSuccess&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;getCoupon&lt;/span&gt;&lt;span class="p"&gt;)({&lt;/span&gt; &lt;span class="nx"&gt;couponCode&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;coupon&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;variants&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's starting to look better! &lt;code&gt;fromSuccess&lt;/code&gt; does the check and throws the error for us when a function fails.&lt;/p&gt;

&lt;h2&gt;
  
  
  Going molecular
&lt;/h2&gt;

&lt;p&gt;We could consider our work done with the code we have so far. It is doing what it is supposed to do, it's broken down into tiny atoms of business logic, and it's dealing with errors properly.&lt;/p&gt;

&lt;p&gt;But we believe there's an even better way to bond our atoms. Right now, they're not truly bonded. They are being used separately, one at a time, and we're manually piecing together their results. If we use &lt;a href="https://www.freecodecamp.org/news/function-composition-in-javascript/" rel="noopener noreferrer"&gt;function composition&lt;/a&gt;, we can bond our atoms to create molecular functions.&lt;/p&gt;

&lt;p&gt;Before we get back to our online shop, let's go over the basics of composition with our silly &lt;code&gt;getLength&lt;/code&gt; example from the beginning of the article.&lt;/p&gt;

&lt;p&gt;Imagine you want to get the length of a string but return it as a string. This is the result we're expecting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getLengthAsString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;abc&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;// result equals&lt;/span&gt;
&lt;span class="c1"&gt;// {&lt;/span&gt;
&lt;span class="c1"&gt;//   success: true,&lt;/span&gt;
&lt;span class="c1"&gt;//   data: '3', ⬅️ Notice this is a string 👀&lt;/span&gt;
&lt;span class="c1"&gt;//   errors: []&lt;/span&gt;
&lt;span class="c1"&gt;// }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our initial implementation of &lt;code&gt;getLengthAsString&lt;/code&gt; could be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getLengthAsString&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; 
  &lt;span class="nf"&gt;composable&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But we already have a &lt;code&gt;numberToString&lt;/code&gt; composable function that converts a number to a string, so we want to use it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;numberToString&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;composable&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getLengthAsString&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;composable&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nf"&gt;fromSuccess&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;numberToString&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now it's time to add composition. For composing functions, we need &lt;a href="https://github.com/seasonedcc/composable-functions/blob/main/API.md#combinators" rel="noopener noreferrer"&gt;combinators&lt;/a&gt;. Combinators are functions that receive composable functions as input and return another composable function.&lt;/p&gt;

&lt;p&gt;We're going to talk about a few combinators in this article, and the first one is &lt;a href="https://github.com/seasonedcc/composable-functions/blob/main/API.md#pipe" rel="noopener noreferrer"&gt;pipe&lt;/a&gt;. &lt;code&gt;pipe&lt;/code&gt; creates a chain of composable functions that will be called in sequence, always passing the result of the last function called as the input to the next.&lt;/p&gt;

&lt;p&gt;Here's our &lt;code&gt;getLenghAsString&lt;/code&gt; using pipe:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getLength&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;composable&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;numberToString&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;composable&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getLengthAsString&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;getLength&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;numberToString&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getLengthAsString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;abc&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;// result equals&lt;/span&gt;
&lt;span class="c1"&gt;// {&lt;/span&gt;
&lt;span class="c1"&gt;//   success: true,&lt;/span&gt;
&lt;span class="c1"&gt;//   data: '3', ⬅️ It is a string 🎉&lt;/span&gt;
&lt;span class="c1"&gt;//   errors: []&lt;/span&gt;
&lt;span class="c1"&gt;// }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why is it better this way?
&lt;/h2&gt;

&lt;p&gt;There are a few reasons why the version with &lt;code&gt;pipe&lt;/code&gt; is better than the one with &lt;code&gt;fromSuccess&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;First, it's easier to change. Want to return both the numeric and the string versions of the result? Just add a &lt;a href="https://github.com/seasonedcc/composable-functions/blob/main/API.md#map" rel="noopener noreferrer"&gt;map&lt;/a&gt; to the composition:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getLengthAsStringAndNumber&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;getLength&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;numberToString&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;asNumber&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;asString&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;})),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getLengthAsStringAndNumber&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;abc&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;// result equals&lt;/span&gt;
&lt;span class="c1"&gt;// {&lt;/span&gt;
&lt;span class="c1"&gt;//   success: true,&lt;/span&gt;
&lt;span class="c1"&gt;//   data: { asNumber: 3, asString: '3' },&lt;/span&gt;
&lt;span class="c1"&gt;//   errors: []&lt;/span&gt;
&lt;span class="c1"&gt;// }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice how we didn't have to change anything in our atomic functions. They remained the same. Only the composition (our molecule) changed.&lt;/p&gt;

&lt;p&gt;Second, it's easier to test. You can write one set of tests for &lt;code&gt;getLength&lt;/code&gt;, another one for &lt;code&gt;numberToString&lt;/code&gt;, and just enough tests on &lt;code&gt;getLengthAsString&lt;/code&gt; to make sure the composition worked.&lt;/p&gt;

&lt;p&gt;Lastly, all compositions are type-safe from end to end. For example, if we try to &lt;code&gt;pipe&lt;/code&gt; a function that returns a string into a function that expects a number, the composition will fail.&lt;/p&gt;

&lt;h2&gt;
  
  
  Back to our online store
&lt;/h2&gt;

&lt;p&gt;Now it's time to create our &lt;code&gt;getProductPageData&lt;/code&gt; with compositions. Here's one way of doing it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getRawData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;collect&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;product&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;getProduct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;variants&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;getVariants&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;coupon&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;getCoupon&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getRawDataWithDiscount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;getRawData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;discount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;coupon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;discount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}))&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getRawDataWithDiscountedVariants&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sequence&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;getRawDataWithDiscount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;applyDiscountToVariants&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getProductPageData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;getRawDataWithDiscountedVariants&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;([{&lt;/span&gt; &lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;coupon&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;variants&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;coupon&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;variants&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It looks daunting, I know. But let's walk through it together, one step at a time.&lt;/p&gt;

&lt;p&gt;First, we &lt;a href="https://github.com/seasonedcc/composable-functions/blob/main/API.md#collect" rel="noopener noreferrer"&gt;collect&lt;/a&gt; all the raw data we need on &lt;code&gt;getRawData&lt;/code&gt;. This function receives &lt;code&gt;{ productId: string; couponCode: string }&lt;/code&gt; and returns an object with &lt;code&gt;product&lt;/code&gt;, &lt;code&gt;variants&lt;/code&gt;, and &lt;code&gt;coupon&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Then we &lt;a href="https://github.com/seasonedcc/composable-functions/blob/main/API.md#map" rel="noopener noreferrer"&gt;map&lt;/a&gt; the raw data on &lt;code&gt;getRawDataWithDiscount&lt;/code&gt;. This function receives the same &lt;code&gt;{ productId: string; couponCode: string }&lt;/code&gt; input and returns an object with &lt;code&gt;product&lt;/code&gt;, &lt;code&gt;variants&lt;/code&gt;, &lt;code&gt;coupon&lt;/code&gt;, and &lt;code&gt;discount&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The reason we add the &lt;code&gt;discount&lt;/code&gt; to the root of our object is because our &lt;code&gt;applyDiscountToVariants&lt;/code&gt; takes a list of &lt;code&gt;variants&lt;/code&gt; and the &lt;code&gt;discount&lt;/code&gt; as input, not the &lt;code&gt;coupon&lt;/code&gt;. In the real world, it's very common to have an atomic function that does what you need but require you to "massage your input" a little in before you can call it.&lt;/p&gt;

&lt;p&gt;Next, we &lt;a href="https://github.com/seasonedcc/composable-functions/blob/main/API.md#sequence" rel="noopener noreferrer"&gt;sequence&lt;/a&gt; getting the raw data with discount and applying the discount to our variants on &lt;code&gt;getRawDataWithDiscountedVariants&lt;/code&gt;. It also receives &lt;code&gt;{ productId: string; couponCode: string }&lt;/code&gt;. Then, it returns a &lt;a href="https://www.w3schools.com/typescript/typescript_tuples.php" rel="noopener noreferrer"&gt;tuple&lt;/a&gt; with the result of &lt;code&gt;getRawDataWithDiscount&lt;/code&gt; as the first element, and the result of &lt;code&gt;applyDiscountToVariants&lt;/code&gt; as the second.&lt;/p&gt;

&lt;p&gt;Finally, we map the result of &lt;code&gt;getRawDataWithDiscountedVariants&lt;/code&gt; to get rid of the tuple and return an object with the final data structure we want on &lt;code&gt;getProductPageData&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Calling our function
&lt;/h2&gt;

&lt;p&gt;Now you can call your business function from the outside world. This usually means calling it from inside a controller, a loader, or any other method for gathering data.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getProductPageData&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;123&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;couponCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;10OFF&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;success&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// 🚨 Any errors thrown in any of the composed functions&lt;/span&gt;
  &lt;span class="c1"&gt;// will be on result.errors&lt;/span&gt;
  &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// {&lt;/span&gt;
&lt;span class="c1"&gt;//   product: { id: '123', name: 'Magical T-Shirt' },&lt;/span&gt;
&lt;span class="c1"&gt;//   coupon: { code: '10OFF', discount: 10 },&lt;/span&gt;
&lt;span class="c1"&gt;//   variants: [&lt;/span&gt;
&lt;span class="c1"&gt;//     {&lt;/span&gt;
&lt;span class="c1"&gt;//       productId: '123',&lt;/span&gt;
&lt;span class="c1"&gt;//       sku: 'small',&lt;/span&gt;
&lt;span class="c1"&gt;//       name: 'Small',&lt;/span&gt;
&lt;span class="c1"&gt;//       price: 8.99,&lt;/span&gt;
&lt;span class="c1"&gt;//       priceWithDiscount: 8.091000000000001&lt;/span&gt;
&lt;span class="c1"&gt;//     },&lt;/span&gt;
&lt;span class="c1"&gt;//     {&lt;/span&gt;
&lt;span class="c1"&gt;//       productId: '123',&lt;/span&gt;
&lt;span class="c1"&gt;//       sku: 'medium',&lt;/span&gt;
&lt;span class="c1"&gt;//       name: 'Medium',&lt;/span&gt;
&lt;span class="c1"&gt;//       price: 10.99,&lt;/span&gt;
&lt;span class="c1"&gt;//       priceWithDiscount: 9.891&lt;/span&gt;
&lt;span class="c1"&gt;//     },&lt;/span&gt;
&lt;span class="c1"&gt;//     {&lt;/span&gt;
&lt;span class="c1"&gt;//       productId: '123',&lt;/span&gt;
&lt;span class="c1"&gt;//       sku: 'large',&lt;/span&gt;
&lt;span class="c1"&gt;//       name: 'Large',&lt;/span&gt;
&lt;span class="c1"&gt;//       price: 12.99,&lt;/span&gt;
&lt;span class="c1"&gt;//       priceWithDiscount: 11.691&lt;/span&gt;
&lt;span class="c1"&gt;//     }&lt;/span&gt;
&lt;span class="c1"&gt;//   ]&lt;/span&gt;
&lt;span class="c1"&gt;// }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, this version of &lt;code&gt;getProductPageData&lt;/code&gt; receives an object with the product id and the coupon code instead of two arguments like we had on our initial version.&lt;/p&gt;

&lt;p&gt;We commonly do that because it makes it easier to compose the functions. That's why our atomic functions also receive an object. For example, on &lt;code&gt;getRawDataWithDiscountedVariants&lt;/code&gt; we can get the result of &lt;code&gt;getRawDataWithDiscount&lt;/code&gt; and pass it to &lt;code&gt;applyDiscountToVariants&lt;/code&gt; without transforming it, even if returns an object with more keys than what &lt;code&gt;applyDiscountToVariants&lt;/code&gt; needs.&lt;/p&gt;

&lt;p&gt;If our atomic functions expected multiple arguments instead of an object, we could transform our data at every stage to be exactly what the next function needs. But I think this is a good pattern that leads to more readable compositions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Thinking in composition
&lt;/h2&gt;

&lt;p&gt;It takes a while to adjust our thought process to think of compositions instead of a sequence of function calls and if statements. To be honest, I'm not totally there yet. But the more I use composition, the more I write code that's maintainable and easy to test.&lt;/p&gt;

&lt;p&gt;It will take you and your team some time and learning curve but once you start thinking in compositions, you can skip the intermediate functions above and write your compositions inline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getProductPageData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nf"&gt;sequence&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nf"&gt;collect&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;product&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;getProduct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;variants&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;getVariants&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;coupon&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;getCoupon&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;}),&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;discount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;coupon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;discount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nx"&gt;applyDiscountToVariants&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;([{&lt;/span&gt; &lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;coupon&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;variants&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;coupon&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;variants&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. I hope this method of writing business code helps you give business logic the priority it deserves and create more resilient codebases 🤓&lt;/p&gt;

&lt;p&gt;Let me know how it works for you!&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>typescript</category>
      <category>productivity</category>
      <category>cleancode</category>
    </item>
    <item>
      <title>A feature needs a strong reason to exist</title>
      <dc:creator>Daniel Weinmann</dc:creator>
      <pubDate>Wed, 12 Jun 2024 19:33:22 +0000</pubDate>
      <link>https://dev.to/seasonedcc/a-feature-needs-a-strong-reason-to-exist-3e77</link>
      <guid>https://dev.to/seasonedcc/a-feature-needs-a-strong-reason-to-exist-3e77</guid>
      <description>&lt;p&gt;We're working on a product with an amazing UI for managing projects. Think Trello but different 🙃&lt;/p&gt;

&lt;p&gt;It offers drag and drop, in-place editing, keyboard navigation, etc, all with &lt;a href="https://javascript.plainenglish.io/what-is-optimistic-ui-656b9d6e187c" rel="noopener noreferrer"&gt;optimistic UI&lt;/a&gt;. The code is well-written and fairly organized, but it is inherently complex.&lt;/p&gt;

&lt;p&gt;Recently, we wanted to add a "select multiple tasks and drag them all together" functionality. Before jumping into the code, we did some &lt;a href="https://www.youtube.com/watch?v=h_8M23wVjXk" rel="noopener noreferrer"&gt;shaping&lt;/a&gt; to devise a solution that would not conflict with all the other user interactions we have.&lt;/p&gt;

&lt;p&gt;Then it was time to do a &lt;a href="http://www.extremeprogramming.org/rules/spike.html" rel="noopener noreferrer"&gt;spike&lt;/a&gt; to understand how hard it would be to build it. Before I even started coding the prototype, I knew it would make our codebase much more complex. We already have a lot of state management for the drag and drop, in-place editing, and optimistic UI. Adding another layer of state on the same UI would make it exponentially harder to grasp.&lt;/p&gt;

&lt;p&gt;There is nothing inherently wrong with adding complexity. If the benefits outweigh the costs by a good margin, we're all for it. The problem in this case is that there is not yet a strong reason for this feature to exist.&lt;/p&gt;

&lt;p&gt;The idea came up when we realized we had some time on our hands and wanted to use it to build something. And we feel this feature will be very useful in some cases. But if we're being honest, we still don't know how much so.&lt;/p&gt;

&lt;p&gt;There is no user feedback indicating they're losing time dragging multiple tasks one by one. We only have our own experience with the app and a hunch. Nothing wrong with that either. Many of the best features out there were based on hunches and zero user feedback.&lt;/p&gt;

&lt;p&gt;Still, something felt off while spiking. It doesn't feel like this hunch is worth the added complexity at this point. Maybe as we &lt;a href="https://www.feltpresence.com/framing/" rel="noopener noreferrer"&gt;frame&lt;/a&gt; the problem better, it will.&lt;/p&gt;

&lt;p&gt;That's the beauty of a method like &lt;a href="https://basecamp.com/shapeup" rel="noopener noreferrer"&gt;Shape Up&lt;/a&gt;. It provides great checkpoints where we can decide whether to go ahead with a project or not.&lt;/p&gt;

&lt;p&gt;If the stages of development (framing, shaping, building) were not that clear, we might have moved forward with this feature and ended up with a much more complex codebase without a strong reason for it.&lt;/p&gt;

</description>
      <category>shapeup</category>
      <category>agile</category>
      <category>design</category>
      <category>product</category>
    </item>
    <item>
      <title>Keep your business code separate from the rest</title>
      <dc:creator>Daniel Weinmann</dc:creator>
      <pubDate>Mon, 10 Jun 2024 18:28:10 +0000</pubDate>
      <link>https://dev.to/seasonedcc/keep-your-business-code-separate-from-the-rest-4ek2</link>
      <guid>https://dev.to/seasonedcc/keep-your-business-code-separate-from-the-rest-4ek2</guid>
      <description>&lt;p&gt;One of the best things you can do for a codebase is to keep the business logic separate from everything else.&lt;/p&gt;

&lt;p&gt;Imagine you have an app from 20 years ago, written in an old framework no one uses anymore. Thousands of people rely on it for their jobs, because it is the only app that deeply understands how this particular industry works.&lt;/p&gt;

&lt;p&gt;The user experience could be so much better but the app works and no competitor beat them yet. Now you're tasked with bringing this giant to the modern days by adopting current technologies to improve the overall experience.&lt;/p&gt;

&lt;p&gt;You take a quick look at the code, and being a seasoned dev with a sharp sword, you prepare for war. You know it's going to take your team a long time and it's going to be messy. Why?&lt;/p&gt;

&lt;p&gt;Because most of your time will be spent untangling the logic that needs to stay from the parts that need to go. And what needs to stay is the business logic. The rest is old framework code no one cares about.&lt;/p&gt;

&lt;p&gt;Now, while you untangle this mess, are you going to leave another one for future generations? Of course not. Because you know the current framework you're using now will undoubtedly be replaced by something else in a decade or so, you will do your best to keep the business code separate from everything else.&lt;/p&gt;

&lt;p&gt;To do so, ask yourself the following question at all times:&lt;/p&gt;

&lt;blockquote&gt;
&lt;h2&gt;
  
  
  If I were to migrate to a completely different framework tomorrow, would I be able to take the business logic with me?
&lt;/h2&gt;
&lt;/blockquote&gt;

&lt;p&gt;If the answer is "yes", you're writing code that lasts. If not, you're &lt;a href="https://en.wikipedia.org/wiki/Technical_debt" rel="noopener noreferrer"&gt;borrowing from the future&lt;/a&gt; and some refactoring is in order.&lt;/p&gt;

</description>
      <category>cleancode</category>
      <category>architecture</category>
      <category>productivity</category>
      <category>programming</category>
    </item>
    <item>
      <title>Get out of state management hell with automatic revalidation</title>
      <dc:creator>Daniel Weinmann</dc:creator>
      <pubDate>Tue, 09 Jan 2024 20:47:41 +0000</pubDate>
      <link>https://dev.to/seasonedcc/get-out-of-state-management-hell-with-automatic-revalidation-59bn</link>
      <guid>https://dev.to/seasonedcc/get-out-of-state-management-hell-with-automatic-revalidation-59bn</guid>
      <description>&lt;p&gt;React Router and Remix introduced &lt;a href="https://remix.run/docs/en/main/discussion/data-flow" rel="noopener noreferrer"&gt;automatic revalidation&lt;/a&gt; of data after mutations. I believe this is a &lt;a href="https://dev.to/danielweinmann/the-software-development-paradigm-for-the-2020s-5g8g"&gt;paradigm shift&lt;/a&gt; and &lt;a href="https://dev.to/danielweinmann/why-react-routers-new-data-apis-boost-productivity-pe4"&gt;boosts productivity&lt;/a&gt; like nothing else available today.&lt;/p&gt;

&lt;p&gt;In this article, I'll show you step-by-step why this matters and how to get out of what I call "state management hell" with the help of React Router or Remix.&lt;/p&gt;

&lt;p&gt;Imagine you have a React &lt;a href="https://en.wikipedia.org/wiki/Single-page_application" rel="noopener noreferrer"&gt;SPA&lt;/a&gt; with a top bar. It shows the avatar of the signed-in user in the right corner or a "Sign in" button if there's no current user.&lt;/p&gt;

&lt;p&gt;Your login page lives at "/sign-in" and includes the top bar and a form with email and password right below the top bar. After a successful login, the user is redirected to the home page at "/".&lt;/p&gt;

&lt;p&gt;Now, imagine you are not using a library to handle routing. You have a simple router component that monitors the window's location and decides what to render based on that.&lt;/p&gt;

&lt;p&gt;Because you know all pages will render the top bar, it lives in a separate component that is rendered higher in the hierarchy before your router component.&lt;/p&gt;

&lt;p&gt;What happens after a successful login? The router component detects the change in the location and renders the homepage component. But there's something wrong.&lt;/p&gt;

&lt;p&gt;Since the top bar is above the router in the render hierarchy, it will not be re-rendered. Thus, the user avatar will not appear, and the UI will be stale. You have to reload the page to see the avatar. I bet you're thinking of an app with this kind of bug right now 🙃&lt;/p&gt;

&lt;p&gt;There are several possible solutions to this problem, and we'll talk about how React Router and Remix address this in a minute. But before that, let's look at the most common approach today: centralized app state.&lt;/p&gt;

&lt;p&gt;You add the current user state to a &lt;a href="https://react.dev/learn/passing-data-deeply-with-context" rel="noopener noreferrer"&gt;React Context&lt;/a&gt; or &lt;a href="https://github.com/reduxjs/react-redux" rel="noopener noreferrer"&gt;state&lt;/a&gt; &lt;a href="https://github.com/pmndrs/zustand" rel="noopener noreferrer"&gt;management&lt;/a&gt; &lt;a href="https://github.com/statelyai/xstate" rel="noopener noreferrer"&gt;library&lt;/a&gt;, read from it on the top bar, and write to it after a user signs in. Done. No big deal, right?&lt;/p&gt;

&lt;p&gt;Wrong. It's a huge deal. You now have to make sure all devs in your team keep the structure of the state in mind and remember to update it when necessary.&lt;/p&gt;

&lt;p&gt;More importantly, you must spend a lot of time designing the state. In the example above, we only need to know the current user. But a real app has hundreds or thousands of pieces of data as part of the state.&lt;/p&gt;

&lt;p&gt;You have to constantly make the trade-off between separation of concerns and centralization in state design. If you put everything in one place, the devs need to keep a big structure in mind at all times.&lt;/p&gt;

&lt;p&gt;If you have separate states (auth, clients, orders, etc), the team should remember which states to update after each mutation. Damned if you do, damned if you don't.&lt;/p&gt;

&lt;p&gt;As apps grow, most bugs are related to state management. Diagnosing, fixing, or preventing them becomes costly, with the application state adding a ton of cognitive load to teams.&lt;/p&gt;

&lt;p&gt;New features take longer to be delivered and often break unrelated functionalities. Separate teams need to coordinate closely even when working on different domains.&lt;/p&gt;

&lt;p&gt;This is what I call state management hell!&lt;/p&gt;

&lt;h2&gt;
  
  
  Enters automatic revalidation
&lt;/h2&gt;

&lt;p&gt;So far, we've been only talking about the state that lives in the browser. But if our app needs to store and fetch data from any kind of server, it also has a state on the server side.&lt;/p&gt;

&lt;p&gt;In our example with a centralized state, both the browser and the server need to know who's the current user. Once we sign the user in, the server stores its state somewhere, and the React SPA somewhere else.&lt;/p&gt;

&lt;p&gt;And then, every time we change the server state, we also have to update the browser state. Having two sources of truth is the origin of our state management hell.&lt;/p&gt;

&lt;p&gt;Whenever we have two sources of truth, synchronization issues emerge over time. But unfortunately, we cannot avoid having separate browser and server states in today's world.&lt;/p&gt;

&lt;p&gt;Due to the nature of web browsers, the only way to have a single source of truth is to do a full page refresh every time we make a change to the state. If we do that, the state lives 100% on the server.&lt;/p&gt;

&lt;p&gt;But &lt;a href="https://dev.to/danielweinmann/the-software-development-paradigm-for-the-2020s-5g8g"&gt;it's the 2020s&lt;/a&gt;, and we need a better UX than that. That's where abstractions like libraries and frameworks are useful. If we can't avoid having two sources of truth, let's at least hide this complexity from developers most of the time.&lt;/p&gt;

&lt;p&gt;That's what automatic revalidation does. It lets us code as if there were a single source of truth, even though behind the scenes, there are two.&lt;/p&gt;

&lt;p&gt;Let's walk through our example again, but now it uses React Router 6.4+ or Remix. Our top bar is rendered by a &lt;a href="https://reactrouter.com/en/main/start/tutorial#the-root-route" rel="noopener noreferrer"&gt;root route&lt;/a&gt; that will have all other routes &lt;a href="https://reactrouter.com/en/main/start/tutorial#nested-routes" rel="noopener noreferrer"&gt;nested&lt;/a&gt; inside of it.&lt;/p&gt;

&lt;p&gt;Instead of relying on our custom application state, we'll use a &lt;a href="https://reactrouter.com/en/main/route/loader" rel="noopener noreferrer"&gt;loader&lt;/a&gt; on the root route to fetch the current user from the server and display it on the top bar.&lt;/p&gt;

&lt;p&gt;Then, on the sign-in route, we'll use a &lt;a href="https://reactrouter.com/en/main/components/form" rel="noopener noreferrer"&gt;Form&lt;/a&gt; and an &lt;a href="https://reactrouter.com/en/main/route/action" rel="noopener noreferrer"&gt;action&lt;/a&gt; to sign the user in. The action does the authentication and redirects to the home page.&lt;/p&gt;

&lt;p&gt;Because we used an action, React Router knows we are probably changing something on the server state. Then it does something magically simple: it runs all loaders on the page again.&lt;/p&gt;

&lt;p&gt;That makes the root route re-fetch the current user from the server and re-render, now with the correct avatar at the top bar.&lt;/p&gt;

&lt;p&gt;By assuming that if we're running an action we probably want to re-fetch all data on the rest of the page, Remix and React Router are solving our problem in the best way possible: by making it disappear!&lt;/p&gt;

&lt;p&gt;Of course, there are exceptions. Sometimes our loaders take too long to run, and we want more granular control over what gets revalidated.&lt;/p&gt;

&lt;p&gt;All of that and more &lt;a href="https://reactrouter.com/en/main/route/should-revalidate" rel="noopener noreferrer"&gt;is covered&lt;/a&gt; by both frameworks. Think of automatic revalidation as an excellent default that you can use for 90% of use cases and override whenever you need to.&lt;/p&gt;

&lt;p&gt;But even if you have to do a little state management for 10% of your use cases, you're out of state management hell. Welcome!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>react</category>
      <category>reactrouter</category>
      <category>remix</category>
    </item>
    <item>
      <title>Why React Router's new data APIs boost productivity</title>
      <dc:creator>Daniel Weinmann</dc:creator>
      <pubDate>Thu, 21 Dec 2023 21:13:22 +0000</pubDate>
      <link>https://dev.to/seasonedcc/why-react-routers-new-data-apis-boost-productivity-pe4</link>
      <guid>https://dev.to/seasonedcc/why-react-routers-new-data-apis-boost-productivity-pe4</guid>
      <description>&lt;p&gt;We've been using &lt;a href="https://remix.run/" rel="noopener noreferrer"&gt;Remix&lt;/a&gt; at Seasoned for about two years now. And we consistently found that our teams are &lt;a href="https://dev.to/danielweinmann/the-software-development-paradigm-for-the-2020s-5g8g"&gt;more productive&lt;/a&gt; with it.&lt;/p&gt;

&lt;p&gt;In the past year or so, we also worked on a big React Router SPA to leverage &lt;a href="https://reactrouter.com/en/main/routers/picking-a-router#using-v64-data-apis" rel="noopener noreferrer"&gt;the new data APIs&lt;/a&gt;. And the impact was very similar: a huge improvement in our team's throughput!&lt;/p&gt;

&lt;p&gt;In this article, I'll share why React Router made that happen. There are many other benefits to adopting loaders and actions in your SPA, like &lt;a href="https://www.youtube.com/watch?v=95B8mnhzoCM" rel="noopener noreferrer"&gt;page speed and better UX&lt;/a&gt;. But I'll focus on developer productivity today.&lt;/p&gt;

&lt;p&gt;Before using the new data APIs, about two-thirds of our time was dedicated to managing our data fetching/mutation state. There are two main reasons for that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;When managing state at the component level, we need to check if the data was already fetched, render a pending UI if not, and render our component when the data is ready. A similar approach is necessary for dealing with errors. For every component.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Whenever we mutate data, we need to manually invalidate any state that became stale due to the changes. It's very hard to remember which states to invalidate at each mutation, and most of our bugs had to do with someone forgetting to revalidate something. And because we didn't know which component invalidated which state after each mutation, it took us a long time to diagnose these issues.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These challenges are not solved by &lt;a href="https://tanstack.com/query/latest" rel="noopener noreferrer"&gt;TanStack Query&lt;/a&gt; (FKA React Query), &lt;a href="https://swr.vercel.app/" rel="noopener noreferrer"&gt;SWR&lt;/a&gt;, and most similar libraries. But React Router 6.4+ makes them disappear!&lt;/p&gt;

&lt;p&gt;Here's how we solved them with React Router:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;When you use a &lt;a href="https://reactrouter.com/en/main/route/loader" rel="noopener noreferrer"&gt;loader&lt;/a&gt;, your component is only rendered after the data is fetched. So you don't have to worry about pending UI. In our app, we rely on a &lt;a href="https://dev.to/remix-run-br/creating-a-github-like-progress-bar-for-your-remix-app-153l"&gt;global pending UI&lt;/a&gt; for most interactions, and that's it. For dealing with errors, we rely on &lt;a href="https://reactrouter.com/en/main/route/error-element" rel="noopener noreferrer"&gt;error elements&lt;/a&gt; and &lt;a href="https://reactrouter.com/en/main/route/loader#throwing-in-loaders" rel="noopener noreferrer"&gt;response-throwing&lt;/a&gt; to keep our component focused on the happy path.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;React Router revalidates all loaders on the current route after each &lt;a href="https://reactrouter.com/en/main/route/action" rel="noopener noreferrer"&gt;action&lt;/a&gt;, so we don't have to do any state management for mutations. Zero.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;By eliminating these two problems, we reduced the cognitive load of our devs by an order of magnitude, I believe. Having to think so much about our app's state was slowing us down. And if you're not taking advantage of the data APIs yet, I bet it's dragging you down too!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>react</category>
      <category>reactrouter</category>
      <category>remix</category>
    </item>
    <item>
      <title>The software development paradigm for the 2020s</title>
      <dc:creator>Daniel Weinmann</dc:creator>
      <pubDate>Tue, 19 Dec 2023 15:43:10 +0000</pubDate>
      <link>https://dev.to/seasonedcc/the-software-development-paradigm-for-the-2020s-5g8g</link>
      <guid>https://dev.to/seasonedcc/the-software-development-paradigm-for-the-2020s-5g8g</guid>
      <description>&lt;p&gt;It's hard to see among all the new frameworks promising the world, but a new software development paradigm emerged in the last few months. And it makes building software better and cheaper.&lt;/p&gt;

&lt;p&gt;Please follow me as I try to lay it out in a non-technical way, as well as its ramifications for businesses.&lt;/p&gt;

&lt;h2&gt;
  
  
  What do I mean by paradigm?
&lt;/h2&gt;

&lt;p&gt;A paradigm is a way to see the world that creates distinct patterns from the ones that existed before. In software development, this usually means that a new generation of coders will see things in a different light and create technologies we couldn't anticipate.&lt;/p&gt;

&lt;p&gt;Before frameworks like &lt;a href="https://rubyonrails.org/" rel="noopener noreferrer"&gt;Ruby on Rails&lt;/a&gt; or &lt;a href="https://laravel.com/" rel="noopener noreferrer"&gt;Laravel&lt;/a&gt; existed, devs felt it was common sense to organize things in a way that looked chaotic after we got used to the new paradigm they introduced.&lt;/p&gt;

&lt;p&gt;Before &lt;a href="https://react.dev/" rel="noopener noreferrer"&gt;React&lt;/a&gt; popularized &lt;a href="https://en.wikipedia.org/wiki/Reactive_programming" rel="noopener noreferrer"&gt;reactive programming&lt;/a&gt; and made it the norm for frontend work, we were all used to working 10x more to ensure a great user experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  The current status quo
&lt;/h2&gt;

&lt;p&gt;Since the 2010s, the most common paradigm has three main characteristics:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;It relies on a separation between the backend and frontend, with each of them commonly having separate teams and tech stacks. That was not the norm in the 2000s when frontend and backend coexisted within the same codebase.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It expects rich user interfaces with real-time updates, no full-screen refreshes, and complex interactions. That was virtually impossible before the 2010s, but we got used to it fast.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Developers spend the bulk of their time on synchronization between what the client knows (e.g., a web browser, a mobile app, an IoT app) and what the server knows (i.e., our backend, typically in the form of a REST or GraphQL API). Whenever the client wants to make a change, it has to inform the server, show a loading indicator, and then update the whole app based on the response from the server.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  A new paradigm emerges
&lt;/h2&gt;

&lt;p&gt;I'll save the story about how we got to it for another article, but here are the characteristics of the new paradigm:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;It expects even richer user interfaces than the current status quo.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It doesn't require separation between backend and frontend work but allows for it when necessary. Going one step further, it facilitates the integration of backend and frontend code in the same codebase, ideally the same files.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It automatically synchronizes what the client and the server know for most cases while still allowing for manual synchronization when needed. That is the disruptive innovation right there. Anyone who has tried to automate this while still maintaining rich UIs knows how big of a challenge this is. But we finally got there.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Remixing our stack
&lt;/h2&gt;

&lt;p&gt;I'm sure someone will point out an obscure technology that implemented something similar before. But, for me, credit for this innovation goes to &lt;a href="https://twitter.com/ryanflorence" rel="noopener noreferrer"&gt;Ryan Florence&lt;/a&gt; and &lt;a href="https://twitter.com/mjackson" rel="noopener noreferrer"&gt;Michael Jackson&lt;/a&gt;, creators of &lt;a href="https://remix.run/" rel="noopener noreferrer"&gt;Remix&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;While React's &lt;a href="https://en.wikipedia.org/wiki/Server-side_scripting" rel="noopener noreferrer"&gt;server-side rendering&lt;/a&gt; capabilities and frameworks like &lt;a href="https://nextjs.org/" rel="noopener noreferrer"&gt;Next.js&lt;/a&gt; had already solved the synchronization between client and server when loading information, Remix was the first to solve it when changing information. And when I said that devs spend the bulk of their time syncing client and server, most of the work happens when we need to change something rather than when loading data.&lt;/p&gt;

&lt;p&gt;But, just like fish might not know what water is, it might take us a while to recognize the importance of this innovation. For most devs today, this synchronization doesn't feel like unnecessary work: it just feels like work.&lt;/p&gt;

&lt;p&gt;But as people realize this is a paradigm shift, many other frameworks and libraries will introduce their version of this innovation. And a new generation of engineers will look at us in bewilderment when we tell them we used to have to do all this.&lt;/p&gt;

&lt;h2&gt;
  
  
  Opportunities for businesses
&lt;/h2&gt;

&lt;p&gt;This new paradigm is not conceptual. It's already brilliantly implemented in Remix. Right now, companies who adopt Remix can expect:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;At least 3x in productivity while building better apps: we've measured this at Seasoned for six months and consistently delivered better software faster.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Happier, more engaged developers: the unexpected relief from not having to do gruntwork they weren't even aware of is life-changing for devs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;More flexible teams: all your devs can now work on the same tech stack without separating backend and frontend. Even if you have specialization in your team, it's not mandatory, and there's a lot of overlap between functions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A passionate untapped talent pool: more devs are dying to work with Remix than there are Remix positions. That will not last, but if you get onboard early, you'll have an unfair advantage for a while.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;(&lt;a href="https://www.linkedin.com/pulse/software-development-paradigm-2020s-seasoned-cc/" rel="noopener noreferrer"&gt;Originally posted&lt;/a&gt; on August 17, 2022)&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>react</category>
      <category>remix</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
