<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
  
  <title>Kasparas Anusauskas</title>
  <subtitle>For stuff I like to talk about, mostly with myself</subtitle>
  <link href="https://kasparasanusauskas.com/feed.xml" rel="self" />
  <link href="https://kasparasanusauskas.com/" />
  <updated>2026-01-29T00:00:00Z</updated>
  <id>https://kasparasanusauskas.com/</id>
  <author>
    <name>Kasparas Anusauskas</name>
  </author>
  <entry>
    <title>Test driven development and reading on how to simplify my code</title>
    <link href="https://kasparasanusauskas.com/posts/2021-01/01-test-driven-development-and-simplifying-code-using-functional-programming/" />
    <updated>2021-01-01T00:00:00Z</updated>
    <id>https://kasparasanusauskas.com/posts/2021-01/01-test-driven-development-and-simplifying-code-using-functional-programming/</id>
    <content type="html">&lt;h2&gt;Simple JS function&lt;/h2&gt;
&lt;p&gt;Today I&#39;ve read a nice article about Test Driven Development by &lt;a href=&quot;https://medium.com/@lee_85949&quot;&gt;Lee Cheneler&lt;/a&gt;:
&lt;a href=&quot;https://medium.com/@lee_85949/from-fizzbuzz-to-react-practical-test-driven-development-in-javascript-9fd05a4d1ad6&quot;&gt;https://medium.com/@lee_85949/from-fizzbuzz-to-react-practical-test-driven-development-in-javascript-9fd05a4d1ad6&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Do read it, all examples in this post are taken from there. Remember these are my daily notes helping me to commit to learning something every day, not original content.&lt;/p&gt;
&lt;p&gt;The thing with TDD is, I always feel like it&#39;s just one step away. You could have this beautiful, logical, clean, instantly refactorable flow, but the tasks are usually so complex that you have to reverse engineer some old code + API, and try to implement some stuff to understand how all these endpoints map together etc. Then you have done half of the work, and it&#39;s kind of too late for the &amp;quot;TDD&amp;quot; approach.&lt;/p&gt;
&lt;p&gt;This has to change, thought. To help me commit, I&#39;ve decided to restart with the basics from the article.&lt;/p&gt;
&lt;p&gt;First example was developing &lt;code&gt;fizzbuzz&lt;/code&gt; function using the test driven development flow, which, turns out, is a task frequently given by interviewers. The task is very simple but lend&#39;s itself well to testing:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Write a program that prints the numbers from 1 to 100. But for multiples of three print “Fizz” instead of the number and for the multiples of five print “Buzz”. For numbers which are multiples of both three and five print “FizzBuzz”.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;To do this, we&#39;ll need:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Node&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://jestjs.io/docs/en/getting-started.html&quot;&gt;Jest&lt;/a&gt; (our test runner)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Here&#39;s how simple it is, really:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Install Jest&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; --save-dev jest

&lt;span class=&quot;token comment&quot;&gt;# Inside package.json, add jest command to your scripts:&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&quot;scripts&quot;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&quot;test&quot;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;jest&quot;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# Create test file&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;touch&lt;/span&gt; function.test.js

&lt;span class=&quot;token comment&quot;&gt;# Create file to test in a directory of your choice&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;touch&lt;/span&gt; function.js

&lt;span class=&quot;token comment&quot;&gt;# Write your first test in function.test.js (see Step 1 below) and run Jest to see it fail:&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; run &lt;span class=&quot;token builtin class-name&quot;&gt;test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;Write a test with a small task (return number which was passed as argument)&lt;/li&gt;
&lt;li&gt;Check that it fails&lt;/li&gt;
&lt;li&gt;Write code and make sure the test no longer fails&lt;/li&gt;
&lt;li&gt;Refactor if necessary, which is easy since we now have the test.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Here&#39;s the test:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; fizzbuzz &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;./_tdd-function&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Step 1&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;should return the numbers passed in that are not divisable by 3 or 5&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fizzbuzz&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toBe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fizzbuzz&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toBe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fizzbuzz&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toBe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fizzbuzz&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;7&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toBe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;7&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fizzbuzz&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;11&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toBe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;11&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Step 2&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;should return &#39;fizz&#39; if the number passed in is divisable by 3 &quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fizzbuzz&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toBe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;fizz&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fizzbuzz&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toBe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;fizz&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fizzbuzz&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;9&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toBe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;fizz&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fizzbuzz&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;12&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toBe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;fizz&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Step 3&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;should return &#39;fuzz&#39; if the number passed in is divisable by 5 &quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fizzbuzz&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toBe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;buzz&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fizzbuzz&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toBe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;buzz&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// expect(fizzbuzz(15)).toBe(&quot;buzz&quot;); &amp;lt;-- Too early for that, 15 is divisable both by 3 and 5...&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fizzbuzz&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toBe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;buzz&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Step 4&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;should return &#39;fizzbuzz&#39; if the number passed in is divisable by 3 and 5 &quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fizzbuzz&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;15&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toBe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;fizzbuzz&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fizzbuzz&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;30&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toBe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;fizzbuzz&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fizzbuzz&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;45&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toBe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;fizzbuzz&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And here&#39;s the function:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;fizzbuzz&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;num&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// Could write (num % 15 === 0) which sounds clever but I think is less descriptive&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;num &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; num &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;fizzbuzz&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;num &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;fizz&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;num &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;buzz&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; num&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;exports &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; fizzbuzz &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You write one test, and implement the functionality. Then move to another. It&#39;s super pleasant and simple.&lt;/p&gt;
&lt;h2&gt;A React component&lt;/h2&gt;
&lt;p&gt;Principle is the same with a react component. There&#39;s a lot more trouble in preparing the environment for it, though. Not only does one need Jest, but also some library to help selecting stuff, mocking clicks etc. But, the principle is largely the same. You write a test, make it fail, make it work, refactor until it&#39;s nice.&lt;/p&gt;
&lt;p&gt;The test:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// http://localhost:8000/react-dev-demos/day1&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; React&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; useState &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;react&quot;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Refactored from class:&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;/* 
export class Counter extends React.Component {
  constructor() {
    super()
    this.increment = this.increment.bind(this)
    this.state = {
      count: 0,
    }
  }

  increment() {
    this.setState({
      count: this.state.count + 1,
    })
  }

  render() {
    return (
      &amp;lt;&gt;
        &amp;lt;span data-testid=&quot;count&quot;&gt;Count: {this.state.count}&amp;lt;/span&gt;
        &amp;lt;button type=&quot;button&quot; onClick={this.increment}&gt;
          Increment
        &amp;lt;/button&gt;
      &amp;lt;/&gt;
    )
  }
}
*/&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// To functional component:&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;Counter&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;count&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; setCount&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;increment&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;setCount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;count &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
            &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;span data&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;testid&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;count&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;Count&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;count&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;span&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
            &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;button type&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;button&quot;&lt;/span&gt; onClick&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;increment&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
                Increment
            &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;button&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Day1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; Counter
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The component:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; React &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;react&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; render&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; fireEvent &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;@testing-library/react&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;@testing-library/jest-dom/extend-expect&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; Counter &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;./_tdd-react-component&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token function&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;should display the start count as 0&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; getByTestId &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Counter &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token function&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getByTestId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;count&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toHaveTextContent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Count: 0&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token function&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;should increase the count by 1 when increase button is clicked&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; getByTestId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; getByText &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Counter &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    fireEvent&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;click&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getByText&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Increment&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getByTestId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;count&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toHaveTextContent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Count: 1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    fireEvent&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;click&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getByText&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Increment&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getByTestId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;count&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toHaveTextContent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Count: 2&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The main benefit I see, is you have to really understand the task before doing this, and plan by writing the tests. And no one can argue it&#39;s not OK to do it properly. It&#39;s TDD, so it&#39;s cool, everyone knows that.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Learning to (kind of) enjoy test driven development in JS with simpler tests</title>
    <link href="https://kasparasanusauskas.com/posts/2021-01/02-test-driven-development-keeping-it-simple/" />
    <updated>2021-01-04T00:00:00Z</updated>
    <id>https://kasparasanusauskas.com/posts/2021-01/02-test-driven-development-keeping-it-simple/</id>
    <content type="html">&lt;h2&gt;&lt;/h2&gt;
&lt;p&gt;I have never &lt;em&gt;really&lt;/em&gt; used test driven development. When I tried, it got too clumsy, messy, I tried testing methods of my classes. Didn&#39;t really know what I was doing.&lt;/p&gt;
&lt;p&gt;The light came after attempting to TDD a functional component wish useState / useEffect hooks when they were released. No longer could I access state or internal methods of a class in my tests. Not without keeping them outside the component I then knew this is not how it was intended to be done.&lt;/p&gt;
&lt;p&gt;Along came the (https://testing-library.com/)[https://testing-library.com/] with it&#39;s guildelines about how one should test components exactly the way a user would use them.&lt;/p&gt;
&lt;p&gt;This means always rendering them to a virtual DOM, accessing elements by their DOM attributes, manipulating them and asserting based on DOM state. You just have no way to do more anymore. And that is pretty liberating in a sense that you can just focus on writing a decent test for the real use case.&lt;/p&gt;
&lt;p&gt;An additonal benefit is the encouragment to drop the long standing &amp;quot;1 assert per 1 test&amp;quot; rule (it&#39;s in the FAQ). The tooling is capable to show you exactly on which line the test fails while also outputing color coded &amp;quot;Expected vs Reality&amp;quot; DOM state. So it&#39;s just no longer useful to go through the trouble of writing more tests. Test a success case, test a failure case and that might just do it.&lt;/p&gt;
&lt;h2&gt;The old and the new ways&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;If there is a component involving:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Show &amp;quot;Loading...&amp;quot; text&lt;/li&gt;
&lt;li&gt;Fetch data A from endpoint a&lt;/li&gt;
&lt;li&gt;Fetch data B from endpoint b&lt;/li&gt;
&lt;li&gt;Process A using some Array.reduce + Array.filter etc.&lt;/li&gt;
&lt;li&gt;Process B using some Array.reduce + Array.filter etc.&lt;/li&gt;
&lt;li&gt;Merge processed data using some procedure only used in that component&lt;/li&gt;
&lt;li&gt;Display (correctly) merged data in a list&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;How I used to test it&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I write 7 or more tests for each step. If this was a functional component, I would moved the algorithms in 4, 5, 6 outside the component and test them separately.&lt;/p&gt;
&lt;p&gt;This isn&#39;t ideal, because this is a component, not some helper library. If requirements change, algoriths processing the data will change. 4, 5, 6, 7 will all get broken. Or, what if we use new library which performs exactly what 4, 5 used to do. We can&#39;t refactor 4 and 5 out without breaking the tests.&lt;/p&gt;
&lt;p&gt;However, as I mentioned, this is a component. All we care about is what the user sees is correct, as defined by the task, right?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How I&#39;m learning to do it now&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I write one longer test. In that test I check 1 and 7. Possibly, I also check if 2, 3 calls API only once, because are quite suitable for nasty bugs involving render loops.&lt;/p&gt;
&lt;h2&gt;Does this break TDD if I don&#39;t test each step?&lt;/h2&gt;
&lt;p&gt;Initially, I thought of it that way. How do I &amp;quot;write a small test and make it pass&amp;quot; If I don&#39;t write a test. I mean, we all know the mantra:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Split task into smallest logical parts&lt;/li&gt;
&lt;li&gt;Write a test for that small part&lt;/li&gt;
&lt;li&gt;Make it pass&lt;/li&gt;
&lt;li&gt;Repeat&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Then refactor to make it nice, make it fast. So how do I write the logic without testing it? Well, the key point for me was noticing that mostly, just as I said, the &#39;logic&#39; is just a few &#39;reduce&#39; + &#39;filter&#39; statements. Perfectly acceptable to write using just one test checking the final result.&lt;/p&gt;
&lt;p&gt;To tell you the truth, I didn&#39;t yet TDD a really big component (this is typically a large form with one input affecting value of others after calling API first). However I feel now I&#39;ll just be forced to split it further. And, if there&#39;s a lot of conditional logic, it&#39;s still always possible to move it to functions outside the component and test them separately.&lt;/p&gt;
&lt;p&gt;Any way, it&#39;s the first time I&#39;m actually looking forward to it.&lt;/p&gt;
&lt;h2&gt;Excercise&lt;/h2&gt;
&lt;p&gt;Here&#39;s the test:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; React&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; useEffect&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; useReducer&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; useState &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;react&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; axios &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;axios&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; initialState &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;charCounts&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;isLoading&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;charSelectReducer&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;state&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; action&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;action&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;SUCCESS&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token literal-property property&quot;&gt;charCounts&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; action&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;charCounts&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token literal-property property&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token literal-property property&quot;&gt;isLoading&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ERROR&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token literal-property property&quot;&gt;charCounts&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token literal-property property&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; action&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token literal-property property&quot;&gt;isLoading&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;CharSelect&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; lowercaseUrl&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; uppercaseUrl &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; setValue&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; charCounts&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; error&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; isLoading &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; dispatch&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useReducer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        charSelectReducer&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        initialState
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token function&quot;&gt;useEffect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;token&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; cancelToken&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; cancel &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; axios&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;CancelToken&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; lowercase &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; uppercase &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; Promise&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;all&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
                    axios&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;lowercaseUrl&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; cancelToken &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                    axios&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;uppercaseUrl&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; cancelToken &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; charCounts &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; lowercase
                    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;concat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;uppercase&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sort&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;a&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; b&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; a &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; b&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;reduce&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;acc&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; char&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                        &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; lowChar &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; char&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toLowerCase&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                        acc&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;lowChar&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;acc&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;lowChar&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; acc&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token function&quot;&gt;dispatch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;SUCCESS&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; charCounts &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token function&quot;&gt;dispatch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ERROR&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Unexpected error&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token function&quot;&gt;cancel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;axios&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;isLoading&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Loading...&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; error&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
            &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;select data&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;testid&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;select&quot;&lt;/span&gt; onChange&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;setValue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;target&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; value&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
                &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;option value&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt; data&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;testid&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;char-select-option&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
                    Select char
                &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;option&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;Object&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;keys&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;charCounts&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;char&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
                    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;option key&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;char&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; value&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;char&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; data&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;testid&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;char-select-option&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
                        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;char&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;charCounts&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;char&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
                    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;option&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;select&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
            &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;button&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;Next&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;button&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; CharSelect&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And here&#39;s the function:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; React&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; useEffect&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; useReducer&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; useState &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;react&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; axios &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;axios&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; initialState &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;charCounts&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;isLoading&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;charSelectReducer&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;state&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; action&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;action&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;SUCCESS&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token literal-property property&quot;&gt;charCounts&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; action&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;charCounts&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token literal-property property&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token literal-property property&quot;&gt;isLoading&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ERROR&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token literal-property property&quot;&gt;charCounts&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token literal-property property&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; action&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token literal-property property&quot;&gt;isLoading&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;CharSelect&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; lowercaseUrl&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; uppercaseUrl &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; setValue&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; charCounts&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; error&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; isLoading &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; dispatch&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useReducer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        charSelectReducer&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        initialState
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token function&quot;&gt;useEffect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;token&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; cancelToken&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; cancel &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; axios&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;CancelToken&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; lowercase &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; uppercase &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; Promise&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;all&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
                    axios&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;lowercaseUrl&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; cancelToken &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                    axios&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;uppercaseUrl&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; cancelToken &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; charCounts &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; lowercase
                    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;concat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;uppercase&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sort&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;a&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; b&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; a &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; b&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;reduce&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;acc&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; char&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                        &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; lowChar &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; char&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toLowerCase&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                        acc&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;lowChar&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;acc&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;lowChar&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; acc&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token function&quot;&gt;dispatch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;SUCCESS&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; charCounts &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token function&quot;&gt;dispatch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ERROR&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Unexpected error&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token function&quot;&gt;cancel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;axios&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;isLoading&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Loading...&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; error&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
            &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;select data&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;testid&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;select&quot;&lt;/span&gt; onChange&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;setValue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;target&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; value&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
                &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;option value&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt; data&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;testid&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;char-select-option&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
                    Select char
                &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;option&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;Object&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;keys&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;charCounts&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;char&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
                    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;option key&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;char&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; value&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;char&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; data&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;testid&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;char-select-option&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
                        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;char&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;charCounts&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;char&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
                    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;option&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;select&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
            &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;button&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;Next&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;button&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; CharSelect&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;
</content>
  </entry>
  <entry>
    <title>Track view duration of fake social network feed items using intersection observer. Part 1.</title>
    <link href="https://kasparasanusauskas.com/posts/2021-08/social-feed-item-view-duration-tracking-part-1/" />
    <updated>2021-08-11T00:00:00Z</updated>
    <id>https://kasparasanusauskas.com/posts/2021-08/social-feed-item-view-duration-tracking-part-1/</id>
    <content type="html">&lt;h2&gt;What we&#39;re gonna Build&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Weekly challenge is for experiments, I reserve up to 7 hours from my week for those, including writing the blog. This is not production grade stuff. You have been warned.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Social networks and websites often have feeds where you scroll through content items. It&#39;s then common sense they would measure the view duration of each piece. This would greatly help in serving even more &amp;quot;similarly engaging, intelligence-stimulating content&amp;quot;, because that&#39;s what we like to see, right? I&#39;ve recently seen a movie, &lt;a href=&quot;https://www.google.com/search?q=imdb+the+social+dilemma&quot;&gt;The social dilemma&lt;/a&gt; which talks about this stuff a lot. I don&#39;t agree with the narrative it makes social networks &amp;quot;evil&amp;quot;, but I still enjoyjed it.&lt;/p&gt;
&lt;p&gt;Let&#39;s build a feed of nice images and measure ourselves to see how the whole tracking thing can be built easily, and how the results look, real time.&lt;/p&gt;
&lt;p&gt;To begin with, feel free to watch this demo of the final app. Or just leave it to the end.&lt;/p&gt;
&lt;div class=&quot;videoWrapper&quot;&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube-nocookie.com/embed/LjVAYFTzb1I&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;h2&gt;The app&lt;/h2&gt;
&lt;p&gt;Nothing takes away the fun better than a good, detailed plan. Let&#39;s use React, and build these components:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ViewDurationTracker - renders content, fires callback passed from parent with view duration when it changes;&lt;/li&gt;
&lt;li&gt;Feed - assembles a categorized, pseudo-randomized list of nice photos, and passes them down using props to any component it is supplied;&lt;/li&gt;
&lt;li&gt;Dashboard - displays list of most popular content, and view duration&lt;/li&gt;
&lt;li&gt;Controller - listens for callbacks with view duration for all items, can pass data to another component (dashboard)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I will not present all the code here, as the stuff does get a bit lengthy. Feel free to check it on &lt;a href=&quot;https://github.com/Gonusi/in-browser-view-duration-tracking-using-intersection-observer&quot;&gt;github&lt;/a&gt; or run it on &lt;a href=&quot;https://codesandbox.io/s/in-browser-feed-item-view-duration-tracking-using-intersection-observer-zxgi7&quot;&gt;codesandbox&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Let&#39;s discuss key parts and view the result.&lt;/p&gt;
&lt;h3&gt;ViewDurationTracker - the main element enabling us to very easily time the view&lt;/h3&gt;
&lt;p&gt;To build this stuff, first I had to put down some ground rules.&lt;/p&gt;
&lt;p&gt;On Instagram, when looking at a photo, I sometimes scroll it out of the viewport so my fingers don&#39;t obstruct the view. So, I&#39;ll say that when &lt;strong&gt;85% of an element is in the viewport, we&#39;ll consider it &amp;quot;being viewed&amp;quot;&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Obviously this is greatly simplified, and there is probably a whole branch of analytics just for getting the various &amp;quot;in view&amp;quot; factors right - but 85% is gonna work fine for our 7 hours of alloted time :)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Let&#39;s explore the components responsibilities in more detail:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;render children in a div;&lt;/li&gt;
&lt;li&gt;when 85% enters viewport, save the &lt;code&gt;lastEntryDate&lt;/code&gt; in state;&lt;/li&gt;
&lt;li&gt;when 85% leaves the viewport, calculate view duration as difference between &lt;code&gt;Date.now()&lt;/code&gt; and &lt;code&gt;lastEntryDate&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;call callback passed from parent as a prop with view view duration and some info about the element.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Simple stuff. Yet when I tried to &lt;em&gt;test-driven-develop-it&lt;/em&gt; I found testing it is not possible using regular &lt;a href=&quot;https://jestjs.io/&quot;&gt;Jest&lt;/a&gt; / &lt;a href=&quot;https://testing-library.com/docs/react-testing-library/intro/&quot;&gt;React testing library&lt;/a&gt; / &lt;a href=&quot;https://github.com/jsdom/jsdom&quot;&gt;jsdom&lt;/a&gt; setup, because &lt;em&gt;jsdom does not support real layout, making us unable to test logic based on scroll position&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Fortunately, we now have &lt;a href=&quot;https://www.cypress.io/&quot;&gt;Cypress&lt;/a&gt; which renders tests in a real browser. Lately, it can even render react components for unit testing without having any kind of &amp;quot;app&amp;quot;. I&#39;ll make sure to write about it later.&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// To run:&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// npx cypress open-ct&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; React &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;react&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; mount &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;@cypress/react&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; ViewDurationTracker &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;./ViewDurationTracker&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token function&quot;&gt;beforeEach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  cy&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;viewport&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;320&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;640&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token function&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;renders children inside a div&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;mount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;ViewDurationTracker&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;I&lt;/span&gt; am a single child&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;ViewDurationTracker&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  cy&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;[data-cy=TimedViewContainer]&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;contains&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;I am a single child&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token function&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;calls callback passing correct arguments&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// Let&#39;s use window and not worry about it for once.&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// cy.spy only accepst [object, property] so &lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// let&#39;s not complicate our own lives for now.  &lt;/span&gt;
  window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;callback&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; viewDuration&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; properties &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;viewDuration&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;to&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;be&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;greaterThan&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// Expect 200ms view duration, but allow up to 250ms &lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// The programattic scrolling part consumes a few ms so we&#39;re adjusting for taht&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;viewDuration&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;to&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;be&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;lessThan&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;150&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;properties&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;to&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;be&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;equal&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  cy&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;spy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;window&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;callback&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;as&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;callback&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;mount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;div style&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;paddingTop&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;10000px&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;ViewDurationTracker onViewDurationMsChange&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;callback&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; properties&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
        &lt;span class=&quot;token constant&quot;&gt;I&lt;/span&gt; am a single child
      &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;ViewDurationTracker&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;div&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// At this point we&#39;re in a real browser, on the top of the viewport&lt;/span&gt;
  cy&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;scrollTo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;bottom&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// We&#39;ve now scrolled ±10000px down&lt;/span&gt;
  cy&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;wait&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// After waiting a 200ms we scroll back up&lt;/span&gt;
  cy&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;scrollTo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;top&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// And we check that our callback was called.&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// Callback above contains more checks&lt;/span&gt;
  cy&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;@callback&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;should&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;have.been.called&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// Possibly the more &quot;Cypress&#39;y&quot; way to do this for reference:&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// (I don&#39;t like it):&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// cy.get(&quot;@callback&quot;).should(&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;//  &quot;have.been.calledWithMatch&quot;,&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;//  Cypress.sinon.match.number&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;//    .and(Cypress.sinon.match((x) =&gt; x &gt; 100, &quot;&gt; 100&quot;))&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;//    .and(Cypress.sinon.match((x) =&gt; x &amp;lt; 150, &quot;&amp;lt; 150&quot;))&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// );&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And that&#39;s the test. I was blown away by comparing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;my thoughts on how much trouble it is gonna be to write it&lt;/li&gt;
&lt;li&gt;how little trouble it actually was&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;After having the test, the component itself was very easy too. We&#39;re building on the shoulder of giants people, and using the (Intersection Observer API)[https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API].&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; React&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; useEffect&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; useRef&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; useReducer &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;react&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; initialState &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;lastEntryTime&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;viewDuration&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Using reducer because my 3 simple rules of determining &quot;will you get f****d&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// up by the functional component&quot; say I indeed will:&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// - Does it pass a callback to some function? // true&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// - Does the callback modify state ? // true&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// - Does the state modification relies on previous state? // true When all 3&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;//   are true, you will have stale state problems due to JS closures, just&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;//   manage the state in a reducer like this to avoid it. The callback only&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;//   dispatches actions, and does not need to know anything about the state,&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;//   avoiding problems entirely If in doubt, read the excellent article about it&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;//   here. Read it all, and read it at least 3 times:&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;//   https://overreacted.io/a-complete-guide-to-useeffect/&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;reducer&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;state&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; action&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;action&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;enterViewport&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;lastEntryTime&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Date&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;leaveViewport&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;viewDuration&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;lastEntryTime
          &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;viewDuration &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Date&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;lastEntryTime&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;ViewDurationTracker&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  children&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  properties&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  onViewDurationMsChange&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;props
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; dispatch&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useReducer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;reducer&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; initialState&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; elementRef &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useRef&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// This gets called when interesection observer says &quot;85% of element has&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// entered or left the viewport&quot;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;intersectionCallback&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;entries&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    entries&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;entry&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;entry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;isIntersecting&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;dispatch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;enterViewport&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;dispatch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;leaveViewport&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;useEffect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// On startup, setup the intersection observer for this component instance&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; observer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IntersectionObserver&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;intersectionCallback&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;threshold&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.85&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    observer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;observe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;elementRef&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;current&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// Don&#39;t forget to disconnect it when the instance says bye&lt;/span&gt;
      observer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;disconnect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;useEffect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;viewDuration&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// when view duration of this instance changes, call callback from parent&lt;/span&gt;
      onViewDurationMsChange&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;viewDuration&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;viewDuration&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 
        properties &lt;span class=&quot;token comment&quot;&gt;// might be an {id}, might be anything else...&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;viewDuration&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;div data&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;cy&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;TimedViewContainer&quot;&lt;/span&gt; ref&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;elementRef&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;props&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;children&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;div&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; ViewDurationTracker&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At this point, we can  track the duration of a single item. We still need to deploy a swarm of these trackers on a fake &amp;quot;feed&amp;quot; of images, aggregate all their view durations as the user scrolls through and display the data.&lt;/p&gt;
&lt;h2&gt;Feed, Dashboard and Controller&lt;/h2&gt;
&lt;h3&gt;Fetch photo data using unsplash.com API&lt;/h3&gt;
&lt;p&gt;For the experiment, we&#39;ll need something to track. Unsplash has a wonderful API allowing us to search fetch a list of image data based on many search criteria. I&#39;ve assembled a list of such images fetched from multiple categories (see below). Then, transposed the json data a it as to avoid fiddling with the objects during run time.&lt;/p&gt;
&lt;p&gt;Here&#39;s a node script I scribbled to use the API:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// To run, from script directory:&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// node ./getPhotos.js&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; fetch &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;node-fetch&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; fs &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;fs&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; unsplash &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;unsplash-js&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;ACCESS_KEY&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;mfpNJ7dKkJlWHZ9NFd6fyBhVSLiFZKC90D0dVN48JIs&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;UTM_PARAMS&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;utm_source=ScrollObserver2&amp;amp;utm_medium=referral&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;CATEGORIES&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
	&lt;span class=&quot;token string&quot;&gt;&quot;animals&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;token string&quot;&gt;&quot;architecture&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;token string&quot;&gt;&quot;cars&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;token string&quot;&gt;&quot;fashion&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;token string&quot;&gt;&quot;man&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;token string&quot;&gt;&quot;motorbikes&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;token string&quot;&gt;&quot;nature&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;token string&quot;&gt;&quot;sports&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;token string&quot;&gt;&quot;street&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;token string&quot;&gt;&quot;underwater&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;token string&quot;&gt;&quot;woman&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; api &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; unsplash&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createApi&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token literal-property property&quot;&gt;accessKey&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;ACCESS_KEY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
	fetch&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fetchPhotoData&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; promises&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		promises &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;CATEGORIES&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;category&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;info&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Will fetch category &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;category&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
			&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; api&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;search&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getPhotos&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
				&lt;span class=&quot;token literal-property property&quot;&gt;orientation&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;portrait&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
				&lt;span class=&quot;token literal-property property&quot;&gt;featured&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
				&lt;span class=&quot;token literal-property property&quot;&gt;page&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
				&lt;span class=&quot;token literal-property property&quot;&gt;perPage&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;30&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Undocumented max seems to be 30.&lt;/span&gt;
				&lt;span class=&quot;token literal-property property&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; category&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
			&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; responses &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; Promise&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;all&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;promises&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; idsByCategory &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; dataByIds &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token constant&quot;&gt;CATEGORIES&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;category&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; index&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; categoryPhotos &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; responses&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;index&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;results&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		idsByCategory&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;category&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; categoryPhotos&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; categoryPhotoData &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Object&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;categoryPhotos&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;reduce&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;acc&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; photo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
				&lt;span class=&quot;token literal-property property&quot;&gt;acknowledgeUrl&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; photo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;links&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;download_location&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
				&lt;span class=&quot;token literal-property property&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; photo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;description&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
				&lt;span class=&quot;token literal-property property&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; photo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
				&lt;span class=&quot;token literal-property property&quot;&gt;likes&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; photo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;likes&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
				&lt;span class=&quot;token literal-property property&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; photo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;urls&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;regular&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
				&lt;span class=&quot;token literal-property property&quot;&gt;unsplashLink&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;https://unsplash.com?&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;UTM_PARAMS&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
				&lt;span class=&quot;token literal-property property&quot;&gt;userLink&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;photo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;user&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;links&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;html&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;UTM_PARAMS&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
				&lt;span class=&quot;token literal-property property&quot;&gt;username&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; photo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;user&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;username&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
				category&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
			&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
			acc&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;photo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
			&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; acc&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		dataByIds &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;dataByIds&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;categoryPhotoData &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; categories &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Object&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;keys&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;idsByCategory&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		categories&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
		idsByCategory&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
		dataByIds&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	fs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;writeFileSync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;photos.json&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stringify&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;result&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;info&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Result:&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;info&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;We&#39;re done.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token function&quot;&gt;fetchPhotoData&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Feed&lt;/h3&gt;
&lt;p&gt;Using the photo data fetched, building a list is trivial. However, I wanted to add some additional functionality to ensure all categories have an equal chance to be displayed, with a certain level of randomization.&lt;/p&gt;
&lt;p&gt;The algorithm should roughly:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;randomize order of categories;&lt;/li&gt;
&lt;li&gt;randomize order of images in each category;&lt;/li&gt;
&lt;li&gt;take first image from first category, append to feed;&lt;/li&gt;
&lt;li&gt;take first image from second category, append to feed;&lt;/li&gt;
&lt;li&gt;...&lt;/li&gt;
&lt;li&gt;when categories run out, repeat with the second image;&lt;/li&gt;
&lt;li&gt;...&lt;/li&gt;
&lt;li&gt;when images run out, stop.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This allows us to distribute categories through the list nicely. Later I&#39;ve discovered a bug (ocassionaly one category is shown multiple times), but had no more time to fix it or to add a test for it, at least in this iteration.&lt;/p&gt;
&lt;p&gt;See the code on:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;(codesandbox)[https://codesandbox.io/s/in-browser-feed-item-view-duration-tracking-using-intersection-observer-zxgi7?file=/src/componentDemoViews/FeedDemoView.jsx]&lt;/li&gt;
&lt;li&gt;(Github)[https://github.com/Gonusi/in-browser-view-duration-tracking-using-intersection-observer/blob/main/src/components/Feed/Feed.jsx]&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Controller&lt;/h3&gt;
&lt;p&gt;This one accumulates the view duration of all elements and sorts them. It should be capable of accepting a minimum view duration to track - so that when user scrolls past the list very fast, these images don&#39;t get recorded. That, I&#39;ve found, would only creates noise in our results - user does not actually view these images (unless user is a machine) anyway.&lt;/p&gt;
&lt;p&gt;So, the plan is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;pass a &lt;code&gt;onViewDurationChange&lt;/code&gt; callback / handler to the child Feed component;&lt;/li&gt;
&lt;li&gt;when it&#39;s fired, take the &lt;code&gt;{viewDuration, imageSrc}&lt;/code&gt; or similar;&lt;/li&gt;
&lt;li&gt;put this data into an object containing list of viewed images;&lt;/li&gt;
&lt;li&gt;if such &lt;code&gt;imageSrc&lt;/code&gt; already exists in that list, add the &lt;code&gt;viewDuration&lt;/code&gt; to the previous `viewDuration;&lt;/li&gt;
&lt;li&gt;sort the results based on view duration to form a top 20 list.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;See the code on:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://codesandbox.io/s/in-browser-feed-item-view-duration-tracking-using-intersection-observer-zxgi7?file=/src/components/Controller/Controller.jsx&quot;&gt;codesandbox&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/Gonusi/in-browser-view-duration-tracking-using-intersection-observer/blob/main/src/components/Controller/Controller.jsx&quot;&gt;Github&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Dashboard&lt;/h3&gt;
&lt;p&gt;Top 20 images images should be displayed with their respective view duration in seconds at the botttom of the screen. Clicking on an image should scroll the feed to that image so the user can see it better when browsing the results. So, it also should have controls for pausing the tracking.&lt;/p&gt;
&lt;p&gt;See the code on:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://codesandbox.io/s/in-browser-feed-item-view-duration-tracking-using-intersection-observer-zxgi7?file=/src/components/Dashboard/Dashboard.jsx&quot;&gt;codesandbox&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/Gonusi/in-browser-view-duration-tracking-using-intersection-observer/blob/main/src/components/Dashboard/Dashboard.jsx&quot;&gt;Github&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Results&lt;/h2&gt;
&lt;p&gt;It works quite nicely. I&#39;ve tested the thing multiple times, and yes, if you forget you&#39;re being tracked, you get some results you:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;might not expect;&lt;/li&gt;
&lt;li&gt;might expect but still be a bit ashamed of;&lt;/li&gt;
&lt;li&gt;might be interested by.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The app quickly run into limitations due to the nature of how I selected the images, lack of any statistical methods implemented to actually present results.&lt;/p&gt;
&lt;p&gt;The categories are very broad and non descriptive. For example, &amp;quot;nature&amp;quot; can range from a photo of a river valley, to stuff that is more like two color abstract painting. I prefered the latter, so to interpret results more meaningfully, I&#39;d have to find better ways to categorise / describe the images.&lt;/p&gt;
&lt;p&gt;I had these search terms / categories in the test:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;	&lt;span class=&quot;token string-property property&quot;&gt;&quot;categories&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
		&lt;span class=&quot;token string&quot;&gt;&quot;animals&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
		&lt;span class=&quot;token string&quot;&gt;&quot;architecture&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
		&lt;span class=&quot;token string&quot;&gt;&quot;cars&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
		&lt;span class=&quot;token string&quot;&gt;&quot;fashion&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
		&lt;span class=&quot;token string&quot;&gt;&quot;man&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
		&lt;span class=&quot;token string&quot;&gt;&quot;motorbikes&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
		&lt;span class=&quot;token string&quot;&gt;&quot;nature&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
		&lt;span class=&quot;token string&quot;&gt;&quot;sports&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
		&lt;span class=&quot;token string&quot;&gt;&quot;street&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
		&lt;span class=&quot;token string&quot;&gt;&quot;underwater&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
		&lt;span class=&quot;token string&quot;&gt;&quot;woman&quot;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Roughly, the top 5 images mostly tended to include a variation of &lt;code&gt;[nature, motorbikes, underwater, woman, fashion]&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I am a ±30 year old male, enjoying enduro motorcycling, so it&#39;s all very expected.  However, this is still very interesting stuff, and for my next experiment I might update the tracker, build it into a chrome plugin so I could check the view duration of any web content. Then I could track facebook feeds etc.&lt;/p&gt;
&lt;p&gt;Also, I noticed I start scrolling faster when I get bored, and &amp;quot;preferred images in the end of session&amp;quot; get less view time than they would have gotten if they were seen in the beginning of the session.&lt;/p&gt;
&lt;p&gt;And these feeds are not only photos, it contains political stuff etc. For now, the results are best shown in a video, so enjoy:&lt;/p&gt;
&lt;h3&gt;Improvements to be made in next iteration, should there be one.&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;add categories below images in Dashboard;&lt;/li&gt;
&lt;li&gt;save the data of each session in localStorage and build a statistical dashboard to better interpret results;&lt;/li&gt;
&lt;li&gt;look for ways to adjust for increasing scroll speed as the session progresses.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Check the full code on &lt;a href=&quot;https://github.com/Gonusi/in-browser-view-duration-tracking-using-intersection-observer&quot;&gt;Github&lt;/a&gt; or&lt;a href=&quot;https://codesandbox.io/s/in-browser-feed-item-view-duration-tracking-using-intersection-observer-zxgi7?file=/src/components/ViewDurationTracker/ViewDurationTracker.jsx:0-1643%5D&quot;&gt;codesandbox&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Bye for now, have a great day.
Kasparas&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Setting up a cronjob in a docker container running alpine linux node image container</title>
    <link href="https://kasparasanusauskas.com/posts/2021-08/setting-up-a-cronjob-in-a-linux-alpine-docker-container/" />
    <updated>2021-08-18T00:00:00Z</updated>
    <id>https://kasparasanusauskas.com/posts/2021-08/setting-up-a-cronjob-in-a-linux-alpine-docker-container/</id>
    <content type="html">&lt;p&gt;After finishing a NextJS app running in our docker service stack, I had to make it register to our service health dashboard. It accepts a POST request with some data, which is out of the scope of this article. Anyway, all the other services were using  &lt;em&gt;curl&lt;/em&gt; to post and report their health every other minute.&lt;/p&gt;
&lt;p&gt;Here&#39;s the registration script. Nothing fancy, just using curl to make a POST request to an address defined in an environment variable:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Docker prefils this with container ID, which can be used to access &lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# this container in the network&lt;/span&gt;
&lt;span class=&quot;token assign-left variable&quot;&gt;hostname&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;cat&lt;/span&gt; /etc/hostname&lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;curl&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;--header&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Content-Type: application/json&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
     &lt;span class=&quot;token parameter variable&quot;&gt;--request&lt;/span&gt; POST &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
     &lt;span class=&quot;token parameter variable&quot;&gt;--data&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;{&quot;name&quot;: &quot;some-service-name&quot;, &quot;host&quot;: &#39;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;${hostname}&lt;/span&gt;&quot;&lt;/span&gt;&#39;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&#39; &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
     https://some-dashboard-url.com
&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;cronjob has just finished executing register-to-dashboard script&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here&#39;s my &lt;code&gt;Dockerfile&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Dockerfile&lt;/span&gt;
FROM node:alpine AS runner
ENV NODE_ENV production
WORKDIR /
RUN apk &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; --no-cache &lt;span class=&quot;token function&quot;&gt;curl&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# move script to predefined 1min directory, change permissions, schedule it for running&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# YES, script needs to NOT have .sh file extension&lt;/span&gt;
COPY  /register-to-dashboard.sh /etc/periodic/1min/register-to-dashboard
RUN &lt;span class=&quot;token function&quot;&gt;chmod&lt;/span&gt; +x /etc/periodic/1min/register-to-dashboard
RUN &lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;* * * * * run-parts /etc/periodic/1min&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt; /etc/crontabs/root
ENTRYPOINT &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;startup.sh&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And here&#39;s the &lt;code&gt;startup.sh&lt;/code&gt; file:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# startup.sh&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# make sure errors stop execution&lt;/span&gt;
&lt;span class=&quot;token builtin class-name&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-e&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# start the cronjob&lt;/span&gt;
crond &lt;span class=&quot;token parameter variable&quot;&gt;-f&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-l&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;8&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# start nextjs using our own command from package.json...&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; run start:production
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Alpine linux already comes set up to run cronjobs. It&#39;s directory &lt;code&gt;/etc/periodic&lt;/code&gt; already contain directories &lt;strong&gt;15min&lt;/strong&gt;, &lt;strong&gt;daily&lt;/strong&gt;, &lt;strong&gt;hourly&lt;/strong&gt;, &lt;strong&gt;monthly&lt;/strong&gt;, &lt;strong&gt;weekly&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Putting a shell script, with &lt;strong&gt;.sh extension removed&lt;/strong&gt; (weird I know) into one of these directories, and starting the cronjobs using &lt;code&gt;crond -f -l 8&lt;/code&gt; will execute your script at the specified frequency.&lt;/p&gt;
&lt;p&gt;You can run &lt;code&gt;crontab -l&lt;/code&gt; to check the table specifying this behaviour:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;crontab&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-l&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# do daily/weekly/monthly maintenance&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# min	hour	day	    month	weekday	command&lt;/span&gt;
*/15	*	    *	    *	    *	    run-parts /etc/periodic/15min
&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;	    *	    *	    *	    *	    run-parts /etc/periodic/hourly
&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;	    &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;	    *	    *	    *	    run-parts /etc/periodic/daily
&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;	    &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;	    *	    *	    &lt;span class=&quot;token number&quot;&gt;6&lt;/span&gt;	    run-parts /etc/periodic/weekly
&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;	    &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;	    &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;	    *	    *	    run-parts /etc/periodic/monthly
&lt;span class=&quot;token comment&quot;&gt;# after our Dockerfile runs:&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# RUN echo &quot;* * * * * run-parts /etc/periodic/1min&quot; &gt;&gt; /etc/crontabs/root&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# we&#39;ll have a non-default line here:&lt;/span&gt;
*	    *	    *	    *	    *	    run-parts /etc/periodic/1min
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;From the Dockerfile we create create a new directory, &lt;code&gt;1min&lt;/code&gt; by &lt;code&gt;COPY&lt;/code&gt;&#39;ing a script there, and schedule the directories scripts to be ran every minute, expressed in the crontab syntax as seen above. See &lt;a href=&quot;https://crontab.guru/#*_*_*_*_*&quot;&gt;CronTabGuru&lt;/a&gt; about the syntax.&lt;/p&gt;
&lt;h2&gt;Running this stuff&lt;/h2&gt;
&lt;p&gt;When you have the &lt;code&gt;Dockerfile&lt;/code&gt;, &lt;code&gt;register-to-dashboard.sh&lt;/code&gt; files ready, from that same directory we can run this stuff and check out if all is working fine:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# build a docker image as instructed by the Dockerfile and tag it with &quot;app&quot;&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; build &lt;span class=&quot;token builtin class-name&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-t&lt;/span&gt; app
&lt;span class=&quot;token comment&quot;&gt;# run a container using it&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; run app
&lt;span class=&quot;token comment&quot;&gt;# in a terminal, see a list of running containers&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ps&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# if you don&#39;t see it, you can check all containers, including stopped ones + get more info with --no-trunc&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ps&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-a&lt;/span&gt; --no-trunc
&lt;span class=&quot;token comment&quot;&gt;# find the id or name of your running container, and open a shell on it&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;exec&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-it&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;your container &lt;span class=&quot;token function&quot;&gt;id&lt;/span&gt; or name &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;without brackets&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; /bin/sh
&lt;span class=&quot;token comment&quot;&gt;# when shell opens, we can check out if our Dockerfile has configured the cronjob:&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;crontab&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-l&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# ... look for line like this, if you see it - great&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# *	    *	    *	    *	    *	    run-parts /etc/periodic/1min&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# simulate running of scripts from our folder, the output will tell you which scripts will be run exactly&lt;/span&gt;
run-parts &lt;span class=&quot;token parameter variable&quot;&gt;--test&lt;/span&gt; /etc/periodic/1min
&lt;span class=&quot;token comment&quot;&gt;# if you wish, you can actually run the scripts yourself by issuing same command as the cronjob does&lt;/span&gt;
run-parts /etc/periodic/1min
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Your cronjob should already be running every minute now. So, while we were checking all this stuff, some output should have appeared in the first terminal, started the docker container.&lt;/p&gt;
&lt;p&gt;To see logs of a container anytime, even if you closed that terminal:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; logs &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;container &lt;span class=&quot;token function&quot;&gt;id&lt;/span&gt; or name&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you need to stop the container&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; stop &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;container &lt;span class=&quot;token function&quot;&gt;id&lt;/span&gt; or name&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So much fun, these cronjobs.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Make CRA dev server respond to CORS OPTIONS requests, as a gentleman should</title>
    <link href="https://kasparasanusauskas.com/posts/2021-09/make-cra-dev-server-respond-to-options/" />
    <updated>2021-09-15T00:00:00Z</updated>
    <id>https://kasparasanusauskas.com/posts/2021-09/make-cra-dev-server-respond-to-options/</id>
    <content type="html">&lt;p&gt;If you have ever wished to access the dev server of Create React App, your&#39;e in luck. While working on a login page for a custom authorization server, I needed our OAuth client (SPA app) to return &lt;code&gt;204 OK&lt;/code&gt; response with correct CORS headers even in development mode. Essentially, I wanted two CRA apps running on different ports to be able to talk to each other using cross origin POST requests.&lt;/p&gt;
&lt;p&gt;Development server (also based on CRA) with login page running @ https://login.app:3000:&lt;/p&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;POST `https://client.app:3001/login-callback?code=123?state=456`
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Client App must respond:&lt;/p&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;204 OK
#CORS headers (regular ones ommited for briefness): 
{
    # allow login app to make cross origin POST request to me
    Access-Control-Allow-Origin: https://login.app:3000 
    # allow login app to send me cookies on a cross origin request
    Access-Control-Allow-Credentials: true 
    # allow login app to use these methods on a cross origin request
    Access-Control-Allow-Methods: GET, POST
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Fortunately, if you put a file &lt;code&gt;setupProxy.js&lt;/code&gt; in your &lt;code&gt;src&lt;/code&gt; directory (as generated by CRA), you can configure a node express proxy server, modifying responses made by CRA dev server at your own will. Let&#39;s use a popular (CORS)[https://www.npmjs.com/package/cors] middleware npm module to set up cross origin responses automatically.&lt;/p&gt;
&lt;p&gt;So, using Create React App gives you a similar directory structure (as of 2021). Create the file as shown below:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text/14&quot;&gt;my-app/
  README.md
  node_modules/
  package.json
  public/
    index.html
    favicon.ico
  src/
    App.css
    App.js
    App.test.js
    index.css
    index.js
    logo.svg
    setupProxy.js &amp;lt;--- Create this file
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;File contents should be:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// src/setupProxy.js&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// do npm install --save-dev cors to install the cors package&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; cors &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;cors&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; corsOptions &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token literal-property property&quot;&gt;origin&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;https://login.app:3000&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;token literal-property property&quot;&gt;credentials&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;methods&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;GET,POST&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;exports&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    app&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;use&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;cors&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;corsOptions&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	app&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;*&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;cors&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;corsOptions&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And that&#39;s it. You&#39;ll get the response you need. To recap what we have done:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;configured node express server running (or proxying?) our CRA app on local domain https://client.app:3001;&lt;/li&gt;
&lt;li&gt;when it receives OPTIONS request, it will respond with certain headers (specified above);&lt;/li&gt;
&lt;li&gt;when browser running App https://login.app:3000 makes that OPTIONS request, specified response headers inform it it&#39;s OK to continue with the actual POST request;&lt;/li&gt;
&lt;li&gt;everything works out nicely.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Actually, we could avoid using any imported package at all. We could just create a little middleware ourselves:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// src/setupProxy.js&lt;/span&gt;
module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;exports&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    app&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;use&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;req&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; res&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; next&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        res&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Access-Control-Allow-Origin&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;https://login.app:3000&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        res&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Access-Control-Allow-Methods&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;GET,POST&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        res&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Access-Control-Allow-Credentials&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;true&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So that&#39;s it. This is great stuff, and you can definitely do more than set up CORS with it. FIY, I do not know if this is really a proxy server sitting in between CRA dev server and you, or are we actually configuring the instance of dev server. CRA says we indeed are configuring the dev server instance.&lt;/p&gt;
&lt;p&gt;Further reading:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://create-react-app.dev/docs/proxying-api-requests-in-development/&quot;&gt;official CRA website on setupProxy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS&quot;&gt;CORS on MDN&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Have a great day, reader. End.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Access Express and CRA (Create React app) development server routes on same localhost port</title>
    <link href="https://kasparasanusauskas.com/posts/2022-01/express-and-cra-dev-server-on-shared-domain-and-port/" />
    <updated>2022-01-30T00:00:00Z</updated>
    <id>https://kasparasanusauskas.com/posts/2022-01/express-and-cra-dev-server-on-shared-domain-and-port/</id>
    <content type="html">&lt;p&gt;If you have a fullstack app where, say, landing routes are served using Express, and there&#39;s an inner &amp;quot;App&amp;quot; made using react, it&#39;s comfortable to develop both on the same port. Example scenario:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;http://localhost:3000/app&lt;/code&gt; &amp;lt;- Create react app web development server configured with &lt;code&gt;&amp;quot;homepage&amp;quot;: &amp;quot;app&amp;quot;&lt;/code&gt; in &lt;code&gt;package.json&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;http://localhost:4000/api&lt;/code&gt;  &amp;lt;- Express based API routes&lt;/li&gt;
&lt;li&gt;&lt;code&gt;http://localhost:4000/landing&lt;/code&gt; &amp;lt;- Express based static landing pages&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Create react app has a proxy feature allowing us to seamlessly work on &lt;code&gt;http://localhost:3000/app&lt;/code&gt; and access the API without any CORS issues. To configure it, add &amp;quot;proxy&amp;quot; to the CRA project&#39;s &lt;code&gt;package.json&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   ...&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;token property&quot;&gt;&quot;homepage&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/app&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;token property&quot;&gt;&quot;proxy&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;http://localhost:4000&quot;&lt;/span&gt; &amp;lt;-- add this
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With this config, all requests made to &lt;code&gt;https://localhost:3000/api&lt;/code&gt;, &lt;strong&gt;except those containing &lt;code&gt;Accept: text/html&lt;/code&gt; header&lt;/strong&gt;, will be proxied to &lt;code&gt;https://localhost:4000&lt;/code&gt; (our backend server).&lt;/p&gt;
&lt;p&gt;However, by design this proxying will not work for landing pages (note the &lt;code&gt;Accept: text/html&lt;/code&gt; header exception). The alternative way to configure &amp;quot;proxy&amp;quot; for CRA - the &lt;code&gt;setupProxy.js&lt;/code&gt; will not help in this case too.&lt;/p&gt;
&lt;p&gt;Instead, consider proxying the other way:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Don&#39;t proxy CRA -&amp;gt; Express backend&lt;/li&gt;
&lt;li&gt;Proxy Express backend -&amp;gt; CRA&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Express code&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; request &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;request&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

app&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/app/*&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;req&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; res&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    req&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;pipe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;http://localhost:3000&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;req&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;path&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;pipe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;res&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We take the &lt;code&gt;req&lt;/code&gt; object, pipe it to the &lt;code&gt;request&lt;/code&gt; library with makes identical request to another URL. Then the result is piped back to response.&lt;/p&gt;
&lt;p&gt;Our custom Express server has no limitations regarding any headers, so will successfully proxy the request.&lt;/p&gt;
&lt;h2&gt;Full example and use case&lt;/h2&gt;
&lt;p&gt;Let&#39;s create a basic fullstack app. Obviously (but not so obviously when I was first starting 10 years ago..), you&#39;ll
need &lt;a href=&quot;https://nodejs.org/en/download/&quot;&gt;Node&lt;/a&gt;. Begin by creating
an &lt;a href=&quot;https://expressjs.com/en/starter/hello-world.html&quot;&gt;Express Hello world app&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Express server&lt;/h3&gt;
&lt;p&gt;From inside directory &lt;code&gt;fullstack&lt;/code&gt; (any name will of course work), set up a basic Express app + add &lt;code&gt;request&lt;/code&gt; http client package:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; express request
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will generate a &lt;code&gt;package.json&lt;/code&gt; file similar to this:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// fullstack/package.json&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;dependencies&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;request&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;^2.88.2&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;express&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;^4.17.2&quot;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Create a &lt;code&gt;server.js&lt;/code&gt; file with the &amp;quot;Hello world&amp;quot; app contents:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// fullstack/server.js&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; express &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;express&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; app &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;express&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; port &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4000&lt;/span&gt;

app&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;/&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;req&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; res&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    res&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Hello World!&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

app&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;listen&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;port&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Example app listening on port &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;port&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Your directory structure should look like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://kasparasanusauskas.com/posts/2022-01/express-and-cra-dev-server-on-shared-domain-and-port/dir_structure_1.png&quot; alt=&quot;Directory structure&quot;&gt;&lt;/p&gt;
&lt;p&gt;To test your Express app, from &lt;code&gt;fullstack&lt;/code&gt; directory run:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;node&lt;/span&gt; server.js
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Visit &lt;a href=&quot;http://localhost:4000&quot;&gt;http://localhost:4000&lt;/a&gt;, you should see &amp;quot;Hello world&amp;quot; text content returned by the Express server. Close the server by pressing &lt;code&gt;CTRL + C&lt;/code&gt; in the terminal for now.&lt;/p&gt;
&lt;h3&gt;Create react app &amp;quot;client&amp;quot; setup&lt;/h3&gt;
&lt;p&gt;From &amp;quot;fullstack&amp;quot; directory, create a React app called &amp;quot;client&amp;quot;:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;npx create-react-app client
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Your directory &amp;quot;fullstack&amp;quot; should now look like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://kasparasanusauskas.com/posts/2022-01/express-and-cra-dev-server-on-shared-domain-and-port/dir_structure_2.png&quot; alt=&quot;Directory structure&quot;&gt;&lt;/p&gt;
&lt;p&gt;Configure CRA to have homepage of &lt;code&gt;/app&lt;/code&gt;. This is not strictly required if you know what you&#39;re doing, but out of scope of this article.&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// fullstack/client/package.json&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  ...&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;homepage&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/app&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;                                               

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Start the CRA dev server, from the &lt;code&gt;fullstack/client&lt;/code&gt; directory:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; run dev
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Visit &lt;a href=&quot;http://localhost:3000/app&quot;&gt;http://localhost:3000/app&lt;/a&gt; and you should see a basic react template app.&lt;/p&gt;
&lt;h3&gt;Configure Express proxy&lt;/h3&gt;
&lt;p&gt;Now we need to proxy &lt;code&gt;http://localhost:4000/app/*&lt;/code&gt; to the CRA server running on port 3000. In &lt;code&gt;fullstack/server.js&lt;/code&gt;
containing the &amp;quot;Hello world&amp;quot; Express code, add some lines:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;app&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/app/*&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;req&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; res&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;info&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Proxying to localhost:3000&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;req&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;path&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    req&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;pipe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;http://localhost:3000&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;req&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;path&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;pipe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;res&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Why &lt;code&gt;/app/*&lt;/code&gt; with the wildcard, instead of just &lt;code&gt;/app&lt;/code&gt; ? CRA will need to serve some static assets, like js files, stylesheets, favicons... Requests to all of them need to be proxied to the CRA dev server.&lt;/p&gt;
&lt;h3&gt;Testing it all out&lt;/h3&gt;
&lt;p&gt;The steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;make sure your CRA dev server is still running by visiting http://localhost:3000/app (should open demo react app);&lt;/li&gt;
&lt;li&gt;start the Express server again by running &lt;code&gt;node server.js&lt;/code&gt; from the  &lt;code&gt;fullstack&lt;/code&gt; directory, check if &lt;a href=&quot;http://localhost:4000&quot;&gt;http://localhost:4000&lt;/a&gt; is still showing &amp;quot;Hello world&amp;quot; message;&lt;/li&gt;
&lt;/ol&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;access &lt;a href=&quot;http://localhost:4000/app/hello&quot;&gt;http://localhost:4000/app/hello&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In the 3rd step, what should  happen is:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Express will get a request to &lt;code&gt;http://localhost:4000/app/hello&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;get&lt;/code&gt; handler for &lt;code&gt;http://localhost:4000/app/*&lt;/code&gt; will catch it and run our proxying logic&lt;/li&gt;
&lt;li&gt;&lt;code&gt;request&lt;/code&gt; library will make request to &lt;code&gt;http://localhost:3000/app/hello&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;CRA dev server will return index.html contents&lt;/li&gt;
&lt;li&gt;the html contents will be piped to Express response&lt;/li&gt;
&lt;li&gt;you will get the proxied react page, on the port of Express server&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Hope this makes sense.&lt;/p&gt;
&lt;p&gt;Further reading:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://expressjs.com/en/starter/hello-world.html&quot;&gt;Express hello world&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://create-react-app.dev/docs/proxying-api-requests-in-development/&quot;&gt;Create React App proxy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.becomebetterprogrammer.com/what-does-pipe-mean-in-node-js-how-to-use-it-practical-guide/&quot;&gt;A useful blog post about Node .pipe()&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nodejs.org/en/knowledge/advanced/streams/how-to-use-stream-pipe/&quot;&gt;More docs on .pipe() from nodejs.org&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Have a great day, reader. End.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Typescript - discriminated unions for more concise react component prop types</title>
    <link href="https://kasparasanusauskas.com/posts/2022-06/more-concise-react-component-types-with-discriminated-union/" />
    <updated>2022-06-17T00:00:00Z</updated>
    <id>https://kasparasanusauskas.com/posts/2022-06/more-concise-react-component-types-with-discriminated-union/</id>
    <content type="html">&lt;p&gt;I had a problem where a component providing our app graphql capabilities was returning one of those:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;ApolloProvider /&amp;gt;&lt;/code&gt; - real graphql provider to allow testing some legacy code&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;MockedProvider /&amp;gt;&lt;/code&gt; - mocked graphql provider for testing with... mocks&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;language-tsx&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;NetworkProvider&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;client&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; isMocked&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; mocks&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;isMocked&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;MockedProvider&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;mocks&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;mocks&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
    
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ApolloProvider&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;client&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note the different returned providers accept different props. &lt;code&gt;MockedProvider&lt;/code&gt; accepts &lt;code&gt;mocks&lt;/code&gt; prop, while the &lt;code&gt;ApolloProvider&lt;/code&gt; does not, instead it accepts an optional &lt;code&gt;client&lt;/code&gt; prop. That&#39;s an excellen opportunity to mix up the props and pass something that makes no sense. In the old days I used to use a check in the component to warn user incorrect props were passed, something like this:&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;props&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;isMocked &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;props&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;mocks&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Please only pass mocks if isMocked is true. &#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;props&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;isMocked &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;props&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;client&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Please only pass client if isMocked is false. &#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Today, typescript can warn us if we pass incorrect props based on another prop, removing any need to do such checks. We just need to create 2 interfaces, one for each group of props, both having at least 1 common prop to help TS identify which of the 2 interfaces should be applied.&lt;/p&gt;
&lt;pre class=&quot;language-tsx&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;MockedRequest&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;msw&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MockedProviderProps&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    isMocked&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// the common prop&lt;/span&gt;
    mocks&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; MockedRequest&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ApolloProviderProps&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    isMocked&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// the common prop&lt;/span&gt;
    client&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ApolloClient&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;unknown&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Props&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; MockedProviderProps

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;NetworkProvider&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;client&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; isMocked&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; mocks&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Props&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;isMocked&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;MockedProvider&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;mocks&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;mocks&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ApolloProvider&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;client&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now if we try to do something stupid - like pass the &lt;code&gt;mocks&lt;/code&gt; prop while &lt;code&gt;isMocked === false&lt;/code&gt;, typescript will warn us.&lt;/p&gt;
&lt;p&gt;Just to give another, even more contrived example, let&#39;s use an imaginary component &lt;code&gt;Person&lt;/code&gt;. The Persons live in a really horrible world where you can only choose 2 jobs, and if you don&#39;t have a hobby as an adult, everybody thinks you love nothing. It bodes well to our test case though.&lt;/p&gt;
&lt;pre class=&quot;language-tsx&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AdultProps&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// Usually examples use &quot;type: &#39;adult&#39; | &#39;child&#39;&quot; as common property.&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// Let&#39;s use a boolean just for fun, to make it clear it can work.&lt;/span&gt;
  type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;adult&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// &quot;isAdult&quot; is a common property between an adult and a Child&lt;/span&gt;
  employer&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// An adult must have an employeer&lt;/span&gt;
  jobTitle&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;king&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;engineer&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// and work in one of our rather limited fields.&lt;/span&gt;
  hobby&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;computers&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;fishing&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// An adult can have hobby, but it&#39;s not required.&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ChildProps&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;child&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// The common property allows Typescript to discriminate the types.&lt;/span&gt;
  hobby&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;computers&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;mudcakes&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// A child must have a hobby, note it can overlap with adult hobbies.&lt;/span&gt;
  school&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// A child must have school.&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// It must not have employer or jobTitle (from the adult interface).&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CommonProps&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  name&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Both a child and an adult have names.&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Props is a combo of Common props and either adult or child props.&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Props&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; CommonProps &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;AdultProps &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; ChildProps&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;Person&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;props&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Props&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;props&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;adult&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// We can&#39;t just destructure the props when declaring the function.&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// Typescript won&#39;t be sure they&#39;ll be available yet.&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// It will be though after we check the common prop and determine type.  &lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; employer&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; jobTitle&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; hobby &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; props&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;name&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;, a(n) &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;jobTitle&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; employed by &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;employer&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;. Loves 
        &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;hobby &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;nothing&quot;&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; school&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; hobby &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; props&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;name&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;, a kid studying at &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;school&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;. Loves &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;hobby&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;App&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// Now typescript will warn us if we mess up&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Person&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;Arthur&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token attr-name&quot;&gt;isAdult&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token attr-name&quot;&gt;employer&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;Townsfolk&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token attr-name&quot;&gt;jobTitle&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;king&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;br&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Person&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;John&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token attr-name&quot;&gt;isAdult&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token attr-name&quot;&gt;hobby&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;fishing&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Typescript will warn us that only adults can fish. In it&#39;s own words...&lt;/span&gt;
        &lt;span class=&quot;token attr-name&quot;&gt;school&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;Vilnius 1st middle school&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Try it out in this codesandbox. Try adding &lt;code&gt;school=&amp;quot;xyz&amp;quot;&lt;/code&gt; prop to the &lt;code&gt;Person&lt;/code&gt; with &lt;code&gt;isAdult={true}&lt;/code&gt; prop. Note how typescript warns you that this Person can not have such a prop.&lt;/p&gt;
&lt;iframe src=&quot;https://codesandbox.io/embed/typescript-discriminated-union-props-k1786b?fontsize=14&amp;hidenavigation=1&amp;theme=dark&amp;view=editor&quot; style=&quot;width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;&quot; title=&quot;typescript-discriminated-union-props&quot; allow=&quot;accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking&quot; sandbox=&quot;allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts&quot;&gt;&lt;/iframe&gt;
&lt;p&gt;Further reading:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.typescriptlang.org/docs/handbook/2/narrowing.html#discriminated-unions&quot;&gt;Discriminated unions - this is what I was talking about&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.developerway.com/posts/advanced-typescript-for-react-developers-discriminated-unions&quot;&gt;Another example&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Have a great day, reader. End.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Growing microgreens in basic hydroponics</title>
    <link href="https://kasparasanusauskas.com/posts/2025-03/growing-microgreens-in-basic-hydroponics/" />
    <updated>2025-03-03T00:00:00Z</updated>
    <id>https://kasparasanusauskas.com/posts/2025-03/growing-microgreens-in-basic-hydroponics/</id>
    <content type="html">&lt;p&gt;After wishing to grow microgreens for some time now, and looking into building all kinds of automated systems involving pumps and aerators, I&#39;ve decided to start small. Hydroponics may be a bit of a strong word here, actually, but still - technically true.&lt;/p&gt;
&lt;p&gt;The beauty of microgreens - the tiny plants that get eaten at around 7 - 15 days of age - is that you really don&#39;t (should not, according to tutorials) need much.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://kasparasanusauskas.com/posts/2025-03/growing-microgreens-in-basic-hydroponics/images/microgreen_growing_boxes.resized.jpg&quot; alt=&quot;Microgreens&quot; title=&quot;Growing microgreens&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.amazon.de/-/en/dp/B09NQZH19H&quot;&gt;(25 Eur) 6 hydroponics microgreen growing trays (generic, everybody sells them)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.amazon.de/dp/B0DHKY6TX3&quot;&gt;(6 Eur) Silicone net used for drying vegetables&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;[(10 Eur) worth of regular seeds bought locally]&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You fill up that plastic tray with water, and the seeds sprout roots that go down through plastic webbing, reaching the water. No other medium should be necessary (but we&#39;ll see). According to the tutorials, one could improve output by using hydroponics suitable fertilizer in the water, but it&#39;s not necessary since the greens are cut so early.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://kasparasanusauskas.com/posts/2025-03/growing-microgreens-in-basic-hydroponics/images/microgreen_seeds_1.resized.jpg&quot; alt=&quot;Microgreens&quot; title=&quot;Larger seeds&quot;&gt;&lt;/p&gt;
&lt;p&gt;The trays are quite OK for the price. Made from 3 pieces - a bowl for the water, a plastic grid where you put the seeds and a lid for keeping moisture in. The plastic grid has holes which are too large for small seeds, this is where the silicone net comes in, keeping them from dropping through, but still allowing roots to pass through.&lt;/p&gt;
&lt;p&gt;A few of the boxes were slightly deformed, with the center of the plastic netting bulging upwards, making seeds roll to the sides.&lt;/p&gt;
&lt;p&gt;The silicone is already pre-cut and works perfectly with these trays at &lt;code&gt;[20cm, 28cm]&lt;/code&gt; dimensions. For larger seeds, it&#39;s not needed. Definitely required for smaller ones.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://kasparasanusauskas.com/posts/2025-03/growing-microgreens-in-basic-hydroponics/images/microgreen_seeds_3.resized.jpg&quot; alt=&quot;Small seeds in microgreen hydro box&quot; title=&quot;Smaller seeds&quot;&gt;&lt;/p&gt;
&lt;p&gt;Regular, basic seeds should be fine, just google the (big variety) of plants that can be consumed early on and don&#39;t have poisonous leaves etc. I actually accidentally bought some seeds for radishes nicely covered in a paper strip for easy planting. An extra pain here, but I&#39;ve actually found the extra paper to hold moisture very well.&lt;/p&gt;
&lt;p&gt;I put 400ml of water into each bowl, put the seeds on top, sprayed them with a sprayer and closed the lids. I know it&#39;s recommended to compress the seeds and keep them dark for a few days (to emulate conditions under a layer of soil), but I&#39;ve read they should do fine without the extra steps.&lt;/p&gt;
&lt;p&gt;When they sprout, I have a few lamps ready to shine on them for 16 hours a day (it&#39;s still early sprint here in Lithuania, it&#39;s dark).&lt;/p&gt;
&lt;p&gt;I&#39;ll update this post when they sprout.&lt;/p&gt;
&lt;h2&gt;Day 3 update&lt;/h2&gt;
&lt;p&gt;Beans, both varieties of radishes and brocolli have sprouted. For now the leaves are super small and yellow. I&#39;ll start using artificial lights tomorrow on them. I changed the water. The boxes had a weird, spicy smell in them (all of them), I&#39;m slightly worried I should have changed the water in the box sooner - hopefully not a sign of some bacterial infestation yet. I&#39;m spraying all the seeds with water every day.&lt;/p&gt;
&lt;p&gt;Worryingly, both radishes, which had their seeds wrapped in paper, developed what I believe to be small mold spots. I suspect this is due to the paper always being super wet. I manually removed the mold spots. I marked them with red rectangles in the photo.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://kasparasanusauskas.com/posts/2025-03/growing-microgreens-in-basic-hydroponics/images/sprout_and_mold_microgreens.resized.jpg&quot; alt=&quot;Sprouted microgreens with signs of mold&quot; title=&quot;Sprouted microgreens with signs of mold&quot;&gt;&lt;/p&gt;
&lt;p&gt;I&#39;ll see how this develops further. For now I&#39;ve started tracking the sprouting (S) and problems (yellow background) in this table.
&lt;img src=&quot;https://kasparasanusauskas.com/posts/2025-03/growing-microgreens-in-basic-hydroponics/images/microgreen_table.resized.jpg&quot; alt=&quot;Microgreens sprout tracking table&quot; title=&quot;Tracking sprouting and issues in a sheet&quot;&gt;&lt;/p&gt;
&lt;h2&gt;Day 4 update&lt;/h2&gt;
&lt;p&gt;All have sprouted except for the smallest seeds of &lt;em&gt;Valerianella Locusta&lt;/em&gt; (which I&#39;ve thought would be the first). The plants remain moist, and seem to be doing well. No more mold growth (which was a surprise, maybe it wasn&#39;t mold after all?). I have decided to not change the water today as this would not be sustainable, and LLMs don&#39;t seem to think I need to. There was a spicy smell in most of the containers, yet I suspect this is actually the sprouts.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://kasparasanusauskas.com/posts/2025-03/growing-microgreens-in-basic-hydroponics/images/first_leaves.resized.jpg&quot; alt=&quot;A microgreen growing in hydroponics container&quot; title=&quot;A fast grower&quot;&gt;
&lt;img src=&quot;https://kasparasanusauskas.com/posts/2025-03/growing-microgreens-in-basic-hydroponics/images/other_leaves.resized.jpg&quot; alt=&quot;A microgreen growing in hydroponics container&quot; title=&quot;Everything sprouted&quot;&gt;&lt;/p&gt;
&lt;h2&gt;Day 10 update&lt;/h2&gt;
&lt;p&gt;Ok, so I&#39;ve made a couple of mistakes that ruined the experiment, however I consider it a success in the sense that I got very quickly that growing microgreens is probably not for me. At least not in this configuration.&lt;/p&gt;
&lt;p&gt;We had some excellent 15 Celsius, sunny weather last weeks despite it being early March. And so I made many mistakes (I think):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I kept the lids on during first 6 days or so. The super humid environment caused black funghus on beans. A lot of them died a horrible, gellatinous death.&lt;/li&gt;
&lt;li&gt;After that smelly fiasco, I&#39;ve decided I won&#39;t eat the beans, which reduced motivation.&lt;/li&gt;
&lt;li&gt;I removed the lids, but did not keep up with spraying schedule - it&#39;s just too much work to keep the seeds moist. The smaller seeds quickly dried out in the sun. It was a pity to see the small roots that had developed die too.&lt;/li&gt;
&lt;li&gt;The radishes, which were the best performers but enveloped in paper tissue were not able to free themselves from the paper. Some did, some remain under the paper. The paper is what kept them moist I think though.&lt;/li&gt;
&lt;li&gt;The broccoli are the only ones that I think I&#39;ll be able to eat tomorrow. They grew despite the bad conditions, and without any help from the paper.&lt;/li&gt;
&lt;li&gt;The few surviving bean plants are now too large to consider them &amp;quot;microgreens&amp;quot;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All in all, I&#39;ll try to eat the broccoli, and a few of the radishes. The rest, I&#39;ll try to grow to full size plants by replanting into pots this weekend as to not kill them. I think the experiment has proved that at this time of my life I do not have the patience required to keep such delicate small plants alive. However, I&#39;m still considering building an automated system out of the containers on a small scale - perhaps just 2 boxes.&lt;/p&gt;
&lt;h3&gt;The good performers:&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://kasparasanusauskas.com/posts/2025-03/growing-microgreens-in-basic-hydroponics/images/end/broccoli.resized.jpg&quot; alt=&quot;Broccoli&quot; title=&quot;Broccoli&quot;&gt;
↑ Broccoli
&lt;img src=&quot;https://kasparasanusauskas.com/posts/2025-03/growing-microgreens-in-basic-hydroponics/images/end/radishes.resized.jpg&quot; alt=&quot;Radishes&quot; title=&quot;Radishes&quot;&gt;
↑ Radishes (note the paper)&lt;/p&gt;
&lt;h3&gt;The smelly, poor moldy slimy performers:&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://kasparasanusauskas.com/posts/2025-03/growing-microgreens-in-basic-hydroponics/images/end/beans.resized.jpg&quot; alt=&quot;Beans&quot; title=&quot;Beans&quot;&gt;
↑ Beans (went slimy, moldy, stinky, will not eat but will grow them to full plants)&lt;/p&gt;
&lt;h3&gt;The dead:&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://kasparasanusauskas.com/posts/2025-03/growing-microgreens-in-basic-hydroponics/images/end/leafy_greens.resized.jpg&quot; alt=&quot;Leafy greens&quot; title=&quot;Leafy greens&quot;&gt;
↑ Leafy greens
&lt;img src=&quot;https://kasparasanusauskas.com/posts/2025-03/growing-microgreens-in-basic-hydroponics/images/end/beetroot.resized.jpg&quot; alt=&quot;Beetroot&quot; title=&quot;Beetroot&quot;&gt;
↑ Beetroot&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Transfer thousands of photos from android to mac via ftp</title>
    <link href="https://kasparasanusauskas.com/posts/2025-05/transfer-thousands-of-photos-from-android-to-mac-via-ftp/" />
    <updated>2025-05-29T00:00:00Z</updated>
    <id>https://kasparasanusauskas.com/posts/2025-05/transfer-thousands-of-photos-from-android-to-mac-via-ftp/</id>
    <content type="html">&lt;p&gt;Today I was preparing my old Samsung S23 Ultra for sale after moving to iOS for now. The app &amp;quot;Android transfer&amp;quot;, which &lt;em&gt;kind of worked some of the time&lt;/em&gt; to transfer files from android to mac was no longer available though. The mac also refused to see the phone as a USB storage device despite multiple attempts to connect it (of course setting the phone to &amp;quot;USB / files transfer mode). The &amp;quot;media transfer&amp;quot; mode also did not work (while trying to import via &amp;quot;Photos&amp;quot; app on my mac).&lt;/p&gt;
&lt;p&gt;I then downloaded a &lt;a href=&quot;https://play.google.com/store/apps/details?id=com.medhaapps.wififtpserver&amp;amp;hl=en-US&quot;&gt;one&lt;/a&gt; of the many FTP server apps available for android. I was able to easily connect to the FTP server just by using &amp;quot;Finder&amp;quot; app:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://kasparasanusauskas.com/posts/2025-05/transfer-thousands-of-photos-from-android-to-mac-via-ftp/images/connect.resized.jpg&quot; alt=&quot;Connect to FTP server via finder&quot;&gt;&lt;/p&gt;
&lt;p&gt;However, the finder was super slow / crashed / lost connection to server often (due to 10&#39;s of thousands of images in the &amp;quot;DCIM/Camera&amp;quot; directory) and I was not able to transfer them successfully (apart from doing it manually in batches of like a 1000).&lt;/p&gt;
&lt;p&gt;I tried to download an FTP client and, to my horror, on the appstore, all were paid (and I don&#39;t really need FTP for anything else at this point)&lt;/p&gt;
&lt;p&gt;Fortunately, free options still exist, and ChatGPT wrote a script for me which uses &lt;code&gt;lftp&lt;/code&gt;. To install via homebrew:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;brew &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; lftp
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Save this script &lt;code&gt;download-camera.sh&lt;/code&gt;. I had problems with it being unable to create files / directories due to permissions / Apple &amp;quot;System integrity protection&amp;quot; stuff. So the script saves the results to your users home directory, which seemed to work (It will look like &lt;code&gt;/Users/yourusername/result&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/bin/zsh
set -euo pipefail

# ── connection details (of course match them with your android FTP config)
HOST=&amp;quot;192.168.1.202&amp;quot;
PORT=&amp;quot;2221&amp;quot;
USER=&amp;quot;android&amp;quot;
PASS=&amp;quot;yourpassword&amp;quot;
REMOTE_DIR=&amp;quot;DCIM/Camera&amp;quot;

# ── target folder (flag or env var) ───────────────────────────────
LOCAL_DIR=&amp;quot;${LOCAL_DIR:-$HOME/result}&amp;quot;
while getopts &amp;quot;d:&amp;quot; opt; do
[[ $opt == d ]] &amp;amp;&amp;amp; LOCAL_DIR=&amp;quot;$OPTARG&amp;quot;
done
LOG_FILE=&amp;quot;$LOCAL_DIR/ftp-mirror.log&amp;quot;

mkdir -p &amp;quot;$LOCAL_DIR&amp;quot; 2&amp;gt;/dev/null || {
echo &amp;quot;❌  Cannot write to $LOCAL_DIR — choose another location&amp;quot; &amp;gt;&amp;amp;2
exit 1
}

# ── lftp batch ────────────────────────────────────────────────────
lftp -u &amp;quot;${USER}&amp;quot;,&amp;quot;${PASS}&amp;quot; -p &amp;quot;${PORT}&amp;quot; &amp;quot;${HOST}&amp;quot; &amp;lt;&amp;lt;EOF
# --- connection settings
set net:timeout 30
set net:max-retries 3
set cmd:fail-exit yes
set ftp:list-options -a
set xfer:clobber on

# --- show remote file list
cls -1 &amp;quot;$REMOTE_DIR&amp;quot;

# --- sequential mirror download
mirror --verbose &#92;
--parallel=1 &#92;
--use-pget-n=1 &#92;
--log=&amp;quot;$LOG_FILE&amp;quot; &#92;
&amp;quot;$REMOTE_DIR&amp;quot; &amp;quot;$LOCAL_DIR&amp;quot;

bye
EOF

echo &amp;quot;✅  Finished. Files are in $LOCAL_DIR&amp;quot;
echo &amp;quot;   Transfer log: $LOG_FILE&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Make the script executable:
&lt;code&gt;chmod +x download-camera.sh&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Run the script:
&lt;code&gt;./download-camera.sh&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;And it works - it sequentially downloaded the 25k of photos / videos over the few hours. I had to keep my phones screen on though, the FTP app seemed to go to sleep otherwise (I went to developer options of the phone, and selected &amp;quot;Keep screen on&amp;quot; option&amp;quot;).&lt;/p&gt;
&lt;p&gt;The models are truly becoming an UI for whatether you need - writing programs on the go. Cool but scary.&lt;/p&gt;
&lt;p&gt;Kasparas&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>The Outrun</title>
    <link href="https://kasparasanusauskas.com/books/2026-01/the-outrun/" />
    <updated>2026-01-05T00:00:00Z</updated>
    <id>https://kasparasanusauskas.com/books/2026-01/the-outrun/</id>
    <content type="html">&lt;p&gt;Amy Liptrot · ISBN 978-1-78689-422-9 · Score 3&lt;/p&gt;
&lt;p&gt;I went through the book quickly and really enjoyed it even if it started getting a bit bored at the end. It has a calming quality as it describes the life of an addict getting better through her experiences in nature - the archipelago of northern Scotland isles. Mostly about islands of Orkney and Papay, which I&#39;ve now looked up in google maps.&lt;/p&gt;
&lt;p&gt;It all starts with a painful description (not a memory - she was born on that day) of her father being taken away to a mental institution to the mainland. We then jump to a later time, where she escapes to London, and, as young people do (and older people would love to, but find reasons not to) lives a life with a feeling that things are waiting for her.&lt;/p&gt;
&lt;p&gt;Then comes her increasing struggles with alcohol. The addiction theme appears slowly but steadily, as the book first describes seemingly nice things but you know it&#39;s not gonna end well. The loneliness of the big city (even if the relation comes from perhaps being a &amp;quot;citizen of internet&amp;quot;), the feelings of being left behind as her group of friends moves on. The paradoxes of coming to big city to get to know different people, but in reality finding a safe group of identical ones.&lt;/p&gt;
&lt;p&gt;Later on there&#39;s a crisis and successfull rehab programme. Amy stumbles back to the islands, and, in her own words &amp;quot;just get&#39;s stuck&amp;quot;, but this turns out to be a good thing.&lt;/p&gt;
&lt;p&gt;Back in the islands, she founds newfound joy in the simple way of life and the superficially simple landscapes, flora and fauna in there. And this in there, the main theme of the book appears, and what I initially liked about the book as I quickly flipped through it in the library.&lt;/p&gt;
&lt;p&gt;The writer describes the island mainly through experiences and how they make her feel, instead of directly describing the landscapes, which, at first sight, would probably look great but would get dull and empty and treeless (due to the extreme wind and saltiness, as the author says) quite quickly if just viewed superficially.&lt;/p&gt;
&lt;p&gt;Instead it seems Amy is able to notice an infinite stream of tiny happenings that accumulate into one big and great experience, and it is all described quite vividly. This I kind of relate to, as I find, for example, I&#39;m able to ride the same bike route every day and never find it dull, while some strongly crave new routes. They will see more this is for sure, but the point here is that the difference in how it makes one feel on a different day is enough to make it feel new each day. Together with the smallest changes to the landscape, winds, skies itself which are always there if one looks for it.&lt;/p&gt;
&lt;p&gt;Not that she takes the same route though. She enrolls into a job of looking for rare island birds during the short northern nights and scouts an island for the species, square by square. She then looks at the stars, and thinks stuff like &amp;quot;asteroid Apophis which had been initially calculated to have a chance of ±2 percent to hit Earth is out there&amp;quot;. She describes rituals involving large fires or simply fires burning trash on the islands, and the people themselves. She started walking, and took to finding meaning in it, started swimming in the sea which, at the temperatures and winds and waves I think to be a big effort.&lt;/p&gt;
&lt;p&gt;All the small things gradually help author fight the feeling that nothing is worthwile doing if you don&#39;t get drunk during it. I can somewhat relate to it too while to a lesser extent I guess, as I remember quitting smoking and losing the one nice quick thing you can do after any kind of activity. She had it worse though.. but managed to recover.&lt;/p&gt;
&lt;p&gt;I especially liked and resonated with one paragraph about how she found snorkeling in the islands uncovered a whole new world. I will paste this here, and end this with that.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Face down in shallow water, coated in neoprene and breathing through a tube, I feel as if I&#39;ve opened a door that has always been in my house but I had never noticed. Life can be bigger and richer than I knew.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Surely there must be such doors in all of our lives.&lt;/p&gt;
&lt;img src=&quot;https://kasparasanusauskas.com/books/2026-01/the-outrun/cover.resized.jpg&quot; alt=&quot;The Outrun cover&quot; class=&quot;bookCover&quot;&gt;
&lt;p&gt;P.S. The publishers do a great job by adding first sections of her other books in the end of the book. While I found myself being tired of this one a bit at the end, after reading the start of next 2 ones, I&#39;ll probably get them too.&lt;/p&gt;
&lt;h2&gt;Anything learnt from the book?&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;I know / knew a lot of alcohol-addicted people. Like my Italian stepfather (RIP), who even had a disability assign by state due to it. This book perhaps could have revealed a bit of how they felt, however it&#39;s probably a very personal experience.&lt;/li&gt;
&lt;li&gt;One can seemingly live (and live quite nicely) by just floating through opportunities that appear - kind of without perpetual office job etc. Still one must be brave to pursue this, as the author went that road not due to choice, but necessity.&lt;/li&gt;
&lt;li&gt;There are even more things to be noticed around that I previously thought (the author tought about birds, underwater, wind, stars, ability to think how other people live, which one kind of knows, but knows more of after this read).&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry>
    <title>The Gentle Art of Tramping</title>
    <link href="https://kasparasanusauskas.com/books/2026-01/the-gentle-art-of-tramping/" />
    <updated>2026-01-14T00:00:00Z</updated>
    <id>https://kasparasanusauskas.com/books/2026-01/the-gentle-art-of-tramping/</id>
    <content type="html">&lt;p&gt;Stephen Graham · Year 1926&lt;/p&gt;
&lt;p&gt;I always enjoy travel books. It took me a long time to actually recognize I might be enjoying them more than being on a long trip myself.&lt;/p&gt;
&lt;p&gt;This one originally interested me due to being written 100 years ago at the time of this post (1926). And indeed it is reflected in the language, described landscapes and tools. The language nothing but simple, and was very hard to read initially, but satisfying non the less.&lt;/p&gt;
&lt;p&gt;At the core, it&#39;s a little book about hiking which tries to be a universal guide. It begins with a collection of connected essays on clothing, sleeping outdoors, the best hiking routes known by the author. Interestingly and fittingly to the policital situation of 2026, Crimea, Caucasus mountains in Russia are some of authors favorite locations, but the author notes that the travel there became very dangerous. &amp;quot;“Since the War, and the Revolution, the Caucasus has, unfortunately, become much more dangerous for travelers of all kinds. The tribes are warlike, and have been badly treated by Bolsheviks and Europeans in general”.&lt;/p&gt;
&lt;p&gt;A beautiful section named &amp;quot;The art of idleness&amp;quot; was one I really connected to. It speaks of how &amp;quot;life&#39;s quality is in momentws, not in distance run&amp;quot; - meaning the author considers quality over quantity. Beautiful proze gives examples of the benefits of slow travel, even suggesting travel is only worth it if it&#39;s not a rush, but &amp;quot;the way&amp;quot; (I interpreted it as the way you live).&lt;/p&gt;
&lt;p&gt;If one rushes, then tramping might be only a stamp one puts on his life, &amp;quot;done that&amp;quot; to, as the author says, &amp;quot;a stunt, something done to make a dull person ornamental&amp;quot;. A strong statement, but I appreciate the whole text of being opinionated like that, cause what&#39;s a book if there&#39;s no opinion in it (An encyclopedia or an atlas would fit the bill but you know what I mean).&lt;/p&gt;
&lt;p&gt;&amp;quot;It is a fallacy to think that during the siesta you do not tramp; you are tramping, wandering in unknown parts...&amp;quot; - author suggests.&lt;/p&gt;
&lt;p&gt;...Reading in progres...&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>The Roads to Sata - A 2000-Mile Walk Through Japan</title>
    <link href="https://kasparasanusauskas.com/books/2026-01/the-roads-to-sata-a-2000-mile-walk-through-japan/" />
    <updated>2026-01-21T00:00:00Z</updated>
    <id>https://kasparasanusauskas.com/books/2026-01/the-roads-to-sata-a-2000-mile-walk-through-japan/</id>
    <content type="html">&lt;p&gt;Alan Booth · ISBN 978-0-141-99283-9 · Year 2021 (Originally 1985)&lt;/p&gt;
&lt;p&gt;I bought this book from an overpriced stationary and writing tool shop, which everyone still visits cause it is so nice, and has a few books for sales matching the mood. I guess it&#39;s not that overpriced then as the values lies in the beauty of it but anyway.&lt;/p&gt;
&lt;p&gt;It seemed like an excellent book to prepare for my journey to Japan. Sure it was set more than 40 years back, but I wondered if I could still relate when I visit myself. The author traveled through the west side, which I did not visit, but the general atmosphere, I&#39;d say, did not change.&lt;/p&gt;
&lt;p&gt;About the book - well, it&#39;s gonna be hard to summarize this one, as summarizing a 4 month long journey can be.&lt;/p&gt;
&lt;p&gt;It took me a long time to read. Took it to Japan, then stopped reading it right in the middle. I got back to it though, which rarely happens with books I leave behind. I guess it was not a book to be read each day. Maybe, just like any lengthy trip (even so incredible as this one), it can get a bit tedious if you do it all in one go.&lt;/p&gt;
&lt;p&gt;Alan Booth, who lived in Japan until his early death at 46 in 1993, spent ±120 days in the on-foot-only (no lifts at all) hike of more than 3000kms from the northern tip of Japans most norther island - Hokkaido, to the most souther one of the larger islands - Cape Sata. He took a route by the western coast, omitting Tokyo.&lt;/p&gt;
&lt;p&gt;He describes the people, the incredible landscapes and rising industrial cities, the cool and weird customs, the good and the bad... The great generosity and the extreme racism (not always in a bad way though), the extreme indifference towards the rest of the world and the tremendous beauty of the rituals.&lt;/p&gt;
&lt;p&gt;He does all that in extreme detail - a true talent for that. The key structure for each day is the same - start the day, somethig happens. Walk for some 30-40 miles, detail the roads, something happens.  Drink a few beers, find a Ryokan (a traditional Japanese guest house), describe it and the evening. In between that, though, a lot happens, as one can expect on such a journey.&lt;/p&gt;
&lt;p&gt;He also really knows his stuff - legends, poems, history, mythology is presented in relation the location described.&lt;/p&gt;
&lt;p&gt;The book is truly like a journey. It starts fresh, but ends a bit tired, not that it&#39;s getting worse technically, but you can feel the author getting tired of it himself I think... I remember the middle, but not the ending at all.&lt;/p&gt;
&lt;p&gt;Instead of trying to recount everything, I&#39;ll leave a few quotes I marked. It does not represent the book at all, but instead are some caricatures I liked, some small insights the author does not miss in his daily life, and includes together with the otherwise incredible descriptions of this beautiful country.&lt;/p&gt;
&lt;p&gt;Describing an evening walk made an impression on me:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;In the starlight the narrow road wound up and up into the hills, crossing dark streams on small stone bridges, winding past the last isolated buildings of the town and into the darkness of the forest. I stoped outside one lonely house and listened to a woman singing in her bathroom. The night air was warm, the stars bright silver, and by the time I reached the minshuku at Nakama the white moon had risen high over the woods. 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A family description instead:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;... where I found a drive-in and had a lunch of noodles while the proprietor and his family hooked fish out of a filthy dirty tank and tossed them with unsettling, secretive smiles into a tin bucket to die. 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A discussion about why a sign &amp;quot;Open&amp;quot; is lit but hotel actually closed:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;- But you&#39;ve got a sign all lit up down on the highway
- Yes. We always keep it lit. 
- What for, for goodness sake?
- To make people feel welcome.
- But you&#39;re closed!
- That&#39;s right.
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;Later on that evening, to demonstrate the cosmopolitan nature of her family, she brought me a knife and fork with which to eat a bowl of noodles.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Quite a few useful notes in the book, like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;...shops selling rice and its byproducts are ideal places to make inquiries since they are often the oldest established shops in the district...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A few references to weird Manga:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;There was also a pile of comic books to amuse the breakfasting customers; the one I galnced at (Eros) featured a naked woman tied up in an automobile body shop being assaulted with breathtaking variety of spanners, jacks and big ends.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The author closes the day, (almost every day), with some beautiful proze:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;That night I chuckled myself to sleep, thinking of the sparks coming out of the old woman`s nostrils and hair, and as we slept, each plugged into our dreams, the first snow fell on Mount Fuji.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;P.S. I also found a new way to read travel books with this one - not only marking stuff I like with my pencil, but also having my phone nearby in bed, looking up all the places mentioned. This gave entirely new process to the read, cause you still first imagine the place, then discover the real one in the real photos.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Building a 3d model of a kids room cabinet</title>
    <link href="https://kasparasanusauskas.com/til/2026-01/22_tinkercad-shelving/" />
    <updated>2026-01-22T00:00:00Z</updated>
    <id>https://kasparasanusauskas.com/til/2026-01/22_tinkercad-shelving/</id>
    <content type="html">&lt;p&gt;Our kids bet is a 2-story unit. It is positioned in a  leg-towards-the-door orientation which, kids said, was not very cozy as they see a black hole at their legs at night. Also it&#39;s ver un-feng-shui.&lt;/p&gt;
&lt;p&gt;They also read a lot of books, especially the bigger one. To remedy the situation I&#39;ve decided to build custom shelving which would act as both a barrier and some nice storage.&lt;/p&gt;
&lt;p&gt;The thing is, if I just bought it, it would be oriented towards 1 single side (the non-wall side :) ). But, this would mean a lot of wasted space in my case. This is because the bed itself would obstruct a lot of the shelves.&lt;/p&gt;
&lt;p&gt;I decided to use that obstructed space from the other side instead, so some books can be put in there too.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://kasparasanusauskas.com/til/2026-01/22_tinkercad-shelving/1.resized.jpg&quot; alt=&quot;Side 1&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://kasparasanusauskas.com/til/2026-01/22_tinkercad-shelving/2.resized.jpg&quot; alt=&quot;Side 2&quot;&gt;&lt;/p&gt;
&lt;p&gt;Initially I thought of making it twice deeper and more complex. However, this would be so thick (60cm) that it would almost block the entrance to the room.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://kasparasanusauskas.com/til/2026-01/22_tinkercad-shelving/3.resized.jpg&quot; alt=&quot;Original large unit&quot;&gt;&lt;/p&gt;
&lt;p&gt;All in all, it was really worth modelling the shelving. I was able to arrange available wooden panels I can buy from local shop without any need to cut them up before ordering them. Also it allowed me to visualize that the original shelve (the thick one) will probably not work.&lt;/p&gt;
&lt;p&gt;We&#39;ll see how it goes when I&#39;ll be building it now.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>3d printing a tool for drilling shelves</title>
    <link href="https://kasparasanusauskas.com/til/2026-01/24_3d_printing_a_tool/" />
    <updated>2026-01-24T00:00:00Z</updated>
    <id>https://kasparasanusauskas.com/til/2026-01/24_3d_printing_a_tool/</id>
    <content type="html">&lt;p&gt;Today I had to solve a problem I knew I&#39;ll encounter when &lt;a href=&quot;https://kasparasanusauskas.com/til/2026-01/22_tinkercad-shelving/&quot;&gt;building the shelves for my kids&lt;/a&gt;. I&#39;ll need to drill a lot of holes (12 * 7 shelves), and they had to be perfectly aligned and straight because I was planning to use elegant wooden dowels and glue instead of screws or triangles with screws etc. to hold the shelves.&lt;/p&gt;
&lt;p&gt;I knew this was impossible (for me) to do by hand. Turns out all the 3d tinkering at tinkercad was really useful.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://kasparasanusauskas.com/til/2026-01/24_3d_printing_a_tool/1.resized.jpg&quot; alt=&quot;3d model in tinkercad&quot;&gt;&lt;/p&gt;
&lt;p&gt;The print went for 5 hours. The tool was too big to fit into the printer so I had to glue it from 2 parts with some epoxy.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://kasparasanusauskas.com/til/2026-01/24_3d_printing_a_tool/2.resized.jpg&quot; alt=&quot;Printed tool in action&quot;&gt;&lt;/p&gt;
&lt;p&gt;With the extra adapters the same tool can nicely used for the other orientation I need to drill holes in - this being the most precarious one.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://kasparasanusauskas.com/til/2026-01/24_3d_printing_a_tool/3.resized.jpg&quot; alt=&quot;Difficult drill&quot;&gt;&lt;/p&gt;
&lt;p&gt;The result was really nice. We&#39;ll see if the holes align though when I&#39;ll be assembling the shelving unit!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://kasparasanusauskas.com/til/2026-01/24_3d_printing_a_tool/4.resized.jpg&quot; alt=&quot;Example of holes&quot;&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Learning Ink shading, building tool to build book cabinet</title>
    <link href="https://kasparasanusauskas.com/til/2026-01/25_ink-and-diy-shelving/" />
    <updated>2026-01-25T00:00:00Z</updated>
    <id>https://kasparasanusauskas.com/til/2026-01/25_ink-and-diy-shelving/</id>
    <content type="html">&lt;p&gt;Today I was going through the &lt;a href=&quot;https://kasparasanusauskas.com/books/2026-01/rendering-in-pen-and-ink/&quot;&gt;Ink drawing book&lt;/a&gt; I&#39;ve just started. I can tell this is gonna be useful cause I&#39;m encountering various textures and shading ways I&#39;ll want in my own drawings already (mainly organic leaf shapes used for shading).&lt;/p&gt;
&lt;p&gt;At the horror of any ink connoseur, I was working on my ipad, cause that&#39;s where I&#39;m doing most of my illustrations. BUT, I also ordered a certain ink/pen combo. A very cool book, also nicely written / illustrated.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://kasparasanusauskas.com/til/2026-01/25_ink-and-diy-shelving/ink-shading-1.resized.jpg&quot; alt=&quot;ink-shading-1&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://kasparasanusauskas.com/til/2026-01/25_ink-and-diy-shelving/ink-shading-2.resized.jpg&quot; alt=&quot;ink-shading-2&quot;&gt;&lt;/p&gt;
&lt;p&gt;To see what I&#39;m doing with this one could look into my other blog &lt;a href=&quot;https://sutemudirbtuves.lt&quot;&gt;sutemų dirbtuvės&lt;/a&gt; .&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;I also learned a lot about real pens, nibs, ink. It&#39;s a world one could easily get lost in, as is the case with any truly useful instrument that is also a thing of beauty (if manufactured well). Specifically I was intrigued by &amp;quot;fude&amp;quot; type of pens which have bent tip allowing to get wide line not only by pressure, but also by rotating the nib.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;I also learned that my 3d printed modular tool designed for drilling straigh, well spaced holes in both walls and shelves... works wonders. However, a method I chose to secure the shelving was not ideal. The plan was to use only wooden dowels and glue. The drilled holes were nice and precise enough to match in all 7 * 3 dowel locations, but in the end, I was unable to perfectly weight, clamp or otherwise compress the glue and in some locations a gap of perhaps 0.5mm exists. I think the glue will hold cause it&#39;s not like that everywhere, but it does not make me proud at all.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://kasparasanusauskas.com/til/2026-01/25_ink-and-diy-shelving/cabinet-1.resized.jpg&quot; alt=&quot;cabinet-1&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://kasparasanusauskas.com/til/2026-01/25_ink-and-diy-shelving/cabinet-2.resized.jpg&quot; alt=&quot;cabinet-2&quot;&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Rendering in Pen and Ink - The Classic Book On Pen and Ink</title>
    <link href="https://kasparasanusauskas.com/books/2026-01/rendering-in-pen-and-ink/" />
    <updated>2026-01-26T00:00:00Z</updated>
    <id>https://kasparasanusauskas.com/books/2026-01/rendering-in-pen-and-ink/</id>
    <content type="html">&lt;p&gt;Arthur L. Guptill · ISBN 978-0-307-83188-0 · Year 2014&lt;/p&gt;
&lt;p&gt;People that know me will know I like to doodle and perhaps even paint. Since I&#39;ve now decided to make my hobby a bit more public and directional by developing a book character, I&#39;ve decided to also focus my drawing onto a direction I like. That direction is the &amp;quot;old kids book&amp;quot; style of ink countours and shading, supplemented by some watercolor coloring (or sometimes just the ink).&lt;/p&gt;
&lt;p&gt;So, to start, I&#39;ve started working with a (from what I&#39;ve read) classical guide on ink drawing. This is that book.&lt;/p&gt;
&lt;p&gt;And I really like it. It&#39;s more of a workbook.&lt;/p&gt;
&lt;p&gt;...work in progress...&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Wood works and drawing hobby plan</title>
    <link href="https://kasparasanusauskas.com/til/2026-01/28/" />
    <updated>2026-01-28T00:00:00Z</updated>
    <id>https://kasparasanusauskas.com/til/2026-01/28/</id>
    <content type="html">&lt;p&gt;Today I learned that description for this TIL section is probably to be removed, as the the title is long enough to describe the ... short post itself.&lt;/p&gt;
&lt;p&gt;I also learned that hammering a rectangular piece of wood into a space that is slightly smaller than it&#39;s dimensions is not gonna work. While it seems common knowledge, believe me or not, I spent 10 minutes frantically trying to disprove that. Shocked as I was by my own actions, problem was I had already applied glue and had to get the piece 80cm x 25cm piece of wood in the (too) tight spot until it set. When this did not work at all and the shelves started breaking, I frantically started kniving the non-fitting piece in rough strokes - turns out all the tools are at my cabin.&lt;/p&gt;
&lt;p&gt;In the end it worked out OK, but from now on I will measure my rectangles more carefully and adjust with proper tools before applying them.&lt;/p&gt;
&lt;p&gt;Yup.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;I also created an machine learning based learning / marketing plan for my recent ink drawing studies / hobby. I&#39;ve decided that it&#39;s a good time to start work on my potential &amp;quot;Ratsnack&amp;quot; book - hoping to maybe grow at least the smallest of audiences and validating or disproving the idea (and my capabilities and making an interesting drawing + story).&lt;/p&gt;
&lt;p&gt;While ridiculous, it&#39;s still plan, and better than nothing. It&#39;ll allow me to draw scenes while adapting ink shading exercises, and posting the results to some locations too.&lt;/p&gt;
&lt;p&gt;That&#39;s mostly it.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Learn pen shading with leafy shapes, varnishing large surfaces, some print CSS styles.</title>
    <link href="https://kasparasanusauskas.com/til/2026-01/29/" />
    <updated>2026-01-29T00:00:00Z</updated>
    <id>https://kasparasanusauskas.com/til/2026-01/29/</id>
    <content type="html">&lt;p&gt;Today I sanded and varnished the new DIY shelve for my kids. Turns out water based varnish will raise the grain of the wood, and it&#39;ll look very bad, one must sand it. I learned that sanding is one thing, but you actually seem to &amp;quot;burn in / rub in&amp;quot; the previously varnished surface a bit even until it&#39;s smooth - as it even works with a completely worn sanding pad.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://kasparasanusauskas.com/til/2026-01/29/shelf1.resized.jpg&quot; alt=&quot;Almost finished base book shelf&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://kasparasanusauskas.com/til/2026-01/29/shelf2.resized.jpg&quot; alt=&quot;Almost finished base book shelf&quot;&gt;&lt;/p&gt;
&lt;p&gt;Currently it&#39;s not the best time to make photos, but I&#39;m proud of finishing it so... Even if it&#39;s a bit ...basic. It&#39;ll look much better with all the details I&#39;ll add later on. I also learned that varnishing is not as simple as it looks. You have to be patient. Can&#39;t move the brush all around, as it leaves lines, and they better matched the grain...&lt;/p&gt;
&lt;p&gt;One must also provide a good amount of varnish, otherwise it forms blobs, runs etc.&lt;/p&gt;
&lt;p&gt;Best way is as usual - slow is fast, take your time. In the end I actually learned to kind of enjoy the tedious and boring process. What helped was thinking about doing the work the best I could.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;I then spent a bit of time (as I promised to do this everyday) on my ink drawing book exercises. I encountered the first exercise where I&#39;m having true trouble (can&#39;t draw it as it is shown) - and it&#39;s the random looking leafy pattern which is not random at all...&lt;/p&gt;
&lt;p&gt;What I found helps is imaginging it&#39;s leafy shapes and then forgetting you&#39;re drawing the pattern - draw leafy shapes instead. It&#39;s not exactly the same, but maybe it not necessarily must be. Anyway, I&#39;ll return to this tomorrow. This will be very useful in my future illustrations.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://kasparasanusauskas.com/til/2026-01/29/shading.resized.jpg&quot; alt=&quot;shading with leafy shapes&quot;&gt;&lt;/p&gt;
&lt;p&gt;I also worked on creating a new subreddit &lt;code&gt;r/NaiveIllustration&lt;/code&gt; which will serve as a blog on my learning, kind of, with the idea of the drawings perhaps atracting an audience. Due to all this I was unable to actually finish any drawing and post it. Well, the shelf should take less time tomorrow, maybe then.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;At work I also remembered a few printing CSS styles nuances. For example:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;it&#39;s indeed possible to detect &amp;quot;closing of print dialog&amp;quot;:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;afterprint&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; handleAfterPrint&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// nice&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I also, once again, confirmed that having 2 versions of a microservice you wrote with both being used in the codebase is not the way to go. I&#39;ll start remedying this tomorrow immediatelly.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;I also learned this TIL section is truly a nice way to reflect on the day, even if I&#39;ll probably have to rename it to &amp;quot;Diary&amp;quot; :)&lt;/p&gt;
&lt;p&gt;It being online forces one to try and write in an at least comprehensible manner, even if no one will probably read it. It kind of sums up the day and invigorates me to keep the work tomorrow.&lt;/p&gt;
</content>
  </entry>
</feed>