<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>casraf.dev</title><description/><link>https://casraf.dev/</link><item><title>Flutter Tutorial - Creating a Wheel Spinner Widget</title><link>https://casraf.dev/2019/03/flutter-tutorial-creating-a-wheel-spinner-widget/</link><guid isPermaLink="true">https://casraf.dev/2019/03/flutter-tutorial-creating-a-wheel-spinner-widget/</guid><pubDate>Fri, 22 Mar 2019 10:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;https://flutter.dev&quot;&gt;Flutter&lt;/a&gt; is an amazing framework for app development, and gaining popularity every second.
Being component and composition based, and with the fact that it includes many built-in widgets to
work with, it’s really easy to create beautiful widgets, either simple or complex.&lt;/p&gt;
&lt;p&gt;Today we’ll be learning how to create a real-world case widget for updating a numeric value - a
pitch-knob-like control. Like the ones you can usually find in a studio. It will also support
letting the finger go to keep rolling in the same direction, and using the velocity it was left
with.&lt;/p&gt;

&lt;h3&gt;In this tutorial, we will:&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Create a stateful widget&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;GestureDetector&lt;/code&gt; to detect scroll gestures for us, and update the callbacks appropriately.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;This tutorial assumes you have some experience with Flutter and understand basic Widget
composition and basic state management.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;Step 1: Create the Widget&lt;/h2&gt;
&lt;p&gt;We’ll start basic. We have one base stateful widget, which will hold everything. In this example,
I’ve already added some parameters we will need to accept:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; WheelSpinner&lt;/span&gt;&lt;span&gt; extends&lt;/span&gt;&lt;span&gt; StatefulWidget&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  final&lt;/span&gt;&lt;span&gt; double&lt;/span&gt;&lt;span&gt; max;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  final&lt;/span&gt;&lt;span&gt; double&lt;/span&gt;&lt;span&gt; min;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  final&lt;/span&gt;&lt;span&gt; double&lt;/span&gt;&lt;span&gt; value;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  final&lt;/span&gt;&lt;span&gt; Function&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;double&lt;/span&gt;&lt;span&gt; value)&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt; onSlideUpdate;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  final&lt;/span&gt;&lt;span&gt; Function&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;double&lt;/span&gt;&lt;span&gt; value)&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt; onSlideDone;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  WheelSpinner&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    required&lt;/span&gt;&lt;span&gt; this&lt;/span&gt;&lt;span&gt;.value,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    this&lt;/span&gt;&lt;span&gt;.max &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; double&lt;/span&gt;&lt;span&gt;.infinity,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    this&lt;/span&gt;&lt;span&gt;.min &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; double&lt;/span&gt;&lt;span&gt;.negativeInfinity,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    this&lt;/span&gt;&lt;span&gt;.onSlideDone,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    this&lt;/span&gt;&lt;span&gt;.onSlideUpdate,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  @override&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  _WheelSpinnerState&lt;/span&gt;&lt;span&gt; createState&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; _WheelSpinnerState&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; _WheelSpinnerState&lt;/span&gt;&lt;span&gt; extends&lt;/span&gt;&lt;span&gt; State&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;WheelSpinner&lt;/span&gt;&lt;span&gt;&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  late&lt;/span&gt;&lt;span&gt; double&lt;/span&gt;&lt;span&gt; value;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  @override&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  void&lt;/span&gt;&lt;span&gt; initState&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    value &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; widget.value;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    super&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;initState&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  @override&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  Widget&lt;/span&gt;&lt;span&gt; build&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;BuildContext&lt;/span&gt;&lt;span&gt; context) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; Container&lt;/span&gt;&lt;span&gt;(child&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Text&lt;/span&gt;&lt;span&gt;(value.&lt;/span&gt;&lt;span&gt;toString&lt;/span&gt;&lt;span&gt;()));&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can see, we are accepting a &lt;code&gt;min&lt;/code&gt;, &lt;code&gt;max&lt;/code&gt;, and current &lt;code&gt;value&lt;/code&gt; to use with the widget. We are
also accepting &lt;code&gt;callback&lt;/code&gt;s for when updating (while sliding) and when done (when finger is let go
and the “fling” duration is over), which we will call once we update the actual value.&lt;/p&gt;
&lt;h3&gt;Let’s give it some shape&lt;/h3&gt;
&lt;p&gt;Right now our widget is completely empty, except for that little &lt;code&gt;Text&lt;/code&gt; widget to see our current
value, so let’s do some styling. For this example, We will be creating a rounded, tall box, which
will contain some separation lines, and maybe some shade or gradient.&lt;/p&gt;
&lt;p&gt;Let’s update our build method:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;@override&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Widget&lt;/span&gt;&lt;span&gt; build&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;BuildContext&lt;/span&gt;&lt;span&gt; context) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  double&lt;/span&gt;&lt;span&gt; shadowOffset &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 0.2&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; Container&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    width&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; 60&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    height&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; 100&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    decoration&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; BoxDecoration&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      gradient&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; LinearGradient&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        begin&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Alignment&lt;/span&gt;&lt;span&gt;.topCenter,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        end&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Alignment&lt;/span&gt;&lt;span&gt;.bottomCenter,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        stops&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;0.0&lt;/span&gt;&lt;span&gt;, shadowOffset, &lt;/span&gt;&lt;span&gt;1.0&lt;/span&gt;&lt;span&gt; -&lt;/span&gt;&lt;span&gt; shadowOffset, &lt;/span&gt;&lt;span&gt;1.0&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        colors&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          Colors&lt;/span&gt;&lt;span&gt;.grey[&lt;/span&gt;&lt;span&gt;350&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          Colors&lt;/span&gt;&lt;span&gt;.grey[&lt;/span&gt;&lt;span&gt;50&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          Colors&lt;/span&gt;&lt;span&gt;.grey[&lt;/span&gt;&lt;span&gt;50&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          Colors&lt;/span&gt;&lt;span&gt;.grey[&lt;/span&gt;&lt;span&gt;350&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        ],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      ),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      border&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Border&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;all&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        width&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        style&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; BorderStyle&lt;/span&gt;&lt;span&gt;.solid,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        color&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Colors&lt;/span&gt;&lt;span&gt;.grey[&lt;/span&gt;&lt;span&gt;600&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      ),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      borderRadius&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; BorderRadius&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;circular&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;3.5&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    ),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  );&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We should see something like this:&lt;/p&gt;
&lt;div&gt;
&lt;p&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Still no divider lines, though. Let’s say we want to divvy up our box to 10 segments, so that each
time one of them goes outside the bounding box, we increase or decrease the value by 1. In this
example we create a &lt;code&gt;Stack&lt;/code&gt;, with 11 lines (10 for each division + 1 extra for the scroll effect)
going from top to bottom (note the &lt;code&gt;lineTopPos&lt;/code&gt; function that gets the correct &lt;code&gt;y&lt;/code&gt; value):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;@override&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Widget&lt;/span&gt;&lt;span&gt; build&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;BuildContext&lt;/span&gt;&lt;span&gt; context) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // ...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  decoration&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; // ...,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  child&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Container&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    child&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Stack&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      children&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; List&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;Widget&lt;/span&gt;&lt;span&gt;&amp;gt;.&lt;/span&gt;&lt;span&gt;generate&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        11&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        (i) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          var&lt;/span&gt;&lt;span&gt; top &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; lineTopPos&lt;/span&gt;&lt;span&gt;(value, i);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          return&lt;/span&gt;&lt;span&gt; Positioned&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;fromRect&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            rect&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Rect&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;fromLTWH&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0.0&lt;/span&gt;&lt;span&gt;, top, &lt;/span&gt;&lt;span&gt;60&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            child&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Divider&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;              color&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Colors&lt;/span&gt;&lt;span&gt;.grey[&lt;/span&gt;&lt;span&gt;600&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            ),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          );&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      ).&lt;/span&gt;&lt;span&gt;toList&lt;/span&gt;&lt;span&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    ),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  ),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // ...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;double&lt;/span&gt;&lt;span&gt; lineTopPos&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;double&lt;/span&gt;&lt;span&gt; value, &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; i) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  double&lt;/span&gt;&lt;span&gt; valueFraction &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; (value.&lt;/span&gt;&lt;span&gt;ceil&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; value) &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; 10.0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  double&lt;/span&gt;&lt;span&gt; indexedTop &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 10.0&lt;/span&gt;&lt;span&gt; *&lt;/span&gt;&lt;span&gt; i;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  double&lt;/span&gt;&lt;span&gt; top &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; indexedTop &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; valueFraction;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; top;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note the line that sets &lt;code&gt;valueFraction&lt;/code&gt;. We take our &lt;code&gt;value.ceil()&lt;/code&gt; and reduce the current value.
This always gives us a number between &lt;code&gt;0.0&lt;/code&gt; and &lt;code&gt;1.0&lt;/code&gt; that tells us how much of the &lt;em&gt;next&lt;/em&gt; segment
to show. In reality, whenever we update &lt;code&gt;value&lt;/code&gt;, we will always consider the small fraction we are
scrolling into, which means we don’t jump by 1 every time, which will cause the lines to represent
the value correctly, and also smoothly move as we input our scroll.&lt;/p&gt;
&lt;p&gt;Now, we have something like this:&lt;/p&gt;
&lt;div&gt;
&lt;p&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;And now that it’s all nice and pretty, let’s start handling the logic.&lt;/p&gt;
&lt;h2&gt;Step 2 - Detecting gestures and updating the value&lt;/h2&gt;
&lt;p&gt;We can now wrap our widget with a &lt;code&gt;GestureDetector&lt;/code&gt;. This is a built-in widget that lets you capture
and use scroll, tap and multi-tap gestures on the child widget, and its decendants (that last part
depends on the &lt;code&gt;behavior&lt;/code&gt; parameter).&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;//...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;GestureDetector&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      onVerticalDragStart&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; onDragStart,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      onVerticalDragUpdate&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; onDragUpdate,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      onVerticalDragEnd&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; onDragDone,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      child&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; /* our widget */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;//...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And of course, we need to actually define &lt;code&gt;onDragStart&lt;/code&gt;, &lt;code&gt;onDragUpdate&lt;/code&gt; and &lt;code&gt;onDragDone&lt;/code&gt;.&lt;/p&gt;
&lt;h4&gt;1. onDragStart&lt;/h4&gt;
&lt;p&gt;We’ll start by capturing on what &lt;code&gt;value&lt;/code&gt; and position the finger first started dragging. For that,
we will save them in our state:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;Offset&lt;/span&gt;&lt;span&gt; dragStartOffset;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;double&lt;/span&gt;&lt;span&gt; dragStartValue;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// ...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; onDragStart&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;DragStartDetails&lt;/span&gt;&lt;span&gt; details) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  setState&lt;/span&gt;&lt;span&gt;(() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    dragStartOffset &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; details.globalPosition;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    dragStartValue &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; value;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;&lt;/div&gt;
&lt;h4&gt;2. onDragUpdate&lt;/h4&gt;
&lt;p&gt;On every update, aka when the finger slides up and down, we want to take the distance between the
original start point, and use that to calculate our new value. If the finger scrolled up an amount
equivalent to 10 separator lines, we increase/decrease by 10 accordingly. Of course, these numbers
will be much smaller since we are updating a double, on a subpixel basis.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; onDragUpdate&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;DragUpdateDetails&lt;/span&gt;&lt;span&gt; details) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  var&lt;/span&gt;&lt;span&gt; newValue &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; clamp&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      dragStartValue &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; (details.globalPosition &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; dragStartOffset).dy &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; 20.0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      widget.min,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      widget.max);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  setState&lt;/span&gt;&lt;span&gt;(() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    value &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; newValue;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (widget.onSlideUpdate &lt;/span&gt;&lt;span&gt;!=&lt;/span&gt;&lt;span&gt; null&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    widget.&lt;/span&gt;&lt;span&gt;onSlideUpdate&lt;/span&gt;&lt;span&gt;(value);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We set the new value to use the &lt;code&gt;dragStartValue&lt;/code&gt; and decrease by the distance of the scroll so far,
divided by 20 to scale appropriately with the separator lines. Then we update using the callback, if
that’s relevant.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note:&lt;/em&gt; the &lt;code&gt;clamp&lt;/code&gt; method is a just a convenience method to keep a number between 2 boundaries.
Here is a basic implementation:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;double&lt;/span&gt;&lt;span&gt; clamp&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;T&lt;/span&gt;&lt;span&gt; extends&lt;/span&gt;&lt;span&gt; num&lt;/span&gt;&lt;span&gt;&amp;gt;(&lt;/span&gt;&lt;span&gt;T&lt;/span&gt;&lt;span&gt; number, &lt;/span&gt;&lt;span&gt;T&lt;/span&gt;&lt;span&gt; low, &lt;/span&gt;&lt;span&gt;T&lt;/span&gt;&lt;span&gt; high) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    max&lt;/span&gt;&lt;span&gt;(low &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; 1.0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;min&lt;/span&gt;&lt;span&gt;(number &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; 1.0&lt;/span&gt;&lt;span&gt;, high &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; 1.0&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;3. Testing the current widget&lt;/h4&gt;
&lt;p&gt;We can already test out our widget - we still can’t “fling” the finger, but we can drag our finger
up and down to see the value updating.&lt;/p&gt;
&lt;p&gt;Let’s add a Text widget to our parent &lt;code&gt;build&lt;/code&gt; method, to see the value from the state that’s calling
it. Here is an example of calling our widget:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; _MyHomePageState&lt;/span&gt;&lt;span&gt; extends&lt;/span&gt;&lt;span&gt; State&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;MyHomePage&lt;/span&gt;&lt;span&gt;&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  //...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  @override&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  Widget&lt;/span&gt;&lt;span&gt; build&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;BuildContext&lt;/span&gt;&lt;span&gt; context) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; Scaffold&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      appBar&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; AppBar&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        title&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Text&lt;/span&gt;&lt;span&gt;(widget.title),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      ),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      body&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Center&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        child&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Column&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          mainAxisSize&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; MainAxisSize&lt;/span&gt;&lt;span&gt;.min,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          children&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &amp;lt;&lt;/span&gt;&lt;span&gt;Widget&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            WheelSpinner&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;              value&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; _counter.&lt;/span&gt;&lt;span&gt;toDouble&lt;/span&gt;&lt;span&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;              min&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; 0.0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;              max&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; 100.0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;              onSlideUpdate&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; (val) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; setState&lt;/span&gt;&lt;span&gt;(() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    _counter &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; val;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                  }),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            ),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            Padding&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;              padding&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; const&lt;/span&gt;&lt;span&gt; EdgeInsets&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;only&lt;/span&gt;&lt;span&gt;(top&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; 16.0&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;              child&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Text&lt;/span&gt;&lt;span&gt;(_counter.&lt;/span&gt;&lt;span&gt;toStringAsFixed&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;), textScaleFactor&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; 2.0&lt;/span&gt;&lt;span&gt;,),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            ),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          ],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        ),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      ),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    );&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // ...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now it should work and look like this:&lt;/p&gt;
&lt;div&gt;
&lt;p&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;h4&gt;4. onDragDone&lt;/h4&gt;
&lt;p&gt;Our last piece is also the most fun. Here we will start handling our “fling” physics. We need 2
things here:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Use the velocity of the letting-go gesture to figure out how much to add/reduce from the value&lt;/li&gt;
&lt;li&gt;Dampen this value slowly to create an eased roll effect&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Luckily, animations are super useful in a case like this, for applying curves to a path between 2
number values - we don’t need to calculate the damping ourselves.&lt;/p&gt;
&lt;p&gt;So first thing’s first, we need to define and create a new &lt;code&gt;AnimationController&lt;/code&gt;, and
&lt;code&gt;Animation&amp;lt;double&amp;gt;&lt;/code&gt;, and let’s also set their initial value in our &lt;code&gt;initState&lt;/code&gt; method:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;AnimationController&lt;/span&gt;&lt;span&gt; flingController;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Animation&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;double&lt;/span&gt;&lt;span&gt;&amp;gt; flingAnimation;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; Function&lt;/span&gt;&lt;span&gt;() currentFlingListener;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;@override&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;initState&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  flingAnimation &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; AlwaysStoppedAnimation&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0.0&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  flingController &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; AnimationController&lt;/span&gt;&lt;span&gt;(vsync&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; this&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // ...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Also, since we use animations now, we will want to mixin &lt;code&gt;SingleTickerProviderStateMixin&lt;/code&gt;, which
will manage a ticker for us, for the animation to use:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; _WheelSpinnerState&lt;/span&gt;&lt;span&gt; extends&lt;/span&gt;&lt;span&gt; State&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;WheelSpinner&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    with&lt;/span&gt;&lt;span&gt; SingleTickerProviderStateMixin&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      // ...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, we can start with our new method:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; onDragDone&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;DragEndDetails&lt;/span&gt;&lt;span&gt; details) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  setState&lt;/span&gt;&lt;span&gt;(() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    dragStartOffset &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; null&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  double&lt;/span&gt;&lt;span&gt; velocity &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; details.primaryVelocity;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (velocity.&lt;/span&gt;&lt;span&gt;abs&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (widget.onSlideDone &lt;/span&gt;&lt;span&gt;!=&lt;/span&gt;&lt;span&gt; null&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      widget.&lt;/span&gt;&lt;span&gt;onSlideDone&lt;/span&gt;&lt;span&gt;(value);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the above lines we simply reset the drag start offset, as it’s no longer relevant, now that the
finger was let go. Then we get the velocity of the drag, and if it’s 0, we return early and submit
our callback, &lt;code&gt;onSlideDone&lt;/code&gt; with the latest value.&lt;/p&gt;
&lt;p&gt;Now, we can proceed with handling the “fling”.&lt;/p&gt;
&lt;p&gt;We’ll start by saving the value that was set when we first let go of the finger.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; onDragDone&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;DragEndDetails&lt;/span&gt;&lt;span&gt; details) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // ...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  double&lt;/span&gt;&lt;span&gt; originalValue &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; value;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // ...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, we want to add a listener to our animation value. We will generate a listener based on the
value that was let go at, because we will need it to calculate the updated value:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;flingListener&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;double&lt;/span&gt;&lt;span&gt; originalValue) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; () {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    double&lt;/span&gt;&lt;span&gt; newValue &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        clamp&lt;/span&gt;&lt;span&gt;(originalValue &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; flingAnimation.value, widget.min, widget.max);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (newValue &lt;/span&gt;&lt;span&gt;!=&lt;/span&gt;&lt;span&gt; value) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      setState&lt;/span&gt;&lt;span&gt;(() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        value &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; newValue;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      if&lt;/span&gt;&lt;span&gt; (flingAnimation.value &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; flingController.upperBound) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        if&lt;/span&gt;&lt;span&gt; (widget.onSlideDone &lt;/span&gt;&lt;span&gt;!=&lt;/span&gt;&lt;span&gt; null&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          widget.&lt;/span&gt;&lt;span&gt;onSlideDone&lt;/span&gt;&lt;span&gt;(value);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      } &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        if&lt;/span&gt;&lt;span&gt; (widget.onSlideUpdate &lt;/span&gt;&lt;span&gt;!=&lt;/span&gt;&lt;span&gt; null&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          widget.&lt;/span&gt;&lt;span&gt;onSlideUpdate&lt;/span&gt;&lt;span&gt;(value);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  };&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the line:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;double&lt;/span&gt;&lt;span&gt; newValue &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        clamp&lt;/span&gt;&lt;span&gt;(originalValue &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; flingAnimation.value, widget.min, widget.max);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can see we use the animation value and simply decrease it from the original value. Depending on
the fling direction, this will either continue up or down along with the animation value.&lt;/p&gt;
&lt;p&gt;Now we can set our listener to that function once we call it. Saving it to an instance variable will
allow us to remove the listener on dispose, as we will have a reference to the same listener
function.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;  // ...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  currentFlingListener &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; flingListener&lt;/span&gt;&lt;span&gt;(originalValue);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // ...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then we start a &lt;code&gt;Tween&lt;/code&gt; animation: we start at &lt;code&gt;0.0&lt;/code&gt;, and end at &lt;code&gt;velocity&lt;/code&gt;. Whether the velocity is
positive or negative, the math will work to reach the final number we want.&lt;/p&gt;
&lt;p&gt;We set the &lt;code&gt;curve&lt;/code&gt; as we want (in this case, &lt;code&gt;Curves.decelerate&lt;/code&gt;), and attach the &lt;code&gt;parent&lt;/code&gt; animation
controller; attach the listener, and finally when we’re done, we can use &lt;code&gt;forward()&lt;/code&gt; to start
animating.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;  flingController.duration &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; Duration&lt;/span&gt;&lt;span&gt;(milliseconds&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; velocity.&lt;/span&gt;&lt;span&gt;abs&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;toInt&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  flingAnimation &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      Tween&lt;/span&gt;&lt;span&gt;(begin&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; 0.0&lt;/span&gt;&lt;span&gt;, end&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; velocity &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; 100&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;animate&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;CurvedAnimation&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    curve&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Curves&lt;/span&gt;&lt;span&gt;.decelerate,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    parent&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; flingController,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  ))&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        ..&lt;/span&gt;&lt;span&gt;addListener&lt;/span&gt;&lt;span&gt;(currentFlingListener);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  flingController&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    ..&lt;/span&gt;&lt;span&gt;reset&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    ..&lt;/span&gt;&lt;span&gt;forward&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We added &lt;code&gt;reset()&lt;/code&gt; just before &lt;code&gt;forward()&lt;/code&gt;, to make sure no previous animations are lingering for
some odd reason. In fact, let’s add a &lt;code&gt;stop()&lt;/code&gt; calls to &lt;code&gt;onDragStart&lt;/code&gt; and &lt;code&gt;onDragUpdate&lt;/code&gt;, as well,
and also reset the animation itself:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; onDragStart&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;DragStartDetails&lt;/span&gt;&lt;span&gt; details) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  flingController.&lt;/span&gt;&lt;span&gt;stop&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  flingAnimation &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; AlwaysStoppedAnimation&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0.0&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // ...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; onDragUpdate&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;DragUpdateDetails&lt;/span&gt;&lt;span&gt; details) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  flingController.&lt;/span&gt;&lt;span&gt;stop&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  flingAnimation &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; AlwaysStoppedAnimation&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0.0&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // ...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And now that it’s all out of the way, our widget should be fully working:&lt;/p&gt;
&lt;div&gt;
&lt;p&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;h3&gt;Done!&lt;/h3&gt;
&lt;p&gt;This should be pretty much it! You can of course style it and expand on it, but I’ve already made
this a package, so any improvements you have, or if you just want to use it, head over to &lt;a href=&quot;https://pub.dartlang.org/packages/wheel_spinner&quot;&gt;the
package on Dart Pub&lt;/a&gt;, or feel free to contribute at &lt;a href=&quot;https://github.com/chenasraf/wheel_spinner&quot;&gt;the source on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The source of the example app used for this tutorial &lt;a href=&quot;https://github.com/chenasraf/wheel_spinner_tutorial&quot;&gt;right here&lt;/a&gt;, if you want to take a
look and compare.&lt;/p&gt;
&lt;p&gt;Feel free to ask questions, provide feedback or correct my mistakes in the comments - I’m sure there
are some.&lt;/p&gt;</content:encoded></item><item><title>Simple Scaffold: Generating multiple files for faster coding</title><link>https://casraf.dev/2019/03/simple-scaffold/</link><guid isPermaLink="true">https://casraf.dev/2019/03/simple-scaffold/</guid><pubDate>Wed, 06 Mar 2019 13:43:40 GMT</pubDate><content:encoded>&lt;p&gt;As most people who have been working a lot in front-end development, I will acknowledge that no
matter what framework, library set or tools you work with, you end up replicating a lot of files,
especially with modern component-based frameworks, such as React, Angular and Vue.js.&lt;/p&gt;
&lt;p&gt;They usually grow to be very different, but they almost always start with the same some sort of
base-skeleton, which gets &lt;em&gt;built-upon&lt;/em&gt; later. A lot of pipeline code written, and &lt;strong&gt;time wasted&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;What have we got?&lt;/h2&gt;
&lt;p&gt;There are some existing solutions to these problems, but they usually are not very flexible, or are
tied to one library or implementation. Let’s take a look at a few examples.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;IDE snippets&lt;/em&gt;, such as VS Code’s easy snippet extensions/configurations are very helpful.
Basically, you can either use configuration JSON files to add snippets to your code quickly, or
use extensions that simply fill these up for you.&lt;/p&gt;
&lt;p&gt;There are nice benefits to this, as you can start typing the ‘prefix’ of the snippet and hit
&lt;kbd&gt;Tab&lt;/kbd&gt; quickly to add small snippets of code. You can also predefine stopping points,
which you can navigate using &lt;kbd&gt;Tab&lt;/kbd&gt; after inserting the snippet, and insert arguments,
method names, etc.&lt;/p&gt;
&lt;p&gt;These are nice, but only for small snippets. When you have to create an entire component, made by
multiple files, you’re stuck with the option to create multiple files yourself, name them
appropriately, and paste different snippets on each file.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;Some NPM libraries&lt;/em&gt; provide ways to scaffold files based on templates, but from what I’ve seen&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;they are tied to specific task runners, libraries or frameworks&lt;/li&gt;
&lt;li&gt;they are difficult to set up and customize&lt;/li&gt;
&lt;li&gt;combining your entire stack will be difficult&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Enter: Simple Scaffold&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://casraf.dev/simple-scaffold&quot;&gt;Read the Simple Scaffold documentation&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I was frustrated with this, and all I wanted was to have different sets of files I could generate,
simply by copying them at predefined directory structures, and fill them with some variable data.&lt;/p&gt;
&lt;p&gt;For instance: I create a lot of React components at work. We have recently moved to a single-file
component structure for starting components; but we have different types of components that we want
to generate based on context, like different file contents for page containers versus general-use
components.&lt;/p&gt;
&lt;p&gt;Being fed up with my options, I made this small NPM package, that does just this, in a very easy and
quick way to setup. Simply put your files wherever you want, and either use the CLI tool or the
package as an import - and you’re good to go.&lt;/p&gt;
&lt;h2&gt;Example: Jekyll Posts&lt;/h2&gt;
&lt;p&gt;Creating Jekyll posts is very simple! However I still had a little bit missing - I have to manually
add the date every time? Oh, man. What about the file identifier name, and title? My
&lt;code&gt;excerpt_separator&lt;/code&gt;?&lt;/p&gt;
&lt;p&gt;So I just made the simplest script in Ruby (to fit in with the Jekyll theme) to run the Simple
Scaffold CLI with some custom parameters.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;#!/usr/bin/env ruby&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;require&lt;/span&gt;&lt;span&gt; &quot;json&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;SCAFFOLD_DIR&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; &quot;scaffold_templates&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;name &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; ARGV&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;dateFmt:&lt;/span&gt;&lt;span&gt; &quot;yyyy-MM-dd&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;fullDateFmt:&lt;/span&gt;&lt;span&gt; &quot;yyyy-MM-dd HH:mm:ss XX&quot;&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;puts&lt;/span&gt;&lt;span&gt; `&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  npx simple-scaffold@latest &quot;&lt;/span&gt;&lt;span&gt;#{name.&lt;/span&gt;&lt;span&gt;join&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot; &quot;&lt;/span&gt;&lt;span&gt;)}&lt;/span&gt;&lt;span&gt;&quot; \&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    --templates &lt;/span&gt;&lt;span&gt;#{&lt;/span&gt;&lt;span&gt;SCAFFOLD_DIR&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;/**/* \&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    --output _drafts \&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    -w true \&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    -s false \&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    -d &apos;&lt;/span&gt;&lt;span&gt;#{&lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;dump&lt;/span&gt;&lt;span&gt;(data)}&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;puts&lt;/span&gt;&lt;span&gt; &apos;Done&apos;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let’s run by this real quick:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Lines 1-7&lt;/em&gt; - setting up locals such as template directory base, and some variables to pass to the
templates.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Lines 9-16&lt;/em&gt; - We pass the parameters via a shell call (back-ticks in ruby), and immediately run
using npx.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Locals are passed to &lt;a href=&quot;https://handlebarsjs.com&quot;&gt;Handlebars&lt;/a&gt;, and can be used both in file/directory names and file
contents.&lt;/p&gt;
&lt;p&gt;Now all I had to do is create a place for my templates, and add a template inside:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;- scaffold_templates/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  -- {{now dateFmt}}-{{kebabCase name}}.md&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And fill it up with some basic post:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;layout&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;post&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;{{ startCase name }}&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;date&lt;/span&gt;&lt;span&gt;: { { &lt;/span&gt;&lt;span&gt;now fullDateFmt&lt;/span&gt;&lt;span&gt; } }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;excerpt_separator&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&amp;lt;!-- more --&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;categories&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Post content goes here&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And voila! Running the script, along with a name:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;./scaffold.rb&lt;/span&gt;&lt;span&gt; &quot;Billy Jean is not my lover&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Generates the following file structure:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;- _posts/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  -- 2019-03-06-billy-jean-is-not-my-lover.markdown&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With the following content:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;layout&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;post&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;Billy Jean is not my lover&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;date&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;2019-03-06 16:43:38 +0200&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;excerpt_separator&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&amp;lt;!-- more --&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;categories&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Post content goes here&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;You can do more&lt;/h2&gt;
&lt;p&gt;Take the same approach and think what you could create generators for with 0 effort. React or Vue
components? You can bundle each in a folder with all the imports, exports, base props, etc all lined
up with your component name.&lt;/p&gt;
&lt;p&gt;Add a script to your &lt;code&gt;package.json&lt;/code&gt;, a couple of files, and you’re good to go:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;package.json&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;gen:component&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;npx simple-scaffold@latest -t scaffold_templates/react-component -o src/components -s true -w true &apos;{&lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;className&lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;myClassName&lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;author&lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;Chen Asraf&lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;}&apos;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;scaffold_templates/react-component/{{pascalCase name}}.tsx&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;/**&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt; * Author: {{ author }}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt; * Date: {{ now &quot;yyyy-MM-dd&quot; }}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt; */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; React &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;react&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { ComponentProps } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;utils/types&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; interface&lt;/span&gt;&lt;span&gt; {{pascalCase name}}Props extends ComponentProps {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  className&lt;/span&gt;&lt;span&gt;?:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; default&lt;/span&gt;&lt;span&gt; {{camelCase name}}: React.&lt;/span&gt;&lt;span&gt;FC&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;{{pascalCase name}}Props&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;props&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;{{className}}&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;{{camelCase name}} Component&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  )&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;scaffold_templates/react-component/index.ts&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; *&lt;/span&gt;&lt;span&gt; from&lt;/span&gt;&lt;span&gt; &apos;./{{pascalCase name}}&apos;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Running &lt;code&gt;npm run gen:component MyComponent&lt;/code&gt; will quickly generate your new components:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;src/components/MyComponent/MyComponent.tsx&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;/**&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt; * Author: Chen Asraf&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt; * Date: 2022-08-10&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt; */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; React &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;react&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { ComponentProps } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;utils/types&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; interface&lt;/span&gt;&lt;span&gt; MyComponentProps&lt;/span&gt;&lt;span&gt; extends&lt;/span&gt;&lt;span&gt; ComponentProps&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  className&lt;/span&gt;&lt;span&gt;?:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; default&lt;/span&gt;&lt;span&gt; MyComponent: React.&lt;/span&gt;&lt;span&gt;FC&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;MyComponentProps&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;props&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;myClassName&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;MyComponent Component&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  )&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;src/components/MyComponent/index.ts&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; *&lt;/span&gt;&lt;span&gt; from&lt;/span&gt;&lt;span&gt; &apos;./MyComponent&apos;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Why stop there? &lt;strong&gt;Create entire app boilerplates&lt;/strong&gt; using the same method - create your boilerplate
app, add all your libraries, and replace your app name with &lt;code&gt;{{name}}&lt;/code&gt;. That’s it! You can run this
package with input from any local files and output them in (or as) your next project.&lt;/p&gt;
&lt;p&gt;Check out the documentation by clicking &lt;a href=&quot;https://casraf.dev/simple-scaffold&quot;&gt;this link&lt;/a&gt;!&lt;/p&gt;</content:encoded></item><item><title>Placeholder.photo: An easy way to add mockup images to your UI and web development workflow</title><link>https://casraf.dev/2022/04/placeholder-photo/</link><guid isPermaLink="true">https://casraf.dev/2022/04/placeholder-photo/</guid><pubDate>Thu, 21 Apr 2022 12:55:03 GMT</pubDate><content:encoded>&lt;p&gt;When prototyping UI designs or code, we often need images to put in places when we have no finalized
images in the design, or in order to test out different sizes and colors when only some are
provided.&lt;/p&gt;
&lt;p&gt;I find myself needing to find an image to use when developing websites and apps often, and finding a
mock image to upload is time consuming - not to mention leaves possibly unwanted files in your code
or design.&lt;/p&gt;
&lt;p&gt;So what do we do?&lt;/p&gt;
&lt;p&gt;Enter: &lt;a href=&quot;https://placeholder.photo&quot;&gt;placeholder.photo&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;What is placeholder.photo?&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://placeholder.photo&quot;&gt;Placeholder.photo&lt;/a&gt; is a service that lets you use placeholder images anywhere, saving you the
hassle of creating images in the required sizes for temporary use-cases.&lt;/p&gt;
&lt;p&gt;It is a tool that gives you an image URL to use anywhere, from any web or other origin.&lt;/p&gt;
&lt;h2&gt;Why would I need this?&lt;/h2&gt;
&lt;p&gt;Say you are working on implementing a website design in HTML/CSS and more languages of your
choosing.&lt;/p&gt;
&lt;p&gt;When confronted with the need to use an image, for, say, an image that exists on the design -
usually I would put it in the appropriate asset location in the project.&lt;/p&gt;
&lt;p&gt;But what if any of these is true…&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The image represents &lt;strong&gt;user uploaded content&lt;/strong&gt;, so the size is variable?&lt;/li&gt;
&lt;li&gt;The image is not finalized and ready for use in the design?&lt;/li&gt;
&lt;li&gt;The image does not exist at all and a placeholder box is given in the design docs?&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;How does placeholder.photo help me solve this?&lt;/h2&gt;
&lt;p&gt;Now instead of having to go ahead and either find images of appropriate sizes, making sure they are
uploaded to an accessible location online, or in the case of updates to the spec, now having to
update said images…&lt;/p&gt;
&lt;p&gt;You can just take a URL from &lt;a href=&quot;https://placeholder.photo&quot;&gt;placeholder.photo&lt;/a&gt;.&lt;/p&gt;
&lt;div&gt;
&lt;p&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;The preview tool lets you decide how the image looks by letting you customize size, background and
text colors, the text content and font size.&lt;/p&gt;
&lt;p&gt;But really, you can just customize the URL yourself and use the images as necessary without having
to rely on it.&lt;/p&gt;
&lt;p&gt;A URL like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;https://placeholder.photo/img/255?bg_color=fb9e00&amp;amp;fg_color=ffffff&amp;amp;text=My+Text&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Will generate this image:&lt;/p&gt;
&lt;div&gt;
&lt;p&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;When generating from the tool, you can easily copy the image URL you made using the copy button:&lt;/p&gt;
&lt;div&gt;
&lt;p&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Or drag &amp;amp; drop to a supporting tool:&lt;/p&gt;
&lt;div&gt;
&lt;p&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;And that’s it! Enjoy easier time coding &amp;amp; designing.&lt;/p&gt;
&lt;div&gt;
&lt;p&gt;
&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;/div&gt;</content:encoded></item><item><title>The 5 Steps of Completion: Learning to break coding problems down</title><link>https://casraf.dev/2022/05/the-5-steps-of-completion-learning-to-break-coding-problems-down/</link><guid isPermaLink="true">https://casraf.dev/2022/05/the-5-steps-of-completion-learning-to-break-coding-problems-down/</guid><pubDate>Sun, 01 May 2022 17:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When developers first start their journey, sometimes syntax is easy. You’ve memorized a few
commands, classes, and method names, and you can make simple applications quite easily. Also, there
is plenty of docs and tutorials online - as well as Stack Overflow - surely copying-and-pasting your
way through would be enough, right?&lt;/p&gt;
&lt;p&gt;Let’s try to answer the question: how to start approaching problem solving for any given task?&lt;/p&gt;

&lt;p&gt;Well, the real problem most &lt;strong&gt;actually&lt;/strong&gt; face on a day-to-day basis when trying to program is not
determining which lines of &lt;em&gt;if&lt;/em&gt;s and &lt;em&gt;for&lt;/em&gt; loops go where. The problem starts at… well, the
problem. Where to start? How to approach a solution to a given task or problem you are facing?&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Some people, when confronted with a problem, think “I know, I’ll use regular expressions.” Now
they have two problems.&lt;/p&gt;
&lt;p&gt;~ Jamie Zawinski&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Learning to put theory to practice&lt;/h2&gt;
&lt;p&gt;In my time developing I tried hard to learn to recognize the most important skills a developer has
in their tool set - and one of the biggest hurdles for starting developers - is the ability to know
&lt;em&gt;how&lt;/em&gt; to face &lt;em&gt;any&lt;/em&gt; problem - at least, in the context of development.&lt;/p&gt;
&lt;p&gt;When an up-coming developer gets a new task – either from someone else (a boss, a colleague)
or that they themselves define (for challenge, for proof of concept, for learning) - they often get
stuck at the first hurdle - “where do I start?”&lt;/p&gt;
&lt;p&gt;Now that you can create simple sentences – you have no idea where to begin when trying to
write entire paragraphs or essays. How does it my “hello world” actually do what it should? How do I
take what I know and combine the parts so they work with each other?&lt;/p&gt;
&lt;p&gt;Here, I will try to give you some thought experiments and tools so you can be self-sufficient when
it comes to starting a new coding task, whether it is adding to existing code or beginning from
scratch.&lt;/p&gt;
&lt;p&gt;We will start very simple, and then try to apply the same ideas to more complex tasks later on. So
while it may seem very stupid and superficial at first, it will come together soon enough.&lt;/p&gt;
&lt;h2&gt;Defining a “problem”&lt;/h2&gt;
&lt;p&gt;A &lt;strong&gt;problem&lt;/strong&gt; in our context is any &lt;strong&gt;task&lt;/strong&gt; that has been given, however large or small.&lt;/p&gt;
&lt;p&gt;For example, here are some small task examples:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Add a “Contact” button to the home page that links to the contact page [design attached]&lt;/li&gt;
&lt;li&gt;Create a program that calculates the area of a rectangle&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And here are some larger tasks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create a snake game&lt;/li&gt;
&lt;li&gt;Create a to-do list application&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All tasks are completely unique, and yet somewhat alike. How do we approach each of these from a
development perspective?&lt;/p&gt;
&lt;h2&gt;The methodology - The 5 Steps of Completion&lt;/h2&gt;
&lt;p&gt;Let’s start introducing our frame of thought. We need to define the context of our task, so we can
ask the proper questions right after.&lt;/p&gt;
&lt;h3&gt;1. Ask Questions&lt;/h3&gt;
&lt;p&gt;Let’s take a basic example to start.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Given story: Add a “Contact” button to the home page that links to the contact page&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Ask yourself the following questions:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;What is the current &lt;strong&gt;state&lt;/strong&gt;? (i.e., from what point do I start?)&lt;br /&gt;
This won’t become a task later, but will be important for you to picture a “delta” of sorts,
which draws a path in your mind from A (what there is) to B (what you want achieve).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;What does the user expect to &lt;strong&gt;see&lt;/strong&gt; change?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;What &lt;strong&gt;interactions&lt;/strong&gt; can the user make with the change?&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;2. Give Answers&lt;/h3&gt;
&lt;p&gt;Let’s attempt to answer these questions for this case, one by one.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Q:&lt;/strong&gt; What is the current state?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A:&lt;/strong&gt; The user can see a website, with various sections, images and links. In the home page,
there is no button that links to the “Contact” page.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Q:&lt;/strong&gt; What does the user expect to see change?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A:&lt;/strong&gt; The user should see a new button with the text “Contact”&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Q:&lt;/strong&gt; What interactions can the user make with the change?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A:&lt;/strong&gt; The user should be able to click the button. Clicking it will point to the “Contact” page.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;We didn’t learn a lot of new information by breaking down these questions. But we did gain
separation which is critical. Now we have a &lt;strong&gt;list of tasks&lt;/strong&gt;, instead of &lt;strong&gt;one task&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Why is that helpful?&lt;/p&gt;
&lt;h3&gt;3. Break it down&lt;/h3&gt;
&lt;p&gt;Large and small tasks alike – even the smallest of addition to your code, can be broken down
even further. It is crucial that we learn to break things down as developers, because then we can
create &lt;strong&gt;actionable items&lt;/strong&gt; which we can start &lt;strong&gt;working&lt;/strong&gt; on.&lt;/p&gt;
&lt;p&gt;The example above might be a bit trivial, but it gets the point across - we can break everything
down further, into infinity. Really, this has no limit. But we should stop somewhere…&lt;/p&gt;
&lt;p&gt;So what next?&lt;/p&gt;
&lt;h3&gt;4. Create Actionables&lt;/h3&gt;
&lt;p&gt;Now what we want to do is take the answers from above, and compile them into a list of actions. The
first question is redundant as it describes what is going on right now. So we will list the rest and
try to create action items from them:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The user should see a new button with the text “Contact”&lt;/strong&gt;
&lt;ol&gt;
&lt;li&gt;Show a button on the page&lt;/li&gt;
&lt;li&gt;Have the text say “Contact”&lt;/li&gt;
&lt;li&gt;Design the button to fit with the design&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The user should be able to click the button. Clicking it will point to the “Contact” page.&lt;/strong&gt;
&lt;ol&gt;
&lt;li&gt;Attach a click “handler” to the button&lt;/li&gt;
&lt;li&gt;Make it redirect the page to the desired URL&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We are making some progress. We now have a list of tasks to work with. See how 2 small questions
already turned into 5 tasks, and that is for a tiny problem.&lt;/p&gt;
&lt;h3&gt;5. Solve It&lt;/h3&gt;
&lt;p&gt;Now that we have tasks - we can get crackin’.&lt;/p&gt;
&lt;p&gt;For the sake of this let’s assume we are developers that are relatively new to web development. What
would we do to approach this task?&lt;/p&gt;
&lt;h4&gt;When in doubt, look it up&lt;/h4&gt;
&lt;p&gt;Google, Duck Duck Go, Brave, whatever. What do you choose? Your preference. What would we search for
here exactly?&lt;/p&gt;
&lt;p&gt;The trick is to be as &lt;strong&gt;broad as possible&lt;/strong&gt;, and when the answers don’t help, get more specific from
there. This is different from debugging - where you should be as specific as possible and search for
exact matches. But I digress. Oh, and always prepend/append the programming/hypertext language you
are referencing.&lt;/p&gt;
&lt;p&gt;The action item is “Show a button on the page”. So let’s try see: &lt;strong&gt;how do we display a button in
HTML?&lt;/strong&gt; and &lt;strong&gt;how do I set its text?&lt;/strong&gt; Now that we have these simple questions, we can remove some
excess words and we remain with &lt;strong&gt;2 great keywords&lt;/strong&gt; to search which should get us starting on the
topic, at the most general sense:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://google.com/search?q=html+button&quot;&gt;html button&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now to answer the next burning question - &lt;strong&gt;how to make the button fit with the design?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We have another tricky task here.&lt;br /&gt;
Say our design looks something like this:&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Here, if we are unfamiliar with everything, breaking down even further can really help. Let’s look
at what defines the style of this button:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It has a gradient fill/background&lt;/li&gt;
&lt;li&gt;It has white text&lt;/li&gt;
&lt;li&gt;It has rounded corners&lt;/li&gt;
&lt;li&gt;It has a shadow behind it&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We will want to search for more specific things, which will also be our broken down tasks; such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://google.com/search?q=css+gradient+fill&quot;&gt;css gradient fill&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://google.com/search?q=css+rounded+box&quot;&gt;css text color&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://google.com/search?q=css+rounded+box&quot;&gt;css rounded box&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://google.com/search?q=css+shadow&quot;&gt;css shadow&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Try some more simple task ideas, and practice breaking down tasks into chunks that you can
definitely complete. As tasks become more complex, they will sometimes become harder. But sometimes
you will find that they don’t, really — they just become &lt;strong&gt;longer&lt;/strong&gt;, and &lt;strong&gt;larger&lt;/strong&gt;, which may
seem daunting, but all big tasks can sometimes be broken up into many, many tiny tasks, which will
be easy to actually get done.&lt;/p&gt;
&lt;h4&gt;Any progress is important: No task is too small&lt;/h4&gt;
&lt;p&gt;One of the important concepts behind this approach is that tasks should be broken down until you can
picture in your mind the entire task’s execution, to some extent. It’s not really a rule to follow,
but more of a guiding line. Tasks that you can picture yourself doing almost fully, you can probably
complete. And if the smallest task is still beyond your knowledge, then you try to break it down
further, or must go out and seek that knowledge.&lt;/p&gt;
&lt;p&gt;Which is to say - in order to make your tasks &lt;strong&gt;useful&lt;/strong&gt;, you must make them &lt;strong&gt;doable&lt;/strong&gt;. Once you
can tick a checkbox that says “I’m done”, no matter the size of the contribution to the total pool
of tasks, you still made some &lt;strong&gt;progress&lt;/strong&gt;. That progress piles up slowly, and soon you will see
your projects come to life.&lt;/p&gt;
&lt;h4&gt;The next steps&lt;/h4&gt;
&lt;p&gt;Practice makes perfect - so whatever side project you have, apply this new knowledge to it. Take
your tasks, ask the questions, and turn them into smaller ones.&lt;/p&gt;
&lt;p&gt;In the next part of this series, we will take a look at some more complex examples - abstract, large
ideas that need to become a coded reality. For the meantime, a good grasp of simple tasks breakdown
will shape your minds properly for the next difficulty that lies ahead.&lt;/p&gt;</content:encoded></item><item><title>How to use React memoization hooks for increased performance</title><link>https://casraf.dev/2022/08/how-to-use-react-memoization-hooks-for-increased-performance/</link><guid isPermaLink="true">https://casraf.dev/2022/08/how-to-use-react-memoization-hooks-for-increased-performance/</guid><pubDate>Wed, 10 Aug 2022 13:41:15 GMT</pubDate><content:encoded>&lt;p&gt;As React apps grow bigger and more complex, performance becomes more and more of a problem. As
components become larger and contain more and more sub-components, rendering becomes slow and turns
into a bottleneck.&lt;/p&gt;
&lt;p&gt;How do we tackle this? If you haven’t used &lt;code&gt;useMemo&lt;/code&gt; and &lt;code&gt;useCallback&lt;/code&gt;, we can start with those.&lt;/p&gt;

&lt;p&gt;In this tutorial, we will take a look at how these 2 very easy and handy callbacks work, and why
they are so useful. In fact, these days my eyes get sore when I don’t see them used. So let’s dive
in to what they do.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;React.useMemo&lt;/h2&gt;
&lt;p&gt;This React hook’s one goal is to save a value for later use, and not re-calculating it on the spot.&lt;/p&gt;
&lt;p&gt;Let’s take an example of some expensive logic that runs in our render function:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; ExpensiveComponent&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; React&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;FC&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;props&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;list&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setList&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; React.&lt;/span&gt;&lt;span&gt;useState&lt;/span&gt;&lt;span&gt;([])&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;counter&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setCounter&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; React.&lt;/span&gt;&lt;span&gt;useState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; multiplied&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; list.&lt;/span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; (i &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; 972&lt;/span&gt;&lt;span&gt; +&lt;/span&gt;&lt;span&gt; 1000&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; 5213&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;join&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;, &apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  function&lt;/span&gt;&lt;span&gt; addRandom&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    setList&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;prev&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;prev, Math.&lt;/span&gt;&lt;span&gt;floor&lt;/span&gt;&lt;span&gt;(Math.&lt;/span&gt;&lt;span&gt;random&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; 10000&lt;/span&gt;&lt;span&gt;)])&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  function&lt;/span&gt;&lt;span&gt; increaseCounter&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    setCounter&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;prev&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; ++&lt;/span&gt;&lt;span&gt;prev)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      Counter: {counter}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;&lt;/span&gt;&lt;span&gt;br&lt;/span&gt;&lt;span&gt; /&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      Multiplied: {multiplied}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;&lt;/span&gt;&lt;span&gt;br&lt;/span&gt;&lt;span&gt; /&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt; onClick&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{addRandom}&amp;gt;Add Random&amp;lt;/&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt; onClick&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{increaseCounter}&amp;gt;Increase Counter&amp;lt;/&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  )&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Doesn’t seem very problematic, but take a look at the &lt;code&gt;multiplied&lt;/code&gt; variable. The logic in this
example isn’t too bad, but imagine working with a giant list of special objects. This mapping alone
could be a performance problem, especially if it’s looped in a parent component.&lt;/p&gt;
&lt;p&gt;In this case, there is another state hook - &lt;code&gt;counter&lt;/code&gt;. When &lt;code&gt;setCounter&lt;/code&gt; is called, &lt;code&gt;multiplied&lt;/code&gt;
will be calculated all over again, wasting previous resources, even when an update in that case is
not needed as these variables are independent of each-other.&lt;/p&gt;
&lt;p&gt;That’s where &lt;code&gt;useMemo&lt;/code&gt; comes in hand (read the
&lt;a href=&quot;https://reactjs.org/docs/hooks-reference.html#usememo&quot;&gt;official docs&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;You can use this hook to save the value, and retrieve the same object until a re-calculation is
needed.&lt;/p&gt;
&lt;p&gt;Here’s how it’s used, the only line we need to change is the &lt;code&gt;multiplied&lt;/code&gt; definition:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; multiplied&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; React.&lt;/span&gt;&lt;span&gt;useMemo&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;recalculating multiplied:&apos;&lt;/span&gt;&lt;span&gt;, list)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; list.&lt;/span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; (i &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; 972&lt;/span&gt;&lt;span&gt; +&lt;/span&gt;&lt;span&gt; 1000&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; 5213&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;join&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;, &apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}, [list])&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;useMemo&lt;/code&gt; hook takes 2 arguments:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The &lt;code&gt;create&lt;/code&gt; function - used to return the calculated value of the variable we want to eventually
use&lt;/li&gt;
&lt;li&gt;A list of dependencies. The dependency list is used to determine &lt;strong&gt;when&lt;/strong&gt; a new value should be
calculated - i.e, when to run the &lt;code&gt;create&lt;/code&gt; function again.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;We added a &lt;code&gt;console.log&lt;/code&gt; call here just to note when a new value is being calculated.&lt;/p&gt;
&lt;p&gt;And with those changes, we can try our component again (here is the updated code just in case):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; ExpensiveComponent&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; React&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;FC&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;props&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;list&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setList&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; React.&lt;/span&gt;&lt;span&gt;useState&lt;/span&gt;&lt;span&gt;([])&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;counter&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setCounter&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; React.&lt;/span&gt;&lt;span&gt;useState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; multiplied&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; React.&lt;/span&gt;&lt;span&gt;useMemo&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;recalculating multiplied:&apos;&lt;/span&gt;&lt;span&gt;, list)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; list.&lt;/span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; (i &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; 972&lt;/span&gt;&lt;span&gt; +&lt;/span&gt;&lt;span&gt; 1000&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; 5213&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;join&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;, &apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }, [list])&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  function&lt;/span&gt;&lt;span&gt; addRandom&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    setList&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;prev&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;prev, Math.&lt;/span&gt;&lt;span&gt;floor&lt;/span&gt;&lt;span&gt;(Math.&lt;/span&gt;&lt;span&gt;random&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; 10000&lt;/span&gt;&lt;span&gt;)])&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  function&lt;/span&gt;&lt;span&gt; increaseCounter&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    setCounter&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;prev&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; ++&lt;/span&gt;&lt;span&gt;prev)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      Counter: {counter}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;&lt;/span&gt;&lt;span&gt;br&lt;/span&gt;&lt;span&gt; /&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      Multiplied: {multiplied}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;&lt;/span&gt;&lt;span&gt;br&lt;/span&gt;&lt;span&gt; /&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt; onClick&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{addRandom}&amp;gt;Add Random&amp;lt;/&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt; onClick&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{increaseCounter}&amp;gt;Increase Counter&amp;lt;/&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  )&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you now change the counter by using the “Increase Counter” button, you will see our &lt;code&gt;console.log&lt;/code&gt;
call is not being invoked again until we use the other button for “Add Random”.&lt;/p&gt;
&lt;h2&gt;React.useCallback&lt;/h2&gt;
&lt;p&gt;Now we have the other hook - &lt;code&gt;useCallback&lt;/code&gt; (read the
&lt;a href=&quot;https://reactjs.org/docs/hooks-reference.html#usecallback&quot;&gt;official docs&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;This works exactly like the &lt;code&gt;useMemo&lt;/code&gt; hook - except it is for functions instead of variable values.&lt;/p&gt;
&lt;p&gt;We can take our button functions, and wrap each in this hook to make sure our function reference
only changes when needed.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; ExpensiveComponent&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; React&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;FC&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;props&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;list&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setList&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; React.&lt;/span&gt;&lt;span&gt;useState&lt;/span&gt;&lt;span&gt;([])&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;counter&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setCounter&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; React.&lt;/span&gt;&lt;span&gt;useState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; multiplied&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; React.&lt;/span&gt;&lt;span&gt;useMemo&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; list.&lt;/span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; (i &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; 972&lt;/span&gt;&lt;span&gt; +&lt;/span&gt;&lt;span&gt; 1000&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; 5213&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;join&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;, &apos;&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    [list],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  )&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; addRandom&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; React.&lt;/span&gt;&lt;span&gt;useCallback&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; setList&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;prev&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;prev, Math.&lt;/span&gt;&lt;span&gt;floor&lt;/span&gt;&lt;span&gt;(Math.&lt;/span&gt;&lt;span&gt;random&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; 10000&lt;/span&gt;&lt;span&gt;)]),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    [setList],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  )&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; increaseCounter&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; React.&lt;/span&gt;&lt;span&gt;useCallback&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; setCounter&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;prev&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; ++&lt;/span&gt;&lt;span&gt;prev), [setCounter])&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      Counter: {counter}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;&lt;/span&gt;&lt;span&gt;br&lt;/span&gt;&lt;span&gt; /&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      Multiplied: {multiplied}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;&lt;/span&gt;&lt;span&gt;br&lt;/span&gt;&lt;span&gt; /&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt; onClick&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{addRandom}&amp;gt;Add Random&amp;lt;/&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt; onClick&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{increaseCounter}&amp;gt;Increase Counter&amp;lt;/&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  )&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now both our variables and functions are memoized and will only change reference when their
dependencies dictate they do.&lt;/p&gt;
&lt;h2&gt;Caveats&lt;/h2&gt;
&lt;p&gt;Using these hooks don’t come without their share of problems.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Consider whether this actually improves performance or not in your specific case. If your state
is pretty regularly changed and these memoizations have to run pretty often, their performance
increases might be outweighed by the performance costs of actually running the memoization logic.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Dependency checking and generating can be expensive. Be careful what you put in the dependency
lists, and if needed, make some more memoization and map your objects and lists in deterministic
ways so that they are statically inspectable easily. Also avoid using expensive methods such as
&lt;code&gt;JSON.stringify&lt;/code&gt; to create those memoizations or dependencies, as it might also be too expensive
to be worth the trouble and might make things worse.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Other things to consider&lt;/h2&gt;
&lt;p&gt;You might want to make sure your project uses lint rules that enforce exhaustive dependencies, as
they make tracking these things much more easy.&lt;/p&gt;
&lt;p&gt;In some cases, you might want to add ignore comments in very specific places, but it makes it very
clear that this part is built that way intentionally and prevents more confusion about when to
update the dependencies.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Hopefully you find this useful. There are many other hooks to learn about, but these 2 are very
useful and often ignored, so I thought it would be good to highlight them. If you are interested you
might want to look up &lt;code&gt;useRef&lt;/code&gt; and how it differs from &lt;code&gt;useMemo&lt;/code&gt;, or maybe I’ll make another part
about it in the future. Who knows?&lt;/p&gt;</content:encoded></item><item><title>React: Using useEffect Effectively</title><link>https://casraf.dev/2022/08/react-using-use-effect-effectively/</link><guid isPermaLink="true">https://casraf.dev/2022/08/react-using-use-effect-effectively/</guid><pubDate>Thu, 18 Aug 2022 21:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Whether you’re new to React or you are recently migrating from class components (this late?), you
might not be familiar with how &lt;code&gt;useEffect&lt;/code&gt; works.&lt;/p&gt;
&lt;p&gt;Actually, you might be using it still from other existing (or copied 😉) code, but have no idea (or
just a small idea) what’s actually happening.&lt;/p&gt;
&lt;p&gt;In this post we’ll dive in to this very flexible hook, some example, and some real, common
use-cases.&lt;/p&gt;

&lt;h2&gt;What is &lt;code&gt;React.useEffect&lt;/code&gt;?&lt;/h2&gt;
&lt;p&gt;This is a React hook that lets you introduce &lt;strong&gt;side-effects&lt;/strong&gt; into your components.&lt;/p&gt;
&lt;p&gt;Here is some example code of a common &lt;code&gt;useEffect&lt;/code&gt; hook:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; MyComponent&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;posts&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setPosts&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; React.&lt;/span&gt;&lt;span&gt;useState&lt;/span&gt;&lt;span&gt;([])&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // This will fire only once when the component mounts&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  React.&lt;/span&gt;&lt;span&gt;useEffect&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    PostsAPI.&lt;/span&gt;&lt;span&gt;getPostsList&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;then&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; setPosts&lt;/span&gt;&lt;span&gt;(data))&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }, [])&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; &amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;{&lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stringify&lt;/span&gt;&lt;span&gt;(posts)}&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;What are side effects?&lt;/h3&gt;
&lt;p&gt;Well, side effects are a general programming term, under the larger term “effects”. Effects have a
wide meaning, and side-effects are a subset of that. The short version of the definition of a
side-effect is “a function (or piece of code) that introduces changes to any value that is not
inside its scope” - whether input arguments, or outside scoped variables (or global variables).&lt;/p&gt;
&lt;p&gt;In React components, that mostly means any function inside a component that introduces a change that
is outside the scope of rendering itself.&lt;/p&gt;
&lt;p&gt;Are you calling an outside API? You are introducing an effect. Saving a value to the state?
Introduces an effect. &lt;em&gt;As long as it’s outside the scope of rendering the component itself, it’s a
side-effect.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Side-effects &lt;strong&gt;are not bad per-se&lt;/strong&gt; - but introducing them needs to be done with intent, and with
caution.&lt;/p&gt;
&lt;h3&gt;React side-effects example&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;React.useEffect&lt;/code&gt; is a hook that lets you wrap side-effects in a safe way so that you can avoid
unnecessary renders, and use the necessary ones correctly. Consider the following code:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; MyComponent&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;count&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setCount&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; React.&lt;/span&gt;&lt;span&gt;useState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  CounterAPI.&lt;/span&gt;&lt;span&gt;getCount&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;then&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; setCount&lt;/span&gt;&lt;span&gt;(data))&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; &amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;{count}&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you haven’t noticed th the problem at first glance - don’t worry.&lt;/p&gt;
&lt;p&gt;What happened here is we implicitly introduced a side effect right inside our rendering.&lt;/p&gt;
&lt;p&gt;As soon as the component renders, the &lt;code&gt;CounterAPI&lt;/code&gt; will get called, return something, and &lt;code&gt;setCount&lt;/code&gt;
will trigger a state change, which causes a render.&lt;/p&gt;
&lt;p&gt;Then immediately, the new render runs, and another API call is made… This will go on forever, so
React (or your browser) will crash in some way, killing the page.&lt;/p&gt;
&lt;p&gt;This is why we need to guard against side-effects. Here is a simple change to fix this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; MyComponent&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;count&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setCount&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; React.&lt;/span&gt;&lt;span&gt;useState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  React.&lt;/span&gt;&lt;span&gt;useEffect&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // add this line&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    CounterAPI.&lt;/span&gt;&lt;span&gt;getCount&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;then&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; setCount&lt;/span&gt;&lt;span&gt;(data))&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }, []) &lt;/span&gt;&lt;span&gt;// and this line&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; &amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;{count}&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will mean our API only calls once.&lt;/p&gt;
&lt;h3&gt;A replacement for lifecycle methods&lt;/h3&gt;
&lt;p&gt;If you have ever used class-based components in React, you might be familiar with the term. In these
types of components, sometimes we would need to fire events when the component mounts for the first
time, or when one of the props changes, to sync our state properly with our business-logic.&lt;/p&gt;
&lt;p&gt;This is the same effect from before, but in the old* method:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; MyComponent&lt;/span&gt;&lt;span&gt; extends&lt;/span&gt;&lt;span&gt; React&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Component&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  render&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // return ...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  componentDidMount&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    MyAPI.&lt;/span&gt;&lt;span&gt;getResults&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;then&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;setState&lt;/span&gt;&lt;span&gt;({ apiData: data }))&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
* It&apos;s not deprecated, and React will support it for a while to come. But class-based components
are generally considered to be outdated, a bit less easy to predict sometimes - especially for
newcomers, and generally too verbose for sometimes really simple code.
&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;useEffect&lt;/code&gt; hook is also the de-facto replacement to the lifecycle methods, which are
unavailable when you are using function components.&lt;/p&gt;
&lt;p&gt;Let’s dive into how it’s used.&lt;/p&gt;
&lt;h2&gt;Structure of the &lt;code&gt;useEffect&lt;/code&gt; hook&lt;/h2&gt;
&lt;p&gt;A &lt;code&gt;useEffect&lt;/code&gt; call takes 2 arguments.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;React.&lt;/span&gt;&lt;span&gt;useEffect&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // the side-effect code&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // Optionally return a clean-up function&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; doCleanup&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  [&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    /* dependencies */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  ],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first is a function to run at… “some time”. The time in which to run this effect differs on
your use case, and your usage of the 2nd argument. We will talk about how it works shortly.&lt;/p&gt;
&lt;p&gt;The second is optional - but in my opinion, very required - the dependency array. Like many React
hooks (&lt;a href=&quot;/2022/08/how-to-use-react-memoization-hooks-for-increased-performance&quot;&gt;see my post about &lt;code&gt;useMemo&lt;/code&gt; and &lt;code&gt;useCallback&lt;/code&gt;&lt;/a&gt;), it is a list of changes that
the component listens to, only running the effect after changes to the dependencies were detected.&lt;/p&gt;
&lt;p&gt;In the case of &lt;code&gt;useEffect&lt;/code&gt;, the effect always runs at least once, even if that means all the values
it depends on might be &lt;code&gt;undefined&lt;/code&gt;. So wait, how does this work?&lt;/p&gt;
&lt;h2&gt;Thinking “Reactive”&lt;/h2&gt;
&lt;p&gt;This is a bit confusing. We are listening to changes now - okay. I will elaborate a bit.&lt;/p&gt;
&lt;p&gt;If in the past we used to rely on the framework to call specific methods in different lifecycle
times, now we are going to change our way of thinking a bit. This method introduced many problems,
especially because developers were often introducing side-effects where they shouldn’t be possible,
introducing bugs and other problems.&lt;/p&gt;
&lt;p&gt;Many hooks now rely on dependency arrays to define when they are being re-built. Our way of thinking
will be similar - our function will run when changes are made, and they’re always made at least
once - in the mounting phase.&lt;/p&gt;
&lt;p&gt;If we want to fire code once when our component is mounted, we can introduce an effect with 0
dependencies. This causes it to only run once, as no changes will ever be introduced to this very
constant array. This causes the hook to behave exactly as &lt;code&gt;componentDidMount&lt;/code&gt; did.&lt;/p&gt;
&lt;h3&gt;Dependencies: &lt;code&gt;[] !== undefined&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;It’s important to note that supplying &lt;strong&gt;no array&lt;/strong&gt; in the dependency argument, and supplying an
&lt;strong&gt;empty array&lt;/strong&gt; are 2 different things! Try it out yourself to see the difference.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; MyComponent&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;count&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setCount&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; React.&lt;/span&gt;&lt;span&gt;useState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  React.&lt;/span&gt;&lt;span&gt;useEffect&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;no array&apos;&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  React.&lt;/span&gt;&lt;span&gt;useEffect&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;empty array&apos;&lt;/span&gt;&lt;span&gt;), [])&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; &amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; onClick&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; setCount&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;prev&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; ++&lt;/span&gt;&lt;span&gt;prev)}&amp;gt;{count}&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Running a component with this code will show different results.&lt;/p&gt;
&lt;p&gt;Immediately, we will see this in the log:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;no array&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;empty array&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Nothing special yet. Let’s click the &lt;code&gt;div&lt;/code&gt; and see what happens.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;no array&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ahh… What’s this? It ran again? But we supplied no dependencies?&lt;/p&gt;
&lt;p&gt;Supplying no value at all means it will update &lt;strong&gt;every time the component renders&lt;/strong&gt;, not just once.
The dependency needs to be an array for any comparison to happen. If it’s &lt;code&gt;undefined&lt;/code&gt;, comparison is
skipped and the effect runs every time.&lt;/p&gt;
&lt;h2&gt;How can we use this now?&lt;/h2&gt;
&lt;p&gt;Let’s review the basics we learned about this hook.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;It runs at least once&lt;/li&gt;
&lt;li&gt;It then runs:
&lt;ol&gt;
&lt;li&gt;every time its dependency array contents change, or&lt;/li&gt;
&lt;li&gt;if there is no array, after every render&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Component mounting&lt;/h3&gt;
&lt;p&gt;The simplest effect we learned about is commonly referred to as a mount effect. It runs once since
you supply no dependencies.&lt;/p&gt;
&lt;p&gt;Let’s say we are viewing a list of posts.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; PostList&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;posts&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setPosts&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; React.&lt;/span&gt;&lt;span&gt;useState&lt;/span&gt;&lt;span&gt;([])&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;page&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setPage&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; React.&lt;/span&gt;&lt;span&gt;useState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  React.&lt;/span&gt;&lt;span&gt;useEffect&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    setLoading&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    PostsAPI.&lt;/span&gt;&lt;span&gt;getPosts&lt;/span&gt;&lt;span&gt;({ page: page }).&lt;/span&gt;&lt;span&gt;then&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      setPosts&lt;/span&gt;&lt;span&gt;(data.posts)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      setLoading&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }, [])&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      {loading&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        ?&lt;/span&gt;&lt;span&gt; &apos;Loading...&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        :&lt;/span&gt;&lt;span&gt; posts.&lt;/span&gt;&lt;span&gt;forEach&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;post&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            return&lt;/span&gt;&lt;span&gt; &amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; key&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{post.id}&amp;gt;{post.title}&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          })}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  )&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will fetch the posts once, and once only.&lt;/p&gt;
&lt;p&gt;Then we have more cases, where we decide what to supply, and we can make our component react to
changes in either the props or change, and introduce the effects we need.&lt;/p&gt;
&lt;p&gt;Let’s consider an extension to this use-case to test with.&lt;/p&gt;
&lt;h3&gt;Reacting to a state change&lt;/h3&gt;
&lt;p&gt;We will add a page selector, which lets us change which page of the posts we are viewing. For the
sake of simplicity, the count per page is implicit and hard-coded in the backend.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;  const PostList = () =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const [posts, setPosts] = React.useState([])&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const [loading, setLoading] = React.useState(true)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;span&gt;+&lt;/span&gt;   const [page, setPage] = React.useState(0)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    React.useEffect(() =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      setLoading(true)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      PostsAPI.getPosts({ page: page }).then((data) =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        setPosts(data.posts)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        setLoading(false)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;span&gt;-&lt;/span&gt;   }, [])&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;span&gt;+&lt;/span&gt;   }, [page])&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;div&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;span&gt;+&lt;/span&gt;       &amp;lt;span onClick={() =&amp;gt; setPage(prev =&amp;gt; ++prev)}&amp;gt;Next page &amp;amp;raquo;&amp;lt;/span&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        {loading&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          ? &apos;Loading...&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          : posts.forEach((post, i) =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;              return &amp;lt;div key={post.id}&amp;gt;{post.title}&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            })}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;/div&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    )&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can see, we supplied our &lt;code&gt;page&lt;/code&gt; as a dependency for the API call. This means that once the
component mounts, and then every time this count changes afterwards, React will run our effect
again, fetching the posts from the API. Neat!&lt;/p&gt;
&lt;h3&gt;The cleanup function, timeouts and intervals&lt;/h3&gt;
&lt;p&gt;Another great use-case is to run some logic on a fixed interval, or after a timeout. Let’s say we
want to increase a counter every second.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; Counter&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;count&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setCount&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; React.&lt;/span&gt;&lt;span&gt;useState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  React.&lt;/span&gt;&lt;span&gt;useEffect&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    setInterval&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      setCount&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;prev&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; ++&lt;/span&gt;&lt;span&gt;prev)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }, &lt;/span&gt;&lt;span&gt;1000&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }, [])&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; &amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;{count}&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This works great! Every second, we get a new count with a larger number.&lt;/p&gt;
&lt;p&gt;But this introduces a bug: navigate to another route, or get this component removed in some way. You
will notice the interval keeps running!&lt;/p&gt;
&lt;p&gt;In a case like this, we want to make sure our interval or timeout stops when the component is no
longer mounted, in order to prevent a memory leak.&lt;/p&gt;
&lt;p&gt;This is where &lt;strong&gt;cleanup functions&lt;/strong&gt; come in handy. A &lt;code&gt;useEffect&lt;/code&gt; call can also accept a function as
a result of its callback.&lt;/p&gt;
&lt;p&gt;That function will run once one of the dependencies change, before the “new” effect runs. It will
also run once the component &lt;strong&gt;unmounts&lt;/strong&gt;, i.e., is being removed from the tree in some way. These
cases are the same phase that calls the cleanup function, with the only difference being whether a
new effect will replace the discarded one, or will the effect end its life there.&lt;/p&gt;
&lt;p&gt;This allows us to do things like cancel timers, API requests, or make some other cleanup to make
sure nothing happens once it’s no longer necessary. React class-component users will recognize this
as being a similar use-case for using &lt;code&gt;componentWillUnmount&lt;/code&gt; lifecycle method. Let’s see how we can
fix our previous example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;  const Counter = () =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const [count, setCount] = React.useState(0)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    React.useEffect(() =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;span&gt;-&lt;/span&gt;     setInterval(() =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;span&gt;+&lt;/span&gt;     const id = setInterval(() =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        setCount((prev) =&amp;gt; ++prev)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      }, 1000)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;span&gt;+&lt;/span&gt;     return () =&amp;gt; clearInterval(id)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }, [])&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return &amp;lt;div&amp;gt;{count}&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you may or may not already know, &lt;code&gt;setInterval&lt;/code&gt; and &lt;code&gt;setTimeout&lt;/code&gt; return an id token that we can
later use to cancel it. By supplying it as a return function to &lt;code&gt;useEffect&lt;/code&gt;, we are telling it to
destroy our timer every time it needs to. If it is supposed to create a new one due to dependency
change, it will still create it and our code will still work.&lt;/p&gt;
&lt;h3&gt;External listeners&lt;/h3&gt;
&lt;p&gt;The last prominent use-case we will show here is using external listeners inside components.&lt;/p&gt;
&lt;p&gt;This is similar to our previous use-case, where we have a function with a side-effect that needs to
be cleaned up. In fact it’s pretty much exactly the same. Let’s say we want to display the window
resolution in our component (or make some condition depend on a value from there). Let’s use our
previous knowledge to make a component that works for this.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; WindowSize&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;width&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setWidth&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; React.&lt;/span&gt;&lt;span&gt;useState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;height&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setHeight&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; React.&lt;/span&gt;&lt;span&gt;useState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  React.&lt;/span&gt;&lt;span&gt;useEffect&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; listener&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      setWidth&lt;/span&gt;&lt;span&gt;(window.innerWidth)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      setHeight&lt;/span&gt;&lt;span&gt;(window.innerHeight)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    window.&lt;/span&gt;&lt;span&gt;addEventListener&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;resize&apos;&lt;/span&gt;&lt;span&gt;, listener)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; window.&lt;/span&gt;&lt;span&gt;removeEventListener&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;resize&apos;&lt;/span&gt;&lt;span&gt;, listener)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }, [])&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      Width: {width}px&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;&lt;/span&gt;&lt;span&gt;br&lt;/span&gt;&lt;span&gt; /&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      Height: {height}px&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  )&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we have an effect that listens to window changes, and makes sure to update the state with it.
Another success! And as you can see, it’s not much different than the interval/timeout example.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I hope that now you might see a bit more clearly how this hook is useful, and how to actually put it
to use properly.&lt;/p&gt;
&lt;p&gt;Experiment with using &lt;code&gt;useEffect&lt;/code&gt; in different cases, and experiment with thinking more reactively
about changes - making dependencies change things as they need, instead of always chaining function
calls whenever you change values just to replicate the same behavior.&lt;/p&gt;

</content:encoded></item><item><title>Create your own useOptimistic hook</title><link>https://casraf.dev/2023/11/create-your-own-use-optimistic-hook/</link><guid isPermaLink="true">https://casraf.dev/2023/11/create-your-own-use-optimistic-hook/</guid><pubDate>Thu, 09 Nov 2023 21:38:07 GMT</pubDate><content:encoded>&lt;p&gt;Recently, I came across &lt;a href=&quot;https://www.youtube.com/watch?v=M3mGY0pgFk0&amp;amp;t=279s&quot;&gt;this video&lt;/a&gt; by Web Dev
Simplified, which introduced me to React’s new
&lt;a href=&quot;https://react.dev/reference/react/useOptimistic&quot;&gt;useOptimistic&lt;/a&gt; hook.&lt;/p&gt;
&lt;p&gt;I began to wonder whether this is even needed. React has never been a library to provide more than
bare-bones controls and hooks. The &lt;code&gt;useCallback&lt;/code&gt;, &lt;code&gt;useMemo&lt;/code&gt;, and &lt;code&gt;useState&lt;/code&gt; hooks are most of the
built-in hooks and they provide a solid base upon which you can build pretty much any logic inside
your components or custom hooks.&lt;/p&gt;
&lt;p&gt;So since when does React provide more high-level abstractions above hooks? At that point, they
become even more opinionated than they already are. Not all use cases are the same - but this &lt;em&gt;is&lt;/em&gt; a
pretty generic hook, all things considered. Apparently, it is still in the experimental stage.&lt;/p&gt;
&lt;p&gt;I decided to experiment with creating this hook on my own, to understand if it was overcoming some
internal system limitation. I looked at various examples of existing optimistic hooks on NPM, but I
didn’t really find one that falls in line with more modern coding styles, or they were completely
outdated and abandoned.&lt;/p&gt;
&lt;h2&gt;Why create your own?&lt;/h2&gt;
&lt;p&gt;Ideally, React should generally keep its size down as much as possible, and not increase the bundle
with more bloat that won’t be used by everyone. I believe this hook is a very nice idea, but it is
not relevant for everyone, and I think if you find it does fit your needs, you don’t have to wait
for official support - you can do it (or copy the final code here) in relatively short time, and
feature-complete (and added).&lt;/p&gt;
&lt;p&gt;In addition, I thought it would make a good thought experiment and some coding practice, it can’t
hurt!&lt;/p&gt;
&lt;h2&gt;Looking at React’s original implementation&lt;/h2&gt;
&lt;p&gt;I decided to first take a look at React’s official implementation of this. Not the source code, but
I did want to get a sense of whether I like the consensus they came up with, or whether I feel it
has room for improvement.&lt;/p&gt;
&lt;p&gt;Here is a basic usage example, verbatim from
&lt;a href=&quot;https://react.dev/reference/react/useOptimistic#use&quot;&gt;React’s docs&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;optimisticState&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;addOptimistic&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; useOptimistic&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  state,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // updateFn&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span&gt;currentState&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;optimisticValue&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // merge and return new state&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // with optimistic value&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Seems nice, but I think we can do better.&lt;/p&gt;
&lt;p&gt;Off the top of my head, I don’t see a way to get the status of the optimistic value. Can I know when
it’s been finalized without having to maintain a separate state?&lt;/p&gt;
&lt;p&gt;Also, there doesn’t seem to be a way to roll back to the original value if an error has occurred. Or
at least, the docs don’t yet specify any. We can fix that, and provide a more “holistic” approach,
which in my opinion doesn’t leave the scope of this hook. Say I push an optimistic update, and it
fails, how can I roll it back?&lt;/p&gt;
&lt;p&gt;We’ll try to make sense of where it should go as we go along.&lt;/p&gt;
&lt;h2&gt;Creating a hook&lt;/h2&gt;
&lt;p&gt;Let’s start with the signature, and make sure we have our types going for us.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; StateUpdater&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;T&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;A&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; T&lt;/span&gt;&lt;span&gt;&amp;gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;currentState&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; T&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;newState&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; A&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; T&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; useOptimistic&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;T&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;A&lt;/span&gt;&lt;span&gt;&amp;gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  initial&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; T&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  merge&lt;/span&gt;&lt;span&gt;?:&lt;/span&gt;&lt;span&gt; StateUpdater&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;T&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;A&lt;/span&gt;&lt;span&gt;&amp;gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; readonly&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;T&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;React&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Dispatch&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;React&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;SetStateAction&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;T&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;gt;] {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // ...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We start with the basics. An initial value, of type &lt;code&gt;T&lt;/code&gt; which is generic, and an optional merge
function. If it’s not provided by the user, we’ll just have it replace the old state with the new
when an update comes. The &lt;code&gt;merge&lt;/code&gt; function can have any type in the argument for the final dispatch,
but we assume &lt;code&gt;T&lt;/code&gt; unless specified.&lt;/p&gt;
&lt;p&gt;Now we should add the state that maintains the optimistic value.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; useOptimistic&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;T&lt;/span&gt;&lt;span&gt;&amp;gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  initial&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; T&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  merge&lt;/span&gt;&lt;span&gt;?:&lt;/span&gt;&lt;span&gt; StateUpdater&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;T&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;A&lt;/span&gt;&lt;span&gt;&amp;gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; readonly&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;T&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;React&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Dispatch&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;React&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;SetStateAction&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;A&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;gt;] {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;state&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setState&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; useState&lt;/span&gt;&lt;span&gt;(initial)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; [state, setState]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We… have a very simple hook working right now. It doesn’t really do anything special at all. Very
bland.&lt;/p&gt;
&lt;p&gt;Ok so, what’s the most basic requirement? Well, for starters, it should let us merge the state if we
provide a &lt;code&gt;merge&lt;/code&gt; function, which we haven’t use yet from our arguments. Then, we should take into
account that updating the parent state (held in &lt;code&gt;initial&lt;/code&gt;) should also update the state itself, so
that it will be up-to-date with the latest server value.&lt;/p&gt;
&lt;h2&gt;Creating the update functions&lt;/h2&gt;
&lt;p&gt;We’ll start by adding the &lt;code&gt;useEffect&lt;/code&gt; hook, that will update from the parent &lt;code&gt;initial&lt;/code&gt; prop when it
updates.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;useEffect&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  setState&lt;/span&gt;&lt;span&gt;(initial)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}, [initial])&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, when the &lt;code&gt;initial&lt;/code&gt; value is updated, the internal state will be kept up-to-date with the
change.&lt;/p&gt;
&lt;p&gt;We can also implement the &lt;code&gt;merge&lt;/code&gt; functionality. For that, we need to provide another implementation
of the &lt;code&gt;setState&lt;/code&gt; function, which uses it internally and adds some more logic.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; update&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; useCallback&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; SetStateAction&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;A&lt;/span&gt;&lt;span&gt;&amp;gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; newValue&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; typeof&lt;/span&gt;&lt;span&gt; value &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &apos;function&apos;&lt;/span&gt;&lt;span&gt; ?&lt;/span&gt;&lt;span&gt; (value &lt;/span&gt;&lt;span&gt;as&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; T&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; T&lt;/span&gt;&lt;span&gt;)(state) &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; value&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; merged&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; merge &lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt; merge&lt;/span&gt;&lt;span&gt;(state, newValue &lt;/span&gt;&lt;span&gt;as&lt;/span&gt;&lt;span&gt; A&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; (newValue &lt;/span&gt;&lt;span&gt;as&lt;/span&gt;&lt;span&gt; T&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    setState&lt;/span&gt;&lt;span&gt;(merged)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  [state],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; [state, update] &lt;/span&gt;&lt;span&gt;// instead of [state, setState]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Okay, it’s a little more to break down here.&lt;/p&gt;
&lt;p&gt;First, we have an identical callback to &lt;code&gt;setState&lt;/code&gt; now, except it also runs the &lt;code&gt;merge&lt;/code&gt; function
before sending the final value.&lt;/p&gt;
&lt;p&gt;In &lt;code&gt;useState&lt;/code&gt;, in addition to passing a new state value, the &lt;code&gt;setState&lt;/code&gt; function can be used with a
callback function, which takes the current state as a parameter, and should return the new state.
The variable &lt;code&gt;newValue&lt;/code&gt; is populated with the result of that process.&lt;/p&gt;
&lt;p&gt;Then we just stack the &lt;code&gt;merge&lt;/code&gt; function on top of that, if it exists.&lt;/p&gt;
&lt;p&gt;The usage for this would still be:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;count&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setCount&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; useOptimistic&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// option 1&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;setCount&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// option 2&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;setCount&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; c &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;All the changes happen internally. Nice.&lt;/p&gt;
&lt;p&gt;That’s pretty much it as it stands right now. React’s implementation is pretty similar to this, with
maybe a slight variation on how the change is calculated with the parent’s update, to determine when
the value should be brought up to date.&lt;/p&gt;
&lt;p&gt;A bit anti-climactic? Well, let’s add a few bells and whistles. And we can now, because we don’t
need React to build it for us. We’re strong!&lt;/p&gt;
&lt;h2&gt;What else can we add? Rolling back on failure&lt;/h2&gt;
&lt;p&gt;The first idea for improvement I have is to be able to catch an error, and roll back the value if it
fails. Let’s begin implementing this.&lt;/p&gt;
&lt;p&gt;First, we need to have a value to go back to. So every time the developer updates the optimistic
state to a new value, and an operation related to it fails, we want to provide a way to revert to
the original value.&lt;/p&gt;
&lt;p&gt;For a recap, here’s the entire hook as it now is, before we make changes:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; StateUpdater&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;T&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;A&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; T&lt;/span&gt;&lt;span&gt;&amp;gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;currentState&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; T&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;newState&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; A&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; T&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; useOptimistic&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;T&lt;/span&gt;&lt;span&gt;&amp;gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  initial&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; T&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  merge&lt;/span&gt;&lt;span&gt;?:&lt;/span&gt;&lt;span&gt; StateUpdater&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;T&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;A&lt;/span&gt;&lt;span&gt;&amp;gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; readonly&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;T&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;React&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Dispatch&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;React&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;SetStateAction&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;A&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;gt;] {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;state&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setState&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; useState&lt;/span&gt;&lt;span&gt;(initial)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  useEffect&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    setState&lt;/span&gt;&lt;span&gt;(initial)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }, [initial])&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; update&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; useCallback&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    (&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; SetStateAction&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;A&lt;/span&gt;&lt;span&gt;&amp;gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const&lt;/span&gt;&lt;span&gt; newValue&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; typeof&lt;/span&gt;&lt;span&gt; value &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &apos;function&apos;&lt;/span&gt;&lt;span&gt; ?&lt;/span&gt;&lt;span&gt; (value &lt;/span&gt;&lt;span&gt;as&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; T&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; T&lt;/span&gt;&lt;span&gt;)(state) &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; value&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const&lt;/span&gt;&lt;span&gt; merged&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; merge &lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt; merge&lt;/span&gt;&lt;span&gt;(state, newValue &lt;/span&gt;&lt;span&gt;as&lt;/span&gt;&lt;span&gt; A&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; (newValue &lt;/span&gt;&lt;span&gt;as&lt;/span&gt;&lt;span&gt; T&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      setState&lt;/span&gt;&lt;span&gt;(merged)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    [state],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  )&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; [state, update]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, let’s say the developer comes across a case like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; MyApp&lt;/span&gt;&lt;span&gt;({ &lt;/span&gt;&lt;span&gt;comments&lt;/span&gt;&lt;span&gt; }) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;oComments&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;addComment&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; useOptimistic&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    comments &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; [],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    (&lt;/span&gt;&lt;span&gt;existingComments&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;newComment&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;existingComments, newComment],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  )&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; sendComment&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; async&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;comment&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Comment&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    addComment&lt;/span&gt;&lt;span&gt;(comment)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    await&lt;/span&gt;&lt;span&gt; sendCommentToApi&lt;/span&gt;&lt;span&gt;(comment) &lt;/span&gt;&lt;span&gt;// could fail and throw&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; ...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this case, we are sending some API request to send the comment content to the backend and make
sure it’s already there.&lt;/p&gt;
&lt;p&gt;But if it fails, we will have to revert it to its original content manually by keeping a separate
state for the original value, and set it back if it failed.&lt;/p&gt;
&lt;p&gt;Instead, we can make our new hook handle this.&lt;/p&gt;
&lt;p&gt;Let’s add another state that maintains the previous value:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;rollbackState&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setRollbackState&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; useState&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;T&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; null&lt;/span&gt;&lt;span&gt;&amp;gt;(&lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And now that we have that, we should set this rollback value once we set a new value through the
regular setter:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; update&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; useCallback&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; SetStateAction&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;A&lt;/span&gt;&lt;span&gt;&amp;gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; newValue&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; typeof&lt;/span&gt;&lt;span&gt; value &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &apos;function&apos;&lt;/span&gt;&lt;span&gt; ?&lt;/span&gt;&lt;span&gt; (value &lt;/span&gt;&lt;span&gt;as&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; T&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; T&lt;/span&gt;&lt;span&gt;)(state) &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; value&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; merged&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; merge &lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt; merge&lt;/span&gt;&lt;span&gt;(state, newValue &lt;/span&gt;&lt;span&gt;as&lt;/span&gt;&lt;span&gt; A&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; (newValue &lt;/span&gt;&lt;span&gt;as&lt;/span&gt;&lt;span&gt; T&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    setRollbackState&lt;/span&gt;&lt;span&gt;(state)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    setState&lt;/span&gt;&lt;span&gt;(merged)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  [state],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We take this value, save it, and… Do nothing with it. We should probably write that logic
somewhere. How’s this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; rollback&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; useCallback&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (rollbackState &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; null&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // you can either return here, or throw an error, and be more strict about false rollbacks:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // throw new Error(&apos;There is no state to roll back to&apos;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  setState&lt;/span&gt;&lt;span&gt;(rollbackState&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  setRollbackState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}, [rollbackState])&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And we can also add a &lt;code&gt;done&lt;/code&gt; callback, which ditches the retained value, just for clean up.
Something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; done&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; useCallback&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  setRollbackState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}, [])&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let’s also return these in our hook:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; useOptimistic&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;T&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;A&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; T&lt;/span&gt;&lt;span&gt;&amp;gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  initial&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; T&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  merge&lt;/span&gt;&lt;span&gt;?:&lt;/span&gt;&lt;span&gt; StateUpdater&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;T&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;A&lt;/span&gt;&lt;span&gt;&amp;gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; readonly&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  T&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  React&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Dispatch&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;React&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;SetStateAction&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;A&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    readonly&lt;/span&gt;&lt;span&gt; rollback&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; void&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    readonly&lt;/span&gt;&lt;span&gt; done&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; void&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;] {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // ...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; [state, update, { rollback, done }]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Nice, right? We just revert. Now to actually use it, we can update the component:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;oComments&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;addComment&lt;/span&gt;&lt;span&gt;, { &lt;/span&gt;&lt;span&gt;done&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;rollback&lt;/span&gt;&lt;span&gt; }] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; useOptimistic&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  //                          ^^^^^^^^^^^^^^^^^^&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  comments &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; [],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span&gt;existingComments&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;newComment&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;existingComments, newComment],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; sendComment&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; async&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;comment&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Comment&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  try&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    addComment&lt;/span&gt;&lt;span&gt;(comment)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    await&lt;/span&gt;&lt;span&gt; sendCommentToApi&lt;/span&gt;&lt;span&gt;(comment) &lt;/span&gt;&lt;span&gt;// could fail and throw&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  } &lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; (e) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    rollback&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That’s not too bad. If we fail, we use &lt;code&gt;rollback&lt;/code&gt; and get back our previous value, and we never had
to save it separately, which is good for scaling this to multiple places.&lt;/p&gt;
&lt;p&gt;But… if you give it a go, you will notice a problem.&lt;/p&gt;
&lt;p&gt;Let’s do a small test:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; fail&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; async&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;comment&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Comment&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  try&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    addComment&lt;/span&gt;&lt;span&gt;(comment)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    await&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Promise&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;res&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; setTimeout&lt;/span&gt;&lt;span&gt;(res, &lt;/span&gt;&lt;span&gt;1000&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    throw&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Error&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;Oops!&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    done&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  } &lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    rollback&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Make this run somewhere and you will realize the value reverted back to &lt;code&gt;null&lt;/code&gt; instead of the old
value, or simply doesn’t roll back. What gives?&lt;/p&gt;
&lt;p&gt;Here is the problem: when we are using &lt;code&gt;rollback&lt;/code&gt;, it uses the state variable, &lt;code&gt;rollbackState&lt;/code&gt;, to
set the value back on the main state. But here is the thing - this variable is a dependency in the
callback hook on &lt;code&gt;rollback&lt;/code&gt;. Take a closer look:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// before:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// const [rollbackState, setRollbackState] = useState&amp;lt;T | null&amp;gt;(null)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;//&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// after:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; rollbackState&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; useRef&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;T&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; null&lt;/span&gt;&lt;span&gt;&amp;gt;(&lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; rollback&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; useCallback&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (rollbackState &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; null&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  setState&lt;/span&gt;&lt;span&gt;(rollbackState&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  setRollbackState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}, [rollbackState])&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;//  ^^^^^^^^^^^^^&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So why is that bad? Well, in our &lt;code&gt;fail&lt;/code&gt; test above, we used &lt;code&gt;addComment&lt;/code&gt; which is our &lt;code&gt;update&lt;/code&gt;
function with a fancy name. When the API call fails we call &lt;code&gt;rollback&lt;/code&gt;. But &lt;code&gt;rollback&lt;/code&gt; was created
bound to the &lt;code&gt;rollbackState&lt;/code&gt; that was there at the time of its creation. We &lt;code&gt;wait&lt;/code&gt; for a promise to
return, and then use the &lt;code&gt;done&lt;/code&gt; or &lt;code&gt;rollback&lt;/code&gt; functions, that were created &lt;strong&gt;before&lt;/strong&gt; the
&lt;code&gt;rollbackState&lt;/code&gt; was even set away from &lt;code&gt;null&lt;/code&gt; and into a value in the first place. So basically we
are re-using the value from before we put anything in it, which means it’s &lt;code&gt;null&lt;/code&gt;, which is the
value we roll back to when using &lt;code&gt;rollback&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;How do we fix this?&lt;/p&gt;
&lt;h2&gt;Fixing the stale value&lt;/h2&gt;
&lt;p&gt;There are probably many possible solutions, but here is my very naive one.&lt;/p&gt;
&lt;p&gt;How do I make sure I get the correct reference to the updated value?&lt;/p&gt;
&lt;p&gt;In reality, I don’t need &lt;code&gt;rollbackState&lt;/code&gt; to be an actual state. Why can I say that with confidence?
Well, I don’t need to call a re-render when it updates, as it is only there to reference a value in
another function when called later, and it’s not displayed in the UI - meaning it has no need to
call for a render when I update it. Also it happens on literally every &lt;code&gt;update&lt;/code&gt; call as well, so I
am trying to uselessly mark the frame dirty for re-render twice when one time is enough.&lt;/p&gt;
&lt;p&gt;With that in mind, I can now safely switch the &lt;code&gt;rollbackState&lt;/code&gt; from a state hook to a &lt;code&gt;useRef&lt;/code&gt; hook.&lt;/p&gt;
&lt;p&gt;Why would that work? Because a &lt;code&gt;useRef&lt;/code&gt; reference is always a constant object. But the &lt;code&gt;current&lt;/code&gt;
value inside that object can be changed. In effect, when we reference a ref value, we can say we are
sure it is the most recent, as the ref never needs to change and no mismatches can occur.&lt;/p&gt;
&lt;p&gt;Here’s how it looks after the fix:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; update&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; useCallback&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; SetStateAction&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;A&lt;/span&gt;&lt;span&gt;&amp;gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; newValue&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; typeof&lt;/span&gt;&lt;span&gt; value &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &apos;function&apos;&lt;/span&gt;&lt;span&gt; ?&lt;/span&gt;&lt;span&gt; (value &lt;/span&gt;&lt;span&gt;as&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; T&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; T&lt;/span&gt;&lt;span&gt;)(state) &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; value&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; merged&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; merge &lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt; merge&lt;/span&gt;&lt;span&gt;(state, newValue &lt;/span&gt;&lt;span&gt;as&lt;/span&gt;&lt;span&gt; A&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; (newValue &lt;/span&gt;&lt;span&gt;as&lt;/span&gt;&lt;span&gt; T&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    rollbackState.current &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; state&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    setState&lt;/span&gt;&lt;span&gt;(merged)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  [state],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; rollback&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; useCallback&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (rollbackState.current &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; null&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  setState&lt;/span&gt;&lt;span&gt;(rollbackState.current&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  rollbackState.current &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; null&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}, [])&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; done&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; useCallback&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  rollbackState.current &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; null&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}, [])&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This way, no matter when I call the &lt;code&gt;rollback&lt;/code&gt; or &lt;code&gt;done&lt;/code&gt; functions, they will always try to access
the same parent object. With that object, they can modify the inner property and we don’t need to
worry about the data being stale.&lt;/p&gt;
&lt;h2&gt;Simplifying usage&lt;/h2&gt;
&lt;p&gt;You might notice that now you can have a pretty redundant pattern of &lt;code&gt;try {...} catch() {...}&lt;/code&gt; where
you would &lt;code&gt;rollback&lt;/code&gt; and &lt;code&gt;done&lt;/code&gt; at the end of each section. Well that’s no fun, we want less code.&lt;/p&gt;
&lt;p&gt;Why not add this to our hook?&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; run&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; useCallback&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  async&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;cb&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; Promise&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;unknown&lt;/span&gt;&lt;span&gt;&amp;gt; &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt; unknown&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    try&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      await&lt;/span&gt;&lt;span&gt; cb&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      done&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    } &lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; (_) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      rollback&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  [done, rollback],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Simple yet elegant. If we export this from the hook:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; [state, update &lt;/span&gt;&lt;span&gt;as&lt;/span&gt;&lt;span&gt; React&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Dispatch&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;React&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;SetStateAction&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;A&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;gt;, { done, rollback, run }]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now it will now let us do:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; sendComment&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; async&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;comment&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Comment&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  run&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    addComment&lt;/span&gt;&lt;span&gt;(comment)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    await&lt;/span&gt;&lt;span&gt; sendCommentToApi&lt;/span&gt;&lt;span&gt;(comment)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Much simpler, right?&lt;/p&gt;
&lt;h2&gt;Saving the promise state&lt;/h2&gt;
&lt;p&gt;One last thing I want us to add is the ability to know when our state is loading. This way, even
without using &lt;code&gt;useMutation&lt;/code&gt; from
&lt;a href=&quot;https://www.npmjs.com/package/@tanstack/react-query&quot;&gt;@tanstack/react-query&lt;/a&gt;, we can get a loading
state of any promise, while working directly with the &lt;code&gt;useOptimistic&lt;/code&gt; hook instead.&lt;/p&gt;
&lt;p&gt;For that we can simply add a state or a ref (decide which you want depending on your wanted DX),
which sets a &lt;code&gt;pending&lt;/code&gt; state to &lt;code&gt;true&lt;/code&gt; and &lt;code&gt;false&lt;/code&gt; when we update the state, and when we call
done/rollback, accordingly.&lt;/p&gt;
&lt;p&gt;Here is the full entire hook with all the things we made here:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; StateUpdater&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;T&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;A&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; T&lt;/span&gt;&lt;span&gt;&amp;gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;currentState&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; T&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;newState&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; A&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; T&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; useOptimistic&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;T&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;A&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; T&lt;/span&gt;&lt;span&gt;&amp;gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  initial&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; T&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  merge&lt;/span&gt;&lt;span&gt;?:&lt;/span&gt;&lt;span&gt; StateUpdater&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;T&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;A&lt;/span&gt;&lt;span&gt;&amp;gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; readonly&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  T&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  React&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Dispatch&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;React&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;SetStateAction&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;A&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    readonly&lt;/span&gt;&lt;span&gt; done&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; void&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    readonly&lt;/span&gt;&lt;span&gt; rollback&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; void&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    readonly&lt;/span&gt;&lt;span&gt; pending&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; boolean&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    readonly&lt;/span&gt;&lt;span&gt; run&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;promise&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; Promise&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;unknown&lt;/span&gt;&lt;span&gt;&amp;gt; &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt; unknown&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; void&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;] {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;state&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setState&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; useState&lt;/span&gt;&lt;span&gt;(initial)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; rollbackState&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; useRef&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;T&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; null&lt;/span&gt;&lt;span&gt;&amp;gt;(&lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;pending&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setPending&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; useState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  useEffect&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    setPending&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    setState&lt;/span&gt;&lt;span&gt;(initial)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }, [initial])&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; update&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; useCallback&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    (&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; SetStateAction&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;A&lt;/span&gt;&lt;span&gt;&amp;gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const&lt;/span&gt;&lt;span&gt; newValue&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; typeof&lt;/span&gt;&lt;span&gt; value &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &apos;function&apos;&lt;/span&gt;&lt;span&gt; ?&lt;/span&gt;&lt;span&gt; (value &lt;/span&gt;&lt;span&gt;as&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; T&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; T&lt;/span&gt;&lt;span&gt;)(state) &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; value&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const&lt;/span&gt;&lt;span&gt; merged&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; merge &lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt; merge&lt;/span&gt;&lt;span&gt;(state, newValue &lt;/span&gt;&lt;span&gt;as&lt;/span&gt;&lt;span&gt; A&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; (newValue &lt;/span&gt;&lt;span&gt;as&lt;/span&gt;&lt;span&gt; T&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      rollbackState.current &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; state&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      setState&lt;/span&gt;&lt;span&gt;(merged)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      setPending&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    [state],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  )&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; rollback&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; useCallback&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (rollbackState.current &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; null&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      // throw new Error(&apos;There is no state to roll back to&apos;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      return&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    setState&lt;/span&gt;&lt;span&gt;(rollbackState.current&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    rollbackState.current &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; null&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    setPending&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }, [])&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; done&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; useCallback&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    rollbackState.current &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; null&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    setPending&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }, [])&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; run&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; useCallback&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    async&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;cb&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; Promise&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;unknown&lt;/span&gt;&lt;span&gt;&amp;gt; &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt; unknown&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      try&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        await&lt;/span&gt;&lt;span&gt; cb&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        done&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      } &lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; (_) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        rollback&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    [done, rollback],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  )&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    state,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    update &lt;/span&gt;&lt;span&gt;as&lt;/span&gt;&lt;span&gt; React&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Dispatch&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;React&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;SetStateAction&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;A&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    { done, rollback, pending, run },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  ] &lt;/span&gt;&lt;span&gt;as&lt;/span&gt;&lt;span&gt; const&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Where to go from here?&lt;/h2&gt;
&lt;p&gt;There are many ways that you can improve this hook, but I feel like it now reached a fairly usable
state which we can all build from in our own special ways.&lt;/p&gt;
&lt;p&gt;Until React decides (or decides against) promoting &lt;code&gt;useOptimistic&lt;/code&gt; to non-canary channels, maybe we
can all benefit from a relatively simple hook that does exactly this without having to wait.&lt;/p&gt;
&lt;p&gt;I hope you found this article useful or interesting. Feel free to ask questions or suggest
improvements in the comments!&lt;/p&gt;</content:encoded></item><item><title>Create an Automated Portfolio Using GitHub and Astro</title><link>https://casraf.dev/2024/08/create-an-automated-portfolio-using-github-and-astro/</link><guid isPermaLink="true">https://casraf.dev/2024/08/create-an-automated-portfolio-using-github-and-astro/</guid><pubDate>Tue, 27 Aug 2024 09:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;h2&gt;I have updated the code in this article to use Astro loaders/Content Layer, and release it as a &lt;a href=&quot;http://npmjs.com/package/github-repos-astro-loader&quot;&gt;package on NPM&lt;/a&gt;!&lt;/h2&gt;
&lt;/blockquote&gt;
&lt;p&gt;I have a tendency to start a lot of projects. I don’t necessarily finish them all — some
software is never truly finished, sometimes they become deprecated, and sometimes, I just end up
bored, or simply forget about them.&lt;/p&gt;
&lt;p&gt;As a contractor/potential employee, I find it useful to redirect people to my GitHub page, where my
projects are most prominently displayed and easily accessible, and they can get a quick glance at
what I like to do and how my code and conduct look.&lt;/p&gt;
&lt;p&gt;Since I have many rotating projects, it’s becoming quite difficult to keep track of which projects
were recently updated, added, or removed. Not to mention, 6 pins on the GitHub profile really isn’t
that much.&lt;/p&gt;
&lt;p&gt;In this article, I will try to automate this process just a bit. This blog seems like the perfect
place to make things a bit more organized but also make sure they can be updated easily. It gives me
more control than GitHub, and I already have a projects page here, which would be a great target to
replace and put something a bit less static in.&lt;/p&gt;
&lt;p&gt;Hopefully, this article will give you some nice ideas on how you can spruce up your website with
some semi-dynamic content, or how you can also help yourself display your featured work with a lot
less manual labor.&lt;/p&gt;
&lt;h2&gt;The goal&lt;/h2&gt;
&lt;p&gt;This blog is an &lt;a href=&quot;https://astro.build/&quot;&gt;Astro&lt;/a&gt; project hosted as a static website. I truly recommend
Astro; it’s an incredibly easy, fun, and flexible way to create a website or a blog that isn’t meant
to be some kind of large web application — but I digress.&lt;/p&gt;
&lt;p&gt;What I’m trying to accomplish here is essentially the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Get all my GitHub repos&lt;/li&gt;
&lt;li&gt;Filter out uninteresting ones and prominently display the best ones&lt;/li&gt;
&lt;li&gt;List others by type/category/language/some other grouping&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now, since this is a static website generator that I am using, there are a few limitations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;There is no “server side” during runtime — any processing is being done during build&lt;/li&gt;
&lt;li&gt;I want the system I am creating to play nicely with the existing code&lt;/li&gt;
&lt;li&gt;I want it to be easy to update, manage, add overrides to, and hide certain repositories&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;The plan&lt;/h2&gt;
&lt;p&gt;As of writing this, I haven’t yet changed a single character of code. I first want to get a glimpse
at what I currently have and try to see if I can come up with a plan for how to get through this.&lt;/p&gt;
&lt;p&gt;I already have a &lt;a href=&quot;/projects&quot;&gt;projects&lt;/a&gt; page; it’s simply an index with a list of some projects. Each
project links to its own page, which might or might not have more information. This isn’t ideal, but
I can already see a way to improve here — GitHub holds a README, a project description,
releases, and links. It would be nice to implement this information somehow into the page.&lt;/p&gt;
&lt;p&gt;In Astro, pages are created inside the &lt;code&gt;src/pages/&lt;/code&gt; directory of a project. Content that can be
generated for those pages using simpler formats, such as Markdown and MDX, is available inside the
&lt;code&gt;src/content&lt;/code&gt; directory. With that in mind, let’s take a peek at the project-related files:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;src/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;├── components&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   ├── ProjectLinks.astro&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;├── content&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   ├── config.ts&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   └── project&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│       ├── dart-script-runner.md&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│       ├── dungeon-paper.md&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│       ├── flutter-wheel-spinner.md&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│       ├── mudblock.md&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│       ├── simple-scaffold.md&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│       └── unaconfig.md&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;└── pages&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    └── projects&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        ├── [...slug].astro&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        └── index.astro&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can see, there are a few files that are relevant to projects.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A component that displays a project&lt;/li&gt;
&lt;li&gt;An index page&lt;/li&gt;
&lt;li&gt;A page for each project (&lt;code&gt;[...slug].astro&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Contents for each project (the &lt;code&gt;.md&lt;/code&gt; files)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So, for each repository I need to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Generate a &lt;code&gt;.md&lt;/code&gt; file&lt;/li&gt;
&lt;li&gt;Update the index page to display them with new information &amp;amp; sorting&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This will get me most of the way there. After that, it will be mostly connecting some pipes to make
sure it’s easy to reproduce this from, say, a CI environment, for easy deployment.&lt;/p&gt;
&lt;h2&gt;Getting the data&lt;/h2&gt;
&lt;p&gt;Well, I can’t display GitHub projects without getting their information, right?&lt;/p&gt;
&lt;p&gt;For this, I’ll need a
&lt;a href=&quot;https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens&quot;&gt;GitHub token&lt;/a&gt;
that works. I’ve already got one set up — perfect.&lt;/p&gt;
&lt;p&gt;Right now, the top of the &lt;code&gt;src/pages/[...slug].astro&lt;/code&gt; file looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; async&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; getStaticPaths&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; posts&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; getCollection&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;project&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; posts.&lt;/span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;post&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; ({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    params: { slug: post.slug },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    props: post,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }))&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; Props&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; CollectionEntry&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;&apos;project&apos;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; post&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; Astro.props&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;description&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;links&lt;/span&gt;&lt;span&gt; } &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; post.data&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;Content&lt;/span&gt;&lt;span&gt; } &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; post.&lt;/span&gt;&lt;span&gt;render&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The function &lt;code&gt;getStaticPaths&lt;/code&gt; is a special function in Astro that is responsible for telling Astro
which pages to generate, and it is using the content directory to do so. I am passing &lt;code&gt;slug&lt;/code&gt; as a
param to the page generator, which means it can be accessed in the name’s placeholder — that’s
what the file is called &lt;code&gt;[...slug].astro&lt;/code&gt;. &lt;code&gt;[]&lt;/code&gt; denotes param tokens to be replaced, and the &lt;code&gt;...&lt;/code&gt;
means they can have a directory path inside.&lt;/p&gt;
&lt;p&gt;The top of the &lt;code&gt;src/pages/index.astro&lt;/code&gt; file looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; projects&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; getCollection&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;project&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;projects.&lt;/span&gt;&lt;span&gt;sort&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;b&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; a.data.order &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; b.data.order&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;})&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Which is where we load all the projects for the index page. Note that they don’t necessarily have to
correlate; I can have pages without links routing to them or projects without pages to link to. This
is not our case, but it’s good to know.&lt;/p&gt;
&lt;p&gt;Practically anything can be loaded here — which is where I intend to invoke the generator of
new pages for the GitHub projects. So, let’s work on getting that to work.&lt;/p&gt;
&lt;h3&gt;Using GitHub API to grab repositories&lt;/h3&gt;
&lt;p&gt;A quick search and we can easily find the
&lt;a href=&quot;https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28#list-repositories-for-a-user&quot;&gt;GitHub API specification for repositories for a user&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This is where I need the API key.&lt;/p&gt;
&lt;p&gt;Let’s create a new TypeScript file and put whatever logic we need there in its own box.&lt;/p&gt;
&lt;p&gt;I’ll start simple and work up from there. Let’s make a request to the repos API, and for each repo
also grab the README file.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// src/github-projects.ts&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { GITHUB_TOKEN } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;./consts&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; GITHUB_USERNAME&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; &apos;chenasraf&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; headers&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Headers&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;headers.&lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;Authorization&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;`Bearer ${&lt;/span&gt;&lt;span&gt;GITHUB_TOKEN&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; async&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; getProjectsList&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Promise&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;GitHubProjectSchema&lt;/span&gt;&lt;span&gt;[]&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // Fetch the data&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; response&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; fetch&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`https://api.github.com/users/${&lt;/span&gt;&lt;span&gt;GITHUB_USERNAME&lt;/span&gt;&lt;span&gt;}/repos`&lt;/span&gt;&lt;span&gt;, { headers })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; repos&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; response.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; projects&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; GitHubProjectSchema&lt;/span&gt;&lt;span&gt;[] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; []&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // Create a new object for each repo + get README&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; repo&lt;/span&gt;&lt;span&gt; of&lt;/span&gt;&lt;span&gt; repos) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; project&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; GitHubProjectSchema.&lt;/span&gt;&lt;span&gt;parse&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      name: repo.name,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      url: repo.html_url,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      description: repo.description,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      stars: repo.stargazers_count,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      order: &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;repo.stargazers_count,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // Get the README.md static file&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; readmeResponse&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; fetch&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      `https://raw.githubusercontent.com/${&lt;/span&gt;&lt;span&gt;GITHUB_USERNAME&lt;/span&gt;&lt;span&gt;}/${&lt;/span&gt;&lt;span&gt;repo&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;}/${&lt;/span&gt;&lt;span&gt;repo&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;default_branch&lt;/span&gt;&lt;span&gt;}/README.md`&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      { headers },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    )&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; readme&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; readmeResponse.&lt;/span&gt;&lt;span&gt;text&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    project.readme &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; readme&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    projects.&lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt;(project)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; projects&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I’ll also create a type for it:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// src/types.ts&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; const&lt;/span&gt;&lt;span&gt; LinkSchema&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; z.&lt;/span&gt;&lt;span&gt;object&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  title: z.&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  href: z.&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  icon: z.&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;})&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; type&lt;/span&gt;&lt;span&gt; LinkSchema&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; z&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;infer&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;typeof&lt;/span&gt;&lt;span&gt; LinkSchema&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; const&lt;/span&gt;&lt;span&gt; GitHubProjectSchema&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; z.&lt;/span&gt;&lt;span&gt;object&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  name: z.&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  title: z.&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  description: z.&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;nullable&lt;/span&gt;&lt;span&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  url: z.&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  stars: z.&lt;/span&gt;&lt;span&gt;number&lt;/span&gt;&lt;span&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  readme: z.&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;optional&lt;/span&gt;&lt;span&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  order: z.&lt;/span&gt;&lt;span&gt;number&lt;/span&gt;&lt;span&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  links: z.&lt;/span&gt;&lt;span&gt;array&lt;/span&gt;&lt;span&gt;(LinkSchema),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;})&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; type&lt;/span&gt;&lt;span&gt; GitHubProjectSchema&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; z&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;infer&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;typeof&lt;/span&gt;&lt;span&gt; GitHubProjectSchema&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Seems like quite a bit of code, but it’s really not complex at all. Here’s a rundown:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Fetch the data from &lt;code&gt;/users/${GITHUB_USERNAME}/repos&lt;/code&gt;, which gives us all the basic information
about each repository&lt;/li&gt;
&lt;li&gt;Fetch the README from the raw file link for each repository&lt;/li&gt;
&lt;li&gt;Put it all together and push into an array&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now, I’ll just use that instead of the original in &lt;code&gt;index.astro&lt;/code&gt;, just so I can test what it shows
and that everything is working.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; projects&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; getProjectsList&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;projects.&lt;/span&gt;&lt;span&gt;sort&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;b&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; a.order &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; b.order&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;})&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// in the template itself:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;projects.&lt;/span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;project&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;&lt;/span&gt;&lt;span&gt;code&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;&lt;/span&gt;&lt;span&gt;pre&lt;/span&gt;&lt;span&gt;&amp;gt;{JSON.stringify(project, null, &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;)}&lt;/span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;pre&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;/&lt;/span&gt;&lt;span&gt;code&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I removed the previous &lt;code&gt;ProjectCard&lt;/code&gt; component for now because we haven’t updated it yet, and I just
wanna make sure things are working.&lt;/p&gt;
&lt;p&gt;Let’s refresh the page, and… Well, this is taking a couple of seconds, but okay, nothing major at
all. It loaded! Wait… this is only 30 projects. Pretty sure I have more than that, even just the
public ones.&lt;/p&gt;
&lt;p&gt;Hmm… Oh, here, I found the problem.&lt;/p&gt;
&lt;h3&gt;Handling pagination&lt;/h3&gt;
&lt;p&gt;Seems like I’ve hit a small snag, but it’s all well documented, so I can only blame my own laziness.&lt;/p&gt;
&lt;p&gt;Here is an excerpt from the GitHub API documentation for &lt;code&gt;/{user}/repos&lt;/code&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;page - integer&lt;/code&gt; The page number of the results to fetch. For more information, see
“&lt;a href=&quot;https://docs.github.com/rest/using-the-rest-api/using-pagination-in-the-rest-api&quot;&gt;Using pagination in the REST API.&lt;/a&gt;”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Okay, let’s implement pagination to get everything:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; fetchRepos&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; repos&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; []&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  let&lt;/span&gt;&lt;span&gt; page &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  let&lt;/span&gt;&lt;span&gt; response &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; fetchReposPage&lt;/span&gt;&lt;span&gt;(page)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  repos.&lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;response.repos)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  while&lt;/span&gt;&lt;span&gt; (response.url) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    response &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; fetchReposPage&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;++&lt;/span&gt;&lt;span&gt;page)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    repos.&lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;response.repos)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; repos&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; fetchReposPage&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;page&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; number&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; response&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; fetch&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    `https://api.github.com/users/${&lt;/span&gt;&lt;span&gt;GITHUB_USERNAME&lt;/span&gt;&lt;span&gt;}/repos?page=${&lt;/span&gt;&lt;span&gt;page&lt;/span&gt;&lt;span&gt;}&amp;amp;per_page=100`&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    { headers },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  )&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; repos&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; response.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; links&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; response.headers.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;link&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  let&lt;/span&gt;&lt;span&gt; url&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; null&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; null&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (links) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; next&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; links.&lt;/span&gt;&lt;span&gt;split&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;,&apos;&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;find&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;link&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; link.&lt;/span&gt;&lt;span&gt;includes&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;rel=&quot;next&quot;&apos;&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (next) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      url &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; next.&lt;/span&gt;&lt;span&gt;split&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;;&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;toString&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;slice&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; { repos, url }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I took some shortcuts — I used an iterating number instead of using the actual page link. It
should still work, and there is no unknown data in the actual link. I just need to make sure it
exists before trying.&lt;/p&gt;
&lt;p&gt;Also, I could have totally made the fetching logic recursive, which might have looked cleaner than a
&lt;code&gt;while&lt;/code&gt; loop. But I don’t wanna stress over this too much. Overengineering is bleh.&lt;/p&gt;
&lt;p&gt;Let’s try it now, and… it works! That didn’t take me multiple iterations to get it right &lt;em&gt;at all&lt;/em&gt;
😇.&lt;/p&gt;
&lt;p&gt;Well, I have all the data I could want for now. On to the next step.&lt;/p&gt;
&lt;h2&gt;Generating Markdown files&lt;/h2&gt;
&lt;p&gt;Astro consumes Markdown files, which allow me to also do some fancier rendering for each project
rather than just showing text. You can add metadata (often called “frontmatter”) at the top of the
file, like so:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Simple Scaffold&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;order&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;200&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;links&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  - &lt;/span&gt;&lt;span&gt;href&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;https://www.npmjs.com/package/simple-scaffold&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    icon&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;logo-npm&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    title&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;NPM&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;description&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;|-&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;p class=&quot;mb-0&quot;&amp;gt;A simple command to generate any file structure, from single components to entire app boilerplates.&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    See&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;a href=&quot;/2019/03/simple-scaffold&quot;&amp;gt;my post&amp;lt;/a&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    detailing more, or the&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;a href=&quot;https://chenasraf.github.io/simple-scaffold&quot;&amp;gt;documentation&amp;lt;/a&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    /&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;a href=&quot;https://www.npmjs.com/package/simple-scaffold&quot;&amp;gt;NPM&amp;lt;/a&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    page to start using it!&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;!-- Markdown content may go here --&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So, I want to generate a similar file, but populated with our new data from GitHub.&lt;/p&gt;
&lt;p&gt;I also want to be able to add custom information or add overrides, which won’t be destroyed if I
happen to want to re-fetch repositories. I’ll think about that later; for now, let’s get this new
task done.&lt;/p&gt;
&lt;h3&gt;Creating the file&lt;/h3&gt;
&lt;p&gt;While this is Markdown at the bottom, the top looks a lot like a YAML file.&lt;/p&gt;
&lt;p&gt;First thing’s first: I’ll move the original files to a different directory, so I don’t destroy them
right now while working. Also, I will probably want to keep some of the data there as overrides or
additions to what the repo info gives me.&lt;/p&gt;
&lt;p&gt;The next step is to convert the structure I already have to YAML. I am trying to decide whether to
use a package or just implement a simple string file writer for this. The latter option is cool, but
I’m sure there will be many problems with correctly escaping quotes and other YAML tokens in the
output, so I prefer just using a pre-made package for this. I quickly found
&lt;a href=&quot;https://www.npmjs.com/package/yaml&quot;&gt;yaml&lt;/a&gt;, and it seems like a perfect option for that.&lt;/p&gt;
&lt;p&gt;This part was surprisingly simple — only a few lines:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; generateProjectFile&lt;/span&gt;&lt;span&gt;({ &lt;/span&gt;&lt;span&gt;readme&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;project&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; GitHubProjectSchema&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; file&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; path.&lt;/span&gt;&lt;span&gt;join&lt;/span&gt;&lt;span&gt;(projectsDir, &lt;/span&gt;&lt;span&gt;`${&lt;/span&gt;&lt;span&gt;project&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;toLowerCase&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;}.md`&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; lines&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;&apos;---&apos;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  lines.&lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt;(yaml.&lt;/span&gt;&lt;span&gt;stringify&lt;/span&gt;&lt;span&gt;(project).&lt;/span&gt;&lt;span&gt;trim&lt;/span&gt;&lt;span&gt;())&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  lines.&lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;---&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (readme) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    lines.&lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt;(readme)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; fs.&lt;/span&gt;&lt;span&gt;writeFile&lt;/span&gt;&lt;span&gt;(file, lines.&lt;/span&gt;&lt;span&gt;join&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; async&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; generateProjectsList&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; projects&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; getProjectsList&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  await&lt;/span&gt;&lt;span&gt; Promise&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;all&lt;/span&gt;&lt;span&gt;(projects.&lt;/span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;(generateProjectFile))&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That already generates all the files for me. Success! I can now revert some of the index code back
to load it back from the original collection:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; generateProjectsList&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; projects&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; getCollection&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;project&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;projects.&lt;/span&gt;&lt;span&gt;sort&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;b&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; a.data.order &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; b.data.order&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;})&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And also update the correct types for Astro:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// src/content/config.ts&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; project&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; defineCollection&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  schema: GitHubProjectSchema,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;})&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; const&lt;/span&gt;&lt;span&gt; collections&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; { project }&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And now my files look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;simple-scaffold&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;description&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&amp;gt;-&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  Generate any file structure &amp;amp;mdash; from single components to entire app boilerplates, with a&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  single command.&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;url&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;https://github.com/chenasraf/simple-scaffold&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;stars&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;53&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;order&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;-53&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;links&lt;/span&gt;&lt;span&gt;: []&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p align=&quot;center&quot;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;img src=&quot;https://chenasraf.github.io//simple-scaffold/img/logo-lg.png&quot; alt=&quot;Logo&quot; /&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;!-- Rest of contents of the README.md (too big to show here) --&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This already looks pretty solid. If I just update the &lt;code&gt;ProjectCard&lt;/code&gt; props and use it again, I should
already see a nice list of projects. Let’s do that:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// src/pages/projects/index.astro&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;grid grid-cols-1 md:grid-cols-2 gap-8&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  {projects.&lt;/span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;project&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; &amp;lt;&lt;/span&gt;&lt;span&gt;ProjectCard&lt;/span&gt;&lt;span&gt; project&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{project} /&amp;gt;)}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// src/components/ProjectCard.astro&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; type&lt;/span&gt;&lt;span&gt; { CollectionEntry } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;astro:content&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; ProjectLinks &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;./ProjectLinks.astro&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; Props&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;project&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; CollectionEntry&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;&apos;project&apos;&lt;/span&gt;&lt;span&gt;&amp;gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;project&lt;/span&gt;&lt;span&gt; } &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; Astro.props&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;description&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;links&lt;/span&gt;&lt;span&gt; } &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; project.data&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;style&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  .project-card&lt;/span&gt;&lt;span&gt; p&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    @&lt;/span&gt;&lt;span&gt;apply&lt;/span&gt;&lt;span&gt; mt-&lt;/span&gt;&lt;span&gt;0;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    @&lt;/span&gt;&lt;span&gt;apply&lt;/span&gt;&lt;span&gt; mb-&lt;/span&gt;&lt;span&gt;0;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;style&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  class:list&lt;/span&gt;&lt;span&gt;={[&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &apos;project-card cursor-default transition duration-300&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &apos;border dark:border-none&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &apos;shadow-generic hover:shadow-generic-hover&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &apos;dark:shadow-generic-dark dark:hover:shadow-generic-dark-hover&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &apos;rounded-2xl p-6&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  ]}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;&lt;/span&gt;&lt;span&gt;h3&lt;/span&gt;&lt;span&gt; class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;mt-0&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt; href&lt;/span&gt;&lt;span&gt;={&lt;/span&gt;&lt;span&gt;`/projects/${&lt;/span&gt;&lt;span&gt;project&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;slug&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;span&gt;}&amp;gt;{title}&amp;lt;/&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;/&lt;/span&gt;&lt;span&gt;h3&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; set:html&lt;/span&gt;&lt;span&gt;={description} /&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;&lt;/span&gt;&lt;span&gt;ProjectLinks&lt;/span&gt;&lt;span&gt; links&lt;/span&gt;&lt;span&gt;={links} /&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Success! I see my GitHub projects all nicely laid out for me on the projects page.&lt;/p&gt;
&lt;p&gt;Let’s update the details page so that clicking it will lead to the actual information.&lt;/p&gt;
&lt;p&gt;There are 2 main things to change; first, let’s update the props so they reflect the new md files:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// src/pages/projects/[slug].astro&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; post&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; Astro.props&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;description&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;links&lt;/span&gt;&lt;span&gt; } &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; post.data&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;Content&lt;/span&gt;&lt;span&gt; } &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; post.&lt;/span&gt;&lt;span&gt;render&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You will notice the &lt;code&gt;const { Content } = await post.render()&lt;/code&gt; line there at the bottom.&lt;/p&gt;
&lt;p&gt;When importing the md file as part of a collection, the actual contents of the md file are already
parsed for Markdown and are returned as a component I can inject into the page.&lt;/p&gt;
&lt;p&gt;The metadata at the top (the frontmatter) is added alongside the content as additional props for
use.&lt;/p&gt;
&lt;p&gt;Now we can simply plug it into the template:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// src/pages/projects/[slug].astro&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// ...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;Page&lt;/span&gt;&lt;span&gt; showTitle&lt;/span&gt;&lt;span&gt; prose&lt;/span&gt;&lt;span&gt; title&lt;/span&gt;&lt;span&gt;={name} &lt;/span&gt;&lt;span&gt;description&lt;/span&gt;&lt;span&gt;={description &lt;/span&gt;&lt;span&gt;??&lt;/span&gt;&lt;span&gt; &apos;&apos;&lt;/span&gt;&lt;span&gt;}&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt; set:html&lt;/span&gt;&lt;span&gt;={description} /&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;no-prose&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;&lt;/span&gt;&lt;span&gt;ProjectLinks&lt;/span&gt;&lt;span&gt; links&lt;/span&gt;&lt;span&gt;={links} /&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;&lt;/span&gt;&lt;span&gt;Prose&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span&gt;Content&lt;/span&gt;&lt;span&gt; /&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;Prose&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;Page&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We now have a fully functioning projects list, along with a singular project page for each repo.
Great!&lt;/p&gt;
&lt;h2&gt;Enriching the data&lt;/h2&gt;
&lt;p&gt;The proof-of-concept is done. Now we can enrich the data a bit more, and maybe update the design. I
also already cached the repository responses into JSON files, so that the process can start over
later without having to re-fetch everything.&lt;/p&gt;
&lt;p&gt;Let’s add some kind of filters to the project page. The easiest win is to add the GitHub link to the
links array:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; getProjectsList&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Promise&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;GitHubProjectSchema&lt;/span&gt;&lt;span&gt;[]&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // ...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; repo&lt;/span&gt;&lt;span&gt; of&lt;/span&gt;&lt;span&gt; repos) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; project&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; GitHubProjectSchema.&lt;/span&gt;&lt;span&gt;parse&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      name: repo.name,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      title: repo.name,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      url: repo.html_url,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      description: repo.description,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      stars: repo.stargazers_count,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      order: &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;repo.stargazers_count,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      // There 👇🏼&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      links: [{ href: repo.html_url, icon: &lt;/span&gt;&lt;span&gt;&apos;logo-github&apos;&lt;/span&gt;&lt;span&gt;, title: &lt;/span&gt;&lt;span&gt;&apos;GitHub&apos;&lt;/span&gt;&lt;span&gt; }],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // ...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, I’ll put my old files in a directory called &lt;code&gt;project-overrides&lt;/code&gt;. We will use this to manually
add override fields to any repo we choose.&lt;/p&gt;
&lt;p&gt;We’ll start by loading the corresponding file:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; fileExists&lt;/span&gt;&lt;span&gt;(overridesFile)) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; content&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; fs.&lt;/span&gt;&lt;span&gt;readFile&lt;/span&gt;&lt;span&gt;(overridesFile, &lt;/span&gt;&lt;span&gt;&apos;utf8&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; allLines&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; content.&lt;/span&gt;&lt;span&gt;toString&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;split&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; lines&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; allLines.&lt;/span&gt;&lt;span&gt;slice&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, content.&lt;/span&gt;&lt;span&gt;lastIndexOf&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;---&apos;&lt;/span&gt;&lt;span&gt;)).&lt;/span&gt;&lt;span&gt;join&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; obj&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; jsYaml.&lt;/span&gt;&lt;span&gt;parse&lt;/span&gt;&lt;span&gt;(lines) &lt;/span&gt;&lt;span&gt;as&lt;/span&gt;&lt;span&gt; GitHubProjectSchema&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then loading the overrides information there:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; link&lt;/span&gt;&lt;span&gt; of&lt;/span&gt;&lt;span&gt; obj.links &lt;/span&gt;&lt;span&gt;??&lt;/span&gt;&lt;span&gt; []) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; found&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; project.links.&lt;/span&gt;&lt;span&gt;findIndex&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; i.href &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; link.href)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (found &lt;/span&gt;&lt;span&gt;&amp;gt;=&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    project.links.&lt;/span&gt;&lt;span&gt;splice&lt;/span&gt;&lt;span&gt;(found, &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, link)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  } &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    project.links.&lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt;(link)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; key&lt;/span&gt;&lt;span&gt; of&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;&apos;title&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;order&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;description&apos;&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;as&lt;/span&gt;&lt;span&gt; const&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (obj[key &lt;/span&gt;&lt;span&gt;as&lt;/span&gt;&lt;span&gt; keyof&lt;/span&gt;&lt;span&gt; typeof&lt;/span&gt;&lt;span&gt; obj] &lt;/span&gt;&lt;span&gt;!=&lt;/span&gt;&lt;span&gt; null&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    project[key &lt;/span&gt;&lt;span&gt;as&lt;/span&gt;&lt;span&gt; keyof&lt;/span&gt;&lt;span&gt; typeof&lt;/span&gt;&lt;span&gt; project] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; obj[key &lt;/span&gt;&lt;span&gt;as&lt;/span&gt;&lt;span&gt; keyof&lt;/span&gt;&lt;span&gt; typeof&lt;/span&gt;&lt;span&gt; obj] &lt;/span&gt;&lt;span&gt;as&lt;/span&gt;&lt;span&gt; never&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I wanted to avoid duplicated links, so I let the overrides take precedence if there are any
conflicts.&lt;/p&gt;
&lt;h3&gt;Filtering out projects&lt;/h3&gt;
&lt;p&gt;I also want to filter out any irrelevant projects. I’ll do that by making a few checks before I push
into the projects array.&lt;/p&gt;
&lt;p&gt;Something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; overridesDir&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; path.&lt;/span&gt;&lt;span&gt;join&lt;/span&gt;&lt;span&gt;(process.&lt;/span&gt;&lt;span&gt;cwd&lt;/span&gt;&lt;span&gt;(), &lt;/span&gt;&lt;span&gt;&apos;src&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;content&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;project-overrides&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; projectIgnoreFile&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; path.&lt;/span&gt;&lt;span&gt;join&lt;/span&gt;&lt;span&gt;(overridesDir, &lt;/span&gt;&lt;span&gt;&apos;.projectignore&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; projectIgnore&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;[] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; []&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;fs.&lt;/span&gt;&lt;span&gt;readFile&lt;/span&gt;&lt;span&gt;(projectIgnoreFile, &lt;/span&gt;&lt;span&gt;&apos;utf8&apos;&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;then&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;content&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  projectIgnore &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; content&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    .&lt;/span&gt;&lt;span&gt;split&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    .&lt;/span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; i.&lt;/span&gt;&lt;span&gt;trim&lt;/span&gt;&lt;span&gt;())&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    .&lt;/span&gt;&lt;span&gt;filter&lt;/span&gt;&lt;span&gt;(Boolean)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;})&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; projectFilter&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;project&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Record&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;any&lt;/span&gt;&lt;span&gt;&amp;gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; boolean&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (projectIgnore.&lt;/span&gt;&lt;span&gt;includes&lt;/span&gt;&lt;span&gt;(project.name)) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; false&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    !&lt;/span&gt;&lt;span&gt;project.fork,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    project.stargazers_count &lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    //&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  ].&lt;/span&gt;&lt;span&gt;every&lt;/span&gt;&lt;span&gt;(Boolean)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can see, I exclude:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Project names listed in the ignore file&lt;/li&gt;
&lt;li&gt;Forks&lt;/li&gt;
&lt;li&gt;Projects without stars&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I can always tweak this further if I need to, but the regular filters got me most of the way, while
I filled the gap with old (but starred) projects in the ignore file.&lt;/p&gt;
&lt;p&gt;Now, I can add it to the main repo loop, simply using &lt;code&gt;continue&lt;/code&gt; to skip the current project:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; getProjectsList&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Promise&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;GitHubProjectSchema&lt;/span&gt;&lt;span&gt;[]&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; repos&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; fetchRepos&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; projects&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; GitHubProjectSchema&lt;/span&gt;&lt;span&gt;[] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; []&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; repo&lt;/span&gt;&lt;span&gt; of&lt;/span&gt;&lt;span&gt; repos) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;projectFilter&lt;/span&gt;&lt;span&gt;(repo)) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      continue&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // ...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    projects.&lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt;(project)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // ...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Marking featured projects&lt;/h3&gt;
&lt;p&gt;Well, that’s easy — I can just add a new property to &lt;code&gt;GitHubProjectSchema&lt;/code&gt;, called &lt;code&gt;featured&lt;/code&gt;.
It’s a simple boolean, and when I want, I can add an override for that field; there shouldn’t be
many of them anyway, and it’s more likely they will already have overrides. Even if not, having a
file with 3 lines isn’t that terrible.&lt;/p&gt;
&lt;p&gt;Let’s update the types:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// src/types.ts&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; const&lt;/span&gt;&lt;span&gt; GitHubProjectSchema&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; z.&lt;/span&gt;&lt;span&gt;object&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  name: z.&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  title: z.&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  description: z.&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;nullable&lt;/span&gt;&lt;span&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  url: z.&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  stars: z.&lt;/span&gt;&lt;span&gt;number&lt;/span&gt;&lt;span&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  readme: z.&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;optional&lt;/span&gt;&lt;span&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  order: z.&lt;/span&gt;&lt;span&gt;number&lt;/span&gt;&lt;span&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  links: z.&lt;/span&gt;&lt;span&gt;array&lt;/span&gt;&lt;span&gt;(LinkSchema),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // here:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  featured: z.&lt;/span&gt;&lt;span&gt;boolean&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;optional&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;default&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;})&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And add the implementation to the overrides loading:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// just add the &apos;featured&apos; property to the list of overridable fields:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; key&lt;/span&gt;&lt;span&gt; of&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;&apos;title&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;order&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;description&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;featured&apos;&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;as&lt;/span&gt;&lt;span&gt; const&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // original logic&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As for changing how featured projects are displayed, that’s up to you. Personally, I partitioned
them into 2 arrays, and had the featured ones at the top with a bit of a difference in the design,
while the other ones were separated to the bottom and also a bit smaller in size.&lt;/p&gt;
&lt;h2&gt;Finishing touches&lt;/h2&gt;
&lt;p&gt;I have the basic structure working well. I could call it a day, but I will also update some styles
in the new pages, re-order the information, stuff like that.&lt;/p&gt;
&lt;p&gt;There are also some other ideas I can think of to improve this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Adding releases links&lt;/li&gt;
&lt;li&gt;Adding license&lt;/li&gt;
&lt;li&gt;Smarter filtering&lt;/li&gt;
&lt;li&gt;Search feature&lt;/li&gt;
&lt;li&gt;Show project tags&lt;/li&gt;
&lt;li&gt;Populate with project URL from the repo info&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But all of these can wait for another day.&lt;/p&gt;
&lt;h2&gt;Wrapping it all up&lt;/h2&gt;
&lt;p&gt;The last thing I need is to make sure it’s easy to update the project files whenever I need to make
an update.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I have a CI that deploys the site on every push to master&lt;/li&gt;
&lt;li&gt;I would like a way to trigger the changes without necessarily making an actual change to a real
file&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I have a few ideas in mind. To manually trigger a rebuild, I could do one of the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use manually triggered GitHub Actions&lt;/li&gt;
&lt;li&gt;Commit some sort of hash file, which will change when needed, causing the build to trigger&lt;/li&gt;
&lt;li&gt;Add the files to gitignore, which will make sure every GitHub Action run goes and fetches the most
up-to-date information&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I think adding the files to gitignore and manually triggering GitHub Actions would solve both of my
issues.&lt;/p&gt;
&lt;p&gt;Updating the workflow is easy; all I have to do is add the &lt;code&gt;workflow_dispatch&lt;/code&gt; event to be able to
manually trigger my deploy action:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# .github/workflows/deploy.yaml&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Deploy&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;on&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  push&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    branches&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      - &lt;/span&gt;&lt;span&gt;master&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  workflow_dispatch&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And to add the files to gitignore:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# .gitignore&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;src/content/project&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Uh-oh&lt;/h3&gt;
&lt;p&gt;Seems there is a major snag.&lt;/p&gt;
&lt;p&gt;In Astro, this all works pretty well in dev mode, but when using the &lt;code&gt;astro build&lt;/code&gt; command, the
newly created files aren’t noticed by the build system (actually not really, I should have noticed I
need an extra refresh after building all the files — it was a hint).&lt;/p&gt;
&lt;p&gt;Apparently, the file list needs to be refreshed, and I don’t think that’s quite possible.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;So… what now?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I decided simpler is better. Let’s add this build step as a prebuild command, separate from Astro.
This will make it work.&lt;/p&gt;
&lt;p&gt;There is one caveat, and that is we can’t use any of Astro’s stuff. So, if I want the types in the
script, I will have to replicate them (and worry about inconsistencies in the future) using my own
zod import, not the one that is bundled with Astro:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// src/github-projects.ts&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; LinkSchema&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; z.&lt;/span&gt;&lt;span&gt;object&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  title: z.&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  href: z.&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  icon: z.&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;})&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; LinkSchema&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; z&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;infer&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;typeof&lt;/span&gt;&lt;span&gt; LinkSchema&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; GitHubProjectSchema&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; z.&lt;/span&gt;&lt;span&gt;object&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  name: z.&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  title: z.&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  description: z.&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;nullable&lt;/span&gt;&lt;span&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  url: z.&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  stars: z.&lt;/span&gt;&lt;span&gt;number&lt;/span&gt;&lt;span&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  readme: z.&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;optional&lt;/span&gt;&lt;span&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  order: z.&lt;/span&gt;&lt;span&gt;number&lt;/span&gt;&lt;span&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  links: z.&lt;/span&gt;&lt;span&gt;array&lt;/span&gt;&lt;span&gt;(LinkSchema),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  featured: z.&lt;/span&gt;&lt;span&gt;boolean&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;optional&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;default&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;})&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; GitHubProjectSchema&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; z&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;infer&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;typeof&lt;/span&gt;&lt;span&gt; GitHubProjectSchema&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Also, I needed to change the way I access env vars:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// from:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; GITHUB_TOKEN&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; import&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;meta&lt;/span&gt;&lt;span&gt;.env.&lt;/span&gt;&lt;span&gt;GH_TOKEN&lt;/span&gt;&lt;span&gt; ||&lt;/span&gt;&lt;span&gt; import&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;meta&lt;/span&gt;&lt;span&gt;.env.&lt;/span&gt;&lt;span&gt;GITHUB_TOKEN&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// to:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; GITHUB_TOKEN&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; process.env.&lt;/span&gt;&lt;span&gt;GH_TOKEN&lt;/span&gt;&lt;span&gt; ||&lt;/span&gt;&lt;span&gt; process.env.&lt;/span&gt;&lt;span&gt;GITHUB_TOKEN&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And then I created a standalone script to run this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { generateProjectsList } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;../github-projects&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;generateProjectsList&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And made it run on pre-build in &lt;code&gt;package.json&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;scripts&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;prebuild&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;tsx src/scripts/generate-projects.ts&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I also removed the generate call from the astro pages.&lt;/p&gt;
&lt;p&gt;And now it works!&lt;/p&gt;
&lt;p&gt;I have also released this as a package. It has been updated and you can have more control over the
filters, and it generally works better as it integrates directly into Astro.
&lt;a href=&quot;http://npmjs.com/package/github-repos-astro-loader&quot;&gt;Check it out&lt;/a&gt;, or install right now using:&lt;/p&gt;
&lt;h4&gt;npm&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;npm&lt;/span&gt;&lt;span&gt; i&lt;/span&gt;&lt;span&gt; github-repos-astro-loader&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;pnpm&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; i&lt;/span&gt;&lt;span&gt; github-repos-astro-loader&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Yarn&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;yarn&lt;/span&gt;&lt;span&gt; i&lt;/span&gt;&lt;span&gt; github-repos-astro-loader&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;That’s it! You can take a look at my &lt;a href=&quot;/projects&quot;&gt;Projects page&lt;/a&gt; to see how it turned out.&lt;/p&gt;
&lt;p&gt;While it’s not perfect, I think it’s an interesting direction to take, and there are many
possibilities for improving how and what to display. Have you done anything similar for yourself?
How do you make sure to keep your projects up to date? Leave a comment below telling us more.&lt;/p&gt;</content:encoded></item><item><title>Pathfinding Guide for 2D Top-View Tiles in Godot 4.3</title><link>https://casraf.dev/2024/09/pathfinding-guide-for-2d-top-view-tiles-in-godot-4-3/</link><guid isPermaLink="true">https://casraf.dev/2024/09/pathfinding-guide-for-2d-top-view-tiles-in-godot-4-3/</guid><pubDate>Tue, 03 Sep 2024 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Making your enemies run after your player character is a common task in 2D games. If you are using
Godot and having trouble implementing pathfinding while respecting your TileSet collisions, you’ve
come to the right place!&lt;/p&gt;
&lt;p&gt;I &lt;strong&gt;love&lt;/strong&gt; Godot. It’s a really good game engine. It has its flaws, sure; but it’s very flexible,
it’s designed to give the developer a lot of control, and it’s still very easy to use. That said, 2D
top-down navigation on TileMaps has proven to be a difficult task to research and plan correctly.&lt;/p&gt;
&lt;p&gt;In this tutorial we will learn how to implement pathfinding, in a flexible and portable way, with
avoiding obstacle tiles being the main focus point which was a pain for me.&lt;/p&gt;
&lt;h2&gt;Motivation&lt;/h2&gt;
&lt;p&gt;Godot 4.3 introduced a lot of new features. Among them are &lt;code&gt;TileMapLayers&lt;/code&gt; nodes, which replace the
old &lt;code&gt;TileMap&lt;/code&gt; node’s layers property. This isn’t actually part of the problem; they mostly work the
same as before.&lt;/p&gt;
&lt;p&gt;The problem is, I was under the impression that if I give some tiles collision information, it would
be used by the pathfinding and be avoided by the moving actors. That is not the case!&lt;/p&gt;
&lt;p&gt;I actually got the most useful information in understanding how to use pathfinding by watching
&lt;a href=&quot;https://www.youtube.com/watch?v=7ZAF_fn3VOc&quot;&gt;this video&lt;/a&gt; by
&lt;a href=&quot;https://www.youtube.com/@CodingQuests&quot;&gt;Coding Quests&lt;/a&gt; on YouTube. His video provides a great basis,
and I recommend you watch it.&lt;/p&gt;
&lt;p&gt;This tutorial will do much of the same, but I will try to give you a more flexible way to handle
obstacle tiles, so that it is more scalable for your game, and easier to manage and expand on.&lt;/p&gt;
&lt;h2&gt;Starting set-up&lt;/h2&gt;
&lt;p&gt;First thing’s first, we need to create some structure and understand how the nodes interact.&lt;/p&gt;
&lt;p&gt;Let’s assume we have the following scene tree (I only included relevant nodes for brevity):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;SceneBase (Node2D)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;├── GroundLayer (TileMapLayer)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;├── ActorLayer (TileMapLayer)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   ├── Player (CharacterBody2D)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   └── Enemy (CharacterBody2D)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;├── Trees (TileMapLayer)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;└── Rocks (TileMapLayer)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is the base starting point. We have a ground layer, which has tiles like grass and dirt. We
have an actors layer, inside it, we have two actors: the player and an enemy. We want the enemy to
chase the player around, finding their path and avoiding blocked tiles on the way.&lt;/p&gt;
&lt;p&gt;Below those, we have 2 more layers just so we can make sure our pathfinding is working across
multiple layers automatically in a more generic, less constrained way than having to manually link
them for each map scene.&lt;/p&gt;
&lt;h3&gt;Preparing the TileSets&lt;/h3&gt;
&lt;p&gt;For any navigable TileSets you plan to use, you will need to add navigation support to it, to allow
pathfinding to work.&lt;/p&gt;
&lt;p&gt;To do that, first, find one of the layers that uses the desired TileSet, and expand the TileSet’s
details.&lt;/p&gt;
&lt;p&gt;Then, expand the “Navigation Layers” section, and add a layer with the bitmask of your choice (just
leave at the default if you’re unsure).&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Once that’s done, go to the TileSet editor. Go to the “Paint” tab, and select a walkable tile, such
as grass or dirt.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;From “Paint Properties”, under “Navigation”, select the navigation layer you created (it should be
the only one).&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;You can start painting on a few more tiles as you see fit. Any tile you mark will be &lt;strong&gt;walkable&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;Implementing pathfinding&lt;/h2&gt;
&lt;p&gt;Now that the basic setup is out of the way, we can carry on. Pathfinding isn’t too tricky to
implement in this case for just the base.&lt;/p&gt;
&lt;p&gt;First, let’s add a child node to the enemy, of type &lt;code&gt;NavigationAgent2D&lt;/code&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt; You may also create a
nav node via code, or use an &lt;code&gt;@export var&lt;/code&gt; as alternate methods to get a navigation node; but
we’ll keep it simple in this tutorial.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Then, let’s attach a (or modify our existing) script for the enemy.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;extends&lt;/span&gt;&lt;span&gt; CharacterBody2D&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;class_name&lt;/span&gt;&lt;span&gt; Enemy&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; speed &lt;/span&gt;&lt;span&gt;:=&lt;/span&gt;&lt;span&gt; 50.0&lt;/span&gt;&lt;span&gt; # can be anything&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;@onready&lt;/span&gt;&lt;span&gt; var&lt;/span&gt;&lt;span&gt; nav: &lt;/span&gt;&lt;span&gt;NavigationAgent2D&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; $&lt;/span&gt;&lt;span&gt;NavigationAgent2D&lt;/span&gt;&lt;span&gt; # change if needed&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;@onready&lt;/span&gt;&lt;span&gt; var&lt;/span&gt;&lt;span&gt; player: &lt;/span&gt;&lt;span&gt;CharacterBody2D&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; %&lt;/span&gt;&lt;span&gt;Player&lt;/span&gt;&lt;span&gt; # change if needed&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;func&lt;/span&gt;&lt;span&gt; _ready&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; void&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	actor_setup.&lt;/span&gt;&lt;span&gt;call_deferred&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	nav.velocity_computed.&lt;/span&gt;&lt;span&gt;connect&lt;/span&gt;&lt;span&gt;(_velocity_computed)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;func&lt;/span&gt;&lt;span&gt; actor_setup&lt;/span&gt;&lt;span&gt;():&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	# Wait for the first physics frame so the NavigationServer can sync.&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	await&lt;/span&gt;&lt;span&gt; get_tree&lt;/span&gt;&lt;span&gt;().physics_frame&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	# Now that the navigation map is no longer empty, set the movement target.&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	set_movement_target&lt;/span&gt;&lt;span&gt;(player.position)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;func&lt;/span&gt;&lt;span&gt; set_movement_target&lt;/span&gt;&lt;span&gt;(movement_target: &lt;/span&gt;&lt;span&gt;Vector2&lt;/span&gt;&lt;span&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	nav.target_position &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; movement_target&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;func&lt;/span&gt;&lt;span&gt; _physics_process&lt;/span&gt;&lt;span&gt;(delta: &lt;/span&gt;&lt;span&gt;float&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; void&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	_move_towards_player&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;func&lt;/span&gt;&lt;span&gt; _move_towards_player&lt;/span&gt;&lt;span&gt;():&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  pass&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Before we implement the actual movement function, let’s go over what we have added so far.&lt;/p&gt;
&lt;p&gt;In the &lt;code&gt;_ready()&lt;/code&gt; function, we do 2 things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Call the &lt;code&gt;actor_setup&lt;/code&gt; function so the &lt;code&gt;nav&lt;/code&gt; node is ready. We use &lt;code&gt;call_deferred()&lt;/code&gt; to make it
wait for the next frame before it is invoked. This assures us that the &lt;code&gt;nav&lt;/code&gt; node is ready.&lt;/li&gt;
&lt;li&gt;We connect the &lt;code&gt;velocity_computed&lt;/code&gt; signal so we can get the &lt;code&gt;nav&lt;/code&gt; node’s velocity for avoidance
(if enabled).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The &lt;code&gt;actor_setup&lt;/code&gt; function also does a couple of things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Waits for 1 physics frame to wait for the &lt;code&gt;nav&lt;/code&gt; agent to be ready&lt;/li&gt;
&lt;li&gt;Sets the navigator’s target position to the player position&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;What does avoidance do?&lt;/h3&gt;
&lt;p&gt;What is avoidance, you ask? It is a feature of the NavigationAgent2D that allows it to avoid other
navigation agents. You can define a radius to circle around agents on your path, and the path will
be recalculated to avoid it. However, it does not relate to collision tiles or static obstacles. We
will handle that a bit later on.&lt;/p&gt;
&lt;h3&gt;Implementing the movement logic&lt;/h3&gt;
&lt;p&gt;Getting back on topic, we will go ahead and implement the logic to move the actor using the
navigation agent.&lt;/p&gt;
&lt;p&gt;This is most of the logic involving the actual navigation agent, so let’s get going.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;func&lt;/span&gt;&lt;span&gt; _move_towards_player&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; void&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	# Update the player position&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	set_movement_target&lt;/span&gt;&lt;span&gt;(player.position)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	# If we&apos;re at the target, stop&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	if&lt;/span&gt;&lt;span&gt; nav.&lt;/span&gt;&lt;span&gt;is_navigation_finished&lt;/span&gt;&lt;span&gt;():&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		return&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	# Get pathfinding information&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	var&lt;/span&gt;&lt;span&gt; current_agent_position: &lt;/span&gt;&lt;span&gt;Vector2&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; global_position&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	var&lt;/span&gt;&lt;span&gt; next_path_position: &lt;/span&gt;&lt;span&gt;Vector2&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; nav.&lt;/span&gt;&lt;span&gt;get_next_path_position&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	# Calculate the new velocity&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	var&lt;/span&gt;&lt;span&gt; new_velocity &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; current_agent_position.&lt;/span&gt;&lt;span&gt;direction_to&lt;/span&gt;&lt;span&gt;(next_path_position) &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; speed&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	# Set correct velocity&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	if&lt;/span&gt;&lt;span&gt; nav.avoidance_enabled:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		nav.&lt;/span&gt;&lt;span&gt;set_velocity&lt;/span&gt;&lt;span&gt;(new_velocity)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	else&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		_velocity_computed&lt;/span&gt;&lt;span&gt;(new_velocity)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	# Do the movement&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	move_and_slide&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;func&lt;/span&gt;&lt;span&gt; _velocity_computed&lt;/span&gt;&lt;span&gt;(safe_velocity: &lt;/span&gt;&lt;span&gt;Vector2&lt;/span&gt;&lt;span&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	velocity &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; safe_velocity&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The comments should help you understand what we do on each section, but I want to explain a bit more
about the avoidance lines:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;func&lt;/span&gt;&lt;span&gt; _move_towards_player&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; void&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	# ...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	# Set correct velocity&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	if&lt;/span&gt;&lt;span&gt; nav.avoidance_enabled:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		nav.&lt;/span&gt;&lt;span&gt;set_velocity&lt;/span&gt;&lt;span&gt;(new_velocity)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	else&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		_velocity_computed&lt;/span&gt;&lt;span&gt;(new_velocity)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	# ...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;func&lt;/span&gt;&lt;span&gt; _velocity_computed&lt;/span&gt;&lt;span&gt;(safe_velocity: &lt;/span&gt;&lt;span&gt;Vector2&lt;/span&gt;&lt;span&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	velocity &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; safe_velocity&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the nav’s avoidance is enabled, we don’t directly set the velocity of the actor. We instead pass
the calculated velocity to the nav agent itself. The agent will use it to handle the next
calculations in order to avoid other nav agents, and report back when it has a new “safer” path.&lt;/p&gt;
&lt;p&gt;When the &lt;code&gt;_velocity_computed&lt;/code&gt; function is called (either by us or by the &lt;code&gt;velocity_computed&lt;/code&gt; signal
of the nav), we simply set the velocity of the actor. Avoidance will make sure to call the
&lt;code&gt;velocity_computed&lt;/code&gt; when necessary, which is why we don’t manually set it in the case it is enabled.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt; It’s important to
note that only &lt;code&gt;NavigationAgent2D&lt;/code&gt; are considered for avoidance, not any obstacle. For that, just
continue reading.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Testing our setup&lt;/h3&gt;
&lt;p&gt;This setup should work for the initial test. Just run the game and see if it works.&lt;/p&gt;
&lt;p&gt;To make it more visual, you can go to the navigation agent of the enemy, and check the “Debug”
property; which will show us the paths the enemy is trying to follow. Also, in Godot’s top menu,
under “Debug” -&amp;gt; check “Visible Navigation”, which will highlight which tiles are used as possible
paths and which aren’t.&lt;/p&gt;
&lt;p&gt;Here’s how it looks right now:&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Ok, that works nicely. How would we go about removing obstacle tiles, though?&lt;/p&gt;
&lt;h2&gt;Obstacle tiles&lt;/h2&gt;
&lt;p&gt;Some tiles, such as rocks and trees, are meant to be obstacles which block your (and others’) path.
But unfortunately (and this is an area where Godot can definitely improve), linking the navigation
area to tile collisions isn’t possible out of the box without some code.&lt;/p&gt;
&lt;p&gt;Let’s see how we need to implement that.&lt;/p&gt;
&lt;h3&gt;Preparing the TileSet&lt;/h3&gt;
&lt;p&gt;One thing we need is to set some collision on the relevant tiles.&lt;/p&gt;
&lt;p&gt;In your TileSet’s inspector, add a Physics layer, similar to how we added a Navigation layer:&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Then, once again, go to the TileSet panel, click “Paint”, but this time, under “Physics Layers”
choose the layer you just added:&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Now when you paint tiles you will make them block your path and your enemies’, if they try to walk
through it.&lt;/p&gt;
&lt;p&gt;They &lt;strong&gt;will&lt;/strong&gt; still try to walk through these tiles, because the navigation isn’t aware of the
physics there. This is where we do a little bit of magic.&lt;/p&gt;
&lt;p&gt;To your ground layer, attach a new script. Call it whatever you like, I picked
&lt;code&gt;NavigationTileLayer&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;In this script we need to do a few things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;We need to get information about all the relevant layers (ones that contain obstacles)&lt;/li&gt;
&lt;li&gt;We need to tell the navigation system that any tiles with collision should be removed from the
nav area&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In order to do that, we’ll write a little bit of code. Not much more, I promise!&lt;/p&gt;
&lt;p&gt;To get the information on relevant layers, there are many possibilities:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Put them all in the same group, and use &lt;code&gt;get_tree().get_nodes_in_group()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Find any sibling nodes with the &lt;code&gt;TileMapLayer&lt;/code&gt; type&lt;/li&gt;
&lt;li&gt;Use an &lt;code&gt;@export var&lt;/code&gt; and manually put the relevant layers in there (less recommended)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To make it super simple, I think we’ll go with the first option.&lt;/p&gt;
&lt;p&gt;First, select your &lt;code&gt;TileMapLayer&lt;/code&gt;s in the scene. Then in the inspector pane, click the “Node” tab,
then “Groups”.&lt;/p&gt;
&lt;p&gt;Simply add a new group with the ”+” button:&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;and add them to the &lt;code&gt;Obstacles&lt;/code&gt; group (change the name if you want but just make sure to also change
it in the script).&lt;/p&gt;
&lt;p&gt;This is how it will look when added (it’s your choice whether to make this group global or scene
local):&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Great, now let’s add a function for that in our script:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;extends&lt;/span&gt;&lt;span&gt; TileMapLayer&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; _obstacles: &lt;/span&gt;&lt;span&gt;Array&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;TileMapLayer&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; []&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;func&lt;/span&gt;&lt;span&gt; _ready&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; void&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	_get_obstacle_layers&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;func&lt;/span&gt;&lt;span&gt; _get_obstacle_layers&lt;/span&gt;&lt;span&gt;():&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	# make sure the name here is the same as the group&apos;s&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	var&lt;/span&gt;&lt;span&gt; layers &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; get_tree&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;get_nodes_in_group&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Obstacles&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	for&lt;/span&gt;&lt;span&gt; layer &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; layers:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		if&lt;/span&gt;&lt;span&gt; layer &lt;/span&gt;&lt;span&gt;is&lt;/span&gt;&lt;span&gt; not&lt;/span&gt;&lt;span&gt; TileMapLayer&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;continue&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		_obstacles.&lt;/span&gt;&lt;span&gt;append&lt;/span&gt;&lt;span&gt;(layer)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ok, great! Now the next step is to remove navigation from collision tiles.&lt;/p&gt;
&lt;p&gt;We will need a couple of things for that. First, we need to implement the
&lt;code&gt;_use_tile_data_runtime_update&lt;/code&gt; function.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;func&lt;/span&gt;&lt;span&gt; _use_tile_data_runtime_update&lt;/span&gt;&lt;span&gt;(coords: &lt;/span&gt;&lt;span&gt;Vector2i&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; bool&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	return&lt;/span&gt;&lt;span&gt; _is_used_by_obstacle&lt;/span&gt;&lt;span&gt;(coords)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This function is an override for a built-in for &lt;code&gt;TileMapLayer&lt;/code&gt;. It should return a bool, which
represents whether the cell at &lt;code&gt;coords&lt;/code&gt; needs to have its data updated in runtime.&lt;/p&gt;
&lt;p&gt;It makes sure we don’t run excess updates on every cell and only update what we need. You probably
noticed I added a stub &lt;code&gt;_is_used_by_obstacle&lt;/code&gt; function there, let’s implement that now:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;func&lt;/span&gt;&lt;span&gt; _is_used_by_obstacle&lt;/span&gt;&lt;span&gt;(coords: &lt;/span&gt;&lt;span&gt;Vector2i&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; bool&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	for&lt;/span&gt;&lt;span&gt; layer &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; _obstacles:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		if&lt;/span&gt;&lt;span&gt; coords &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; layer.&lt;/span&gt;&lt;span&gt;get_used_cells&lt;/span&gt;&lt;span&gt;():&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			var&lt;/span&gt;&lt;span&gt; is_obstacle &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; layer.&lt;/span&gt;&lt;span&gt;get_cell_tile_data&lt;/span&gt;&lt;span&gt;(coords).&lt;/span&gt;&lt;span&gt;get_collision_polygons_count&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			if&lt;/span&gt;&lt;span&gt; is_obstacle:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				return&lt;/span&gt;&lt;span&gt; true&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	return&lt;/span&gt;&lt;span&gt; false&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Our logic here is pretty simple:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Iterate over every layer in &lt;code&gt;_obstacles&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;For each layer, check if the coords correspond to a cell
&lt;ul&gt;
&lt;li&gt;If there is a cell in that layer on those coords, check if it is an obstacle:
&lt;ul&gt;
&lt;li&gt;We do the check by getting the tile data, and retrieving the collision polygons count for it&lt;/li&gt;
&lt;li&gt;If it has more than 0 (i.e., it has a collision shape), it is an obstacle&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;0&lt;/code&gt; we pass to the function is the index of the physics layer we created on the TileSet,
so if you had previous layers, this is what you would want to change&lt;/li&gt;
&lt;li&gt;Return &lt;code&gt;true&lt;/code&gt; if it is an obstacle&lt;/li&gt;
&lt;li&gt;Continue otherwise&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;If no obstacles were found and we reached this point, then we return &lt;code&gt;false&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It’s an efficient way to attach the information from the &lt;code&gt;TileMapLayer&lt;/code&gt; into the navigation area. We
don’t need any custom data layer or any additional setup for the tiles other than the collision
(which we would want anyway, so our player would be blocked).&lt;/p&gt;
&lt;p&gt;Now for the last piece of the puzzle, we just need to actually set the navigation polygon on the
tile to accommodate to the walkable state of the tile. Add a new method on the script:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;func&lt;/span&gt;&lt;span&gt; _tile_data_runtime_update&lt;/span&gt;&lt;span&gt;(coords: &lt;/span&gt;&lt;span&gt;Vector2i&lt;/span&gt;&lt;span&gt;, tile_data: &lt;/span&gt;&lt;span&gt;TileData&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; void&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	if&lt;/span&gt;&lt;span&gt; not&lt;/span&gt;&lt;span&gt; _is_used_by_obstacle&lt;/span&gt;&lt;span&gt;(coords):&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		tile_data.&lt;/span&gt;&lt;span&gt;set_navigation_polygon&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is another built-in method in Godot that will only run on the tiles that have returned true
inside the previous &lt;code&gt;_use_tile_data_runtime_update&lt;/code&gt; method. It will only try to update the tiles
that are relevant, in order to save on memory. Since it will only run on tiles we already queried
that are not walkable, it won’t try to update every tile.&lt;/p&gt;
&lt;h3&gt;Last test&lt;/h3&gt;
&lt;p&gt;Let’s run the game one more time - we can see that any tiles that contain collision are not
highlighted in the navigation debug. We can also notice our agent finding ways around the obstacles.
Success!&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I believe this is a pretty flexible system. Basically, if we need to add more TileSets or tiles to
the same set, we just need to make sure of two things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The layer is grouped (or otherwise connected to the &lt;code&gt;NavigationTileLayer&lt;/code&gt; script’s obstacles)&lt;/li&gt;
&lt;li&gt;We have collision polygons on the required tiles&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Other than those, no special set up is required. This means you can essentially put the script
anywhere, even on a completely separate node, and re-use that same node across all your maps with
minimal effort. And if you don’t want to use groups, you can find other ways to get the correct
layers to use.&lt;/p&gt;
&lt;p&gt;Hopefully this tutorial helps you understand a bit more about the &lt;code&gt;TileMapLayer&lt;/code&gt; nodes, about
&lt;code&gt;NavigationAgent2D&lt;/code&gt;, and about how to use tile data to your advantage. Personally, I had trouble
finding resources that properly explain how to handle 2d obstacles using pathfinding, so I thought
it could be a nice write-up for others to learn from.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Do you have ideas for improvement on this system? Do you have any questions? Please leave a
comment!&lt;/strong&gt;&lt;/p&gt;</content:encoded></item><item><title>Distribute Open-Source Tools with Homebrew Taps: A Beginner&apos;s Guide</title><link>https://casraf.dev/2025/01/distribute-open-source-tools-with-homebrew-taps-a-beginners-guide/</link><guid isPermaLink="true">https://casraf.dev/2025/01/distribute-open-source-tools-with-homebrew-taps-a-beginners-guide/</guid><pubDate>Mon, 27 Jan 2025 08:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Often, when I create open-source tools or apps, it’s tricky to distribute them easily. You usually
need to specify where to download from, and where to place the extracted files, which is a manual
process that doesn’t usually translate into a simple script.&lt;/p&gt;
&lt;p&gt;That’s where Homebrew comes in. It makes it easy to package your tools in a way that works on both
macOS and Linux and installs them for you. Homebrew taps are user-created repositories that simplify
distribution. Instead of asking your users to manually download and configure files, you can provide
them with a single command to install your tool. This seamless experience can make your app more
approachable for users and easier for you to manage.&lt;/p&gt;
&lt;p&gt;There are plenty of package managers – but I think Homebrew is one of the simpler ones to
publish for, believe it or not, which aren’t tied to a specific development language or platform
(such as Node’s NPM, or Dart Pub).&lt;/p&gt;
&lt;p&gt;In this post, we’ll go over how to get your own tap up and running, without having to rely on the
core repository, or having to fulfill the minimum requirements to have your app uploaded to it.&lt;/p&gt;
&lt;h2&gt;What are Homebrew taps?&lt;/h2&gt;
&lt;p&gt;A tap is Homebrew’s term for user-made repositories of software, separate from the main Homebrew
repository.&lt;/p&gt;
&lt;p&gt;Without having to be verified by Homebrew’s team, and without the minimum standards (your software’s
popularity, for example), you can publish your apps on your own repository, and easily point your
users to install using 1 or 2 lines of shell code.&lt;/p&gt;
&lt;p&gt;For example, to use my own tap, you can either add it as a repository to Homebrew:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;brew&lt;/span&gt;&lt;span&gt; tap&lt;/span&gt;&lt;span&gt; chenasraf/tap&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And then install a package:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;brew&lt;/span&gt;&lt;span&gt; install&lt;/span&gt;&lt;span&gt; sofmani&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or, you can directly install from the tap without adding it:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;brew&lt;/span&gt;&lt;span&gt; install&lt;/span&gt;&lt;span&gt; chenasraf/tap/sofmani&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So, how can you make this work for your own apps?&lt;/p&gt;
&lt;h2&gt;Step 1: Setting up a tap repository on GitHub&lt;/h2&gt;
&lt;p&gt;First thing’s first, you need a GitHub repository to hold your tap. The first requirement is
properly naming your repository.&lt;/p&gt;
&lt;p&gt;If you prefix the name of your repository with &lt;code&gt;homebrew-&lt;/code&gt;, the rest of the name becomes your tap
name, which will be under your username.&lt;/p&gt;
&lt;p&gt;For example, for my sample above, the repository I created was named &lt;code&gt;chenasraf/homebrew-tap&lt;/code&gt;, which
then translates into &lt;code&gt;chenasraf/tap&lt;/code&gt; for brew.&lt;/p&gt;
&lt;p&gt;I suggest a similar naming scheme. I personally don’t think it’s a good idea to have more than 1
main tap, as publishing and maintaining multiple repositories might prove to be a hassle. Also, if
all your apps are accessible under the same tap, you can direct your users more easily and have them
install with less possible commands and actions.&lt;/p&gt;
&lt;h2&gt;Step 2: Adding GitHub Actions&lt;/h2&gt;
&lt;p&gt;If you’re not familiar, GitHub Actions are a way to run automatic processes on your repository, in
the server, which also allows you to report success or failure and block PRs or have more actions
done based on the results. It’s often used to automate testing and releasing software on GitHub
whenever something is pushed to a repository or if it fills certain criteria.&lt;/p&gt;
&lt;p&gt;Homebrew supplies us with a few GitHub actions to help us with the publishing process. These, in
addition to publishing, run some tests and pre-requisites to make sure everything is set up properly
and that your tap will be accessible and usable without errors.&lt;/p&gt;
&lt;p&gt;The first action is the brew test bot pipeline. This action will run some tests whenever you push to
your repository, making sure test run and files are formatted properly. This action also creates
Bottles for your app for various platforms. We will discuss Bottles in just a bit.&lt;/p&gt;
&lt;p&gt;To add this action, you can paste this into &lt;code&gt;.github/workflows/test.yml&lt;/code&gt;:&lt;/p&gt;
&lt;h3&gt;Test Action&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;brew test-bot&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;on&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  push&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    branches&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      - &lt;/span&gt;&lt;span&gt;master&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  pull_request&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;jobs&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  test-bot&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    strategy&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      matrix&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        os&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span&gt;ubuntu-22.04&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;macos-13&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;macos-14&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    runs-on&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;${{ matrix.os }}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    steps&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      - &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Set up Homebrew&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        id&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;set-up-homebrew&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        uses&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Homebrew/actions/setup-homebrew@master&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      - &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Cache Homebrew Bundler RubyGems&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        uses&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;actions/cache@v4&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        with&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          path&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;${{ steps.set-up-homebrew.outputs.gems-path }}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          key&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;${{ matrix.os }}-rubygems-${{ steps.set-up-homebrew.outputs.gems-hash }}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          restore-keys&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;${{ matrix.os }}-rubygems-&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      - &lt;/span&gt;&lt;span&gt;run&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;brew test-bot --only-cleanup-before&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      - &lt;/span&gt;&lt;span&gt;run&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;brew test-bot --only-setup&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      - &lt;/span&gt;&lt;span&gt;run&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;brew test-bot --only-tap-syntax&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      - &lt;/span&gt;&lt;span&gt;run&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;brew test-bot --only-formulae&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        if&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;github.event_name == &apos;pull_request&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      - &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Upload bottles as artifact&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        if&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;always() &amp;amp;&amp;amp; github.event_name == &apos;pull_request&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        uses&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;actions/upload-artifact@v4&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        with&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;bottles_${{ matrix.os }}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          path&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;*.bottle.*&apos;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A brief overview of the above:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We run this job on 3 different platforms, Ubuntu 22, and macOS 13 and 14.&lt;/li&gt;
&lt;li&gt;We set up Homebrew, and caching to quicken the process&lt;/li&gt;
&lt;li&gt;We run some of brew’s test-bot functionality, which includes making some tests and building the
binaries for each of the matrix platforms&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now every push to your repository will run the tests, and create bottles for your app. These bottles
will be re-used when you release the next version of your app.&lt;/p&gt;
&lt;h3&gt;Publish Action&lt;/h3&gt;
&lt;p&gt;The next action is where the magic happens. Paste the following into
&lt;code&gt;.github/workflows/publish.yml&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;brew pr-pull&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;on&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  pull_request_target&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    types&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      - &lt;/span&gt;&lt;span&gt;labeled&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;jobs&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  pr-pull&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;contains(github.event.pull_request.labels.*.name, &apos;pr-pull&apos;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    runs-on&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;ubuntu-22.04&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    permissions&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      contents&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;write&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      pull-requests&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;write&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    steps&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      - &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Set up Homebrew&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        uses&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Homebrew/actions/setup-homebrew@master&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      - &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Set up git&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        uses&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Homebrew/actions/git-user-config@master&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      - &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Pull bottles&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        env&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          HOMEBREW_GITHUB_API_TOKEN&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;${{ github.token }}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          PULL_REQUEST&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;${{ github.event.pull_request.number }}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        run&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;brew pr-pull --debug --tap=&quot;$GITHUB_REPOSITORY&quot; &quot;$PULL_REQUEST&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      - &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Push commits&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        uses&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Homebrew/actions/git-try-push@master&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        with&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          token&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;${{ github.token }}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          branch&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;master&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      - &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Delete branch&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        if&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;github.event.pull_request.head.repo.fork == false&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        env&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          BRANCH&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;${{ github.event.pull_request.head.ref }}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        run&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;git push --delete origin &quot;$BRANCH&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This action will create a release for you when your app is ready to be released.&lt;/p&gt;
&lt;p&gt;A brief overview of the above:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We set up homebrew &amp;amp; git&lt;/li&gt;
&lt;li&gt;We pull the previously-created bottles, so we can use them in this action&lt;/li&gt;
&lt;li&gt;We tell Homebrew to push new commits containing the downloaded bottles, and previous existing
commits&lt;/li&gt;
&lt;li&gt;We then delete the source branch&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now, push your changes to your repository, and then create a new branch. This branch will be used to
create a PR that will be the release PR for your app.&lt;/p&gt;
&lt;h2&gt;Step 3: Creating your first Formula&lt;/h2&gt;
&lt;p&gt;In Homebrew, “Formulae” (plural) or “Formula” (singular) are the actual individual app installers. A
formula tells brew where to fetch your code from, and how to install it into the user’s system.&lt;/p&gt;
&lt;p&gt;Each of your apps should have a formula. For our example, I will show you how to create a build for
a Go app – but this can be modified as needed for any other type of app.&lt;/p&gt;
&lt;p&gt;For this, you will need a tagged release for your own app, whatever it is written in. For our
example, we can reference
&lt;a href=&quot;https://github.com/chenasraf/sofmani/releases/tag/v1.4.0&quot;&gt;sofmani v1.4.0&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;A few notes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A downloadable release is not required – we will be instructing Homebrew to install from the
source tar file which we will fetch.&lt;/li&gt;
&lt;li&gt;A tag is not required, any ref can work, such as a branch or commit hash – but it’s
recommended to use a tag to make it easy to follow the correct version.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Creating a formula file&lt;/h3&gt;
&lt;p&gt;To create a new formula file, you can use the &lt;code&gt;brew create&lt;/code&gt; command. This will create a new formula
template that you will have to modify.&lt;/p&gt;
&lt;p&gt;The command should be run as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;brew&lt;/span&gt;&lt;span&gt; create&lt;/span&gt;&lt;span&gt; &amp;lt;&lt;/span&gt;&lt;span&gt;source&lt;/span&gt;&lt;span&gt; tar.gz&lt;/span&gt;&lt;span&gt; UR&lt;/span&gt;&lt;span&gt;L&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To use our example from before, in our case we will use:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;brew&lt;/span&gt;&lt;span&gt; create&lt;/span&gt;&lt;span&gt; https://github.com/chenasraf/sofmani/archive/refs/tags/v1.4.0.tar.gz&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will start a prompt that will ask for some details about your app. Fill them up and you will be
brought into your shell’s editor to edit the formula file. Your file should be located at
&lt;code&gt;/usr/local/Homebrew/Library/Taps/homebrew/homebrew-core/Formula/&amp;lt;first-letter-of-name&amp;gt;/&amp;lt;app-name&amp;gt;.rb&lt;/code&gt;,
or a similar directory somewhere else depending on your Homebrew setup.&lt;/p&gt;
&lt;p&gt;Take note of your file and move it into your repository. In the state it’s currently in, it is meant
to be pushed to your fork of Homebrew, so that you can create an official PR to add it to the core
repository – but for our case, we are only publishing this for our own repository.&lt;/p&gt;
&lt;p&gt;Your file should be placed in the &lt;code&gt;Formula&lt;/code&gt; directory of the repository. To make it easier to sort a
lot of apps, you can divide your formulae to separate directories, where each directory is the first
letter of the apps below it.&lt;/p&gt;
&lt;p&gt;As an example, for our case we can use &lt;code&gt;Formula/s/sofmani.rb&lt;/code&gt; or just &lt;code&gt;Formula/sofmani.rb&lt;/code&gt;. I chose
the latter because I don’t think I am close to having enough apps there to require more splitting
up.&lt;/p&gt;
&lt;h3&gt;Updating the file&lt;/h3&gt;
&lt;p&gt;Now that we have a file in place, it should look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# Documentation: https://docs.brew.sh/Formula-Cookbook&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;#                https://rubydoc.brew.sh/Formula&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# PLEASE REMOVE ALL GENERATED COMMENTS BEFORE SUBMITTING YOUR PULL REQUEST!&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; Sofmani&lt;/span&gt;&lt;span&gt; &amp;lt; &lt;/span&gt;&lt;span&gt;Formula&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  desc &lt;/span&gt;&lt;span&gt;&quot;Installs software from a declerative config on any system &quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  homepage &lt;/span&gt;&lt;span&gt;&quot;https://chenasraf.github.io/sofmani/&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  url &lt;/span&gt;&lt;span&gt;&quot;https://github.com/chenasraf/sofmani/archive/refs/tags/v1.4.0.tar.gz&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  sha256 &lt;/span&gt;&lt;span&gt;&quot;0cb4d1f51b23f272fd8f5146e30f937e7cd9ff1275fbcc732a6f27e017c0426a&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  license &lt;/span&gt;&lt;span&gt;&quot;MIT&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  # depends_on &quot;cmake&quot; =&amp;gt; :build&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  def&lt;/span&gt;&lt;span&gt; install&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    # Remove unrecognized options if they cause configure to fail&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    # https://rubydoc.brew.sh/Formula.html#std_configure_args-instance_method&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    system&lt;/span&gt;&lt;span&gt; &quot;./configure&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;--disable-silent-rules&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;std_configure_args&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    # system &quot;cmake&quot;, &quot;-S&quot;, &quot;.&quot;, &quot;-B&quot;, &quot;build&quot;, *std_cmake_args&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  test&lt;/span&gt;&lt;span&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    # `test do` will create, run in and delete a temporary directory.&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    #&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    # This test will fail and we won&apos;t accept that! For Homebrew/homebrew-core&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    # this will need to be a test that verifies the functionality of the&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    # software. Run the test with `brew test sofmani`. Options passed&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    # to `brew install` such as `--HEAD` also need to be provided to `brew test`.&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    #&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    # The installed folder is not in the path, so use the entire path to any&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    # executables being tested: `system bin/&quot;program&quot;, &quot;do&quot;, &quot;something&quot;`.&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    system&lt;/span&gt;&lt;span&gt; &quot;false&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that yours will differ slightly, but the gist is the same.&lt;/p&gt;
&lt;p&gt;There are 3 main sections we currently want to update.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The &lt;code&gt;install&lt;/code&gt; block&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;test&lt;/code&gt; block&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;depends_on&lt;/code&gt; block&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;Install block&lt;/h4&gt;
&lt;p&gt;The &lt;code&gt;install&lt;/code&gt; block will be used to install the software. This is where you tell Homebrew how to
build the app from source. Since we are demonstrating a go app, we can replace the block with the
following:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; install&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  system&lt;/span&gt;&lt;span&gt; &quot;go&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;build&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;-buildmode&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;exe&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;-o&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;sofmani&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;.&quot;&lt;/span&gt;&lt;span&gt; # Build from source&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  bin.&lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt; &quot;sofmani&quot;&lt;/span&gt;&lt;span&gt; # Install the bin into the required directory&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It’s pretty straightforward, but just to make sure we understand what’s going on:&lt;/p&gt;
&lt;p&gt;First, we use &lt;code&gt;system&lt;/code&gt; to run a shell command. This would be the regular build command for your app.
You can run several commands here, use conditions, and do whatever else you need to get your app
properly built from source. You will notice I used &lt;code&gt;&quot;.&quot;&lt;/code&gt; for the current directory as target.&lt;/p&gt;
&lt;p&gt;Second, we use &lt;code&gt;bin.install&lt;/code&gt; which tells brew to act on the directory &lt;code&gt;bin&lt;/code&gt;, and installs a binary
into the homebrew prefix location that it needs to be in for that directory. In our case we supplied
it the output binary name, &lt;code&gt;&quot;sofmani&quot;&lt;/code&gt;.&lt;/p&gt;
&lt;h4&gt;Test block&lt;/h4&gt;
&lt;p&gt;Here we just need to test that the software is installed and works as expected. This isn’t the best
practice but for our example we will make a short call to the app’s help and check that the output
doesn’t crash.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;test&lt;/span&gt;&lt;span&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  assert_match &lt;/span&gt;&lt;span&gt;&quot;Usage: sofmani [options] [config_file]&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;shell_output&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;#{bin}&lt;/span&gt;&lt;span&gt;/sofmani -h&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This adds a simple string match check against the help output.&lt;/p&gt;
&lt;p&gt;Fore more information about adding tests and more matchers, you can refer here:
&lt;a href=&quot;https://docs.brew.sh/Formula-Cookbook#add-a-test-to-the-formula&quot;&gt;Formula Cookbook — Add a test to the formula&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;Depends on block&lt;/h4&gt;
&lt;p&gt;We also need to tell brew that if the user builds from source, they would require &lt;code&gt;go&lt;/code&gt; to be present
in the system:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;depends_on &lt;/span&gt;&lt;span&gt;go:&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;:build&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This tells Homebrew to require the &lt;code&gt;go&lt;/code&gt; system-wide executable, which can be added either by
Homebrew or in a different method by the user. Limiting it to &lt;code&gt;:build&lt;/code&gt; makes sure that the
requirement is only relevant if building from source, not when downloading from bottle.&lt;/p&gt;
&lt;p&gt;For more information about how the dependencies work (for example, requiring a specific version, or
other steps or options) you can read more here:
&lt;a href=&quot;https://docs.brew.sh/Formula-Cookbook#check-for-dependencies&quot;&gt;Formula Cookbook — Check for dependencies&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Step 4: Releasing a new version&lt;/h2&gt;
&lt;p&gt;Make sure to update any other required information, remove all the unnecessary comments from the
code and push your changes. Make sure you are using an alternative branch and not your default
branch.&lt;/p&gt;
&lt;p&gt;For every new version of app, you should do the following steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Make your changes&lt;/li&gt;
&lt;li&gt;Push to a new branch&lt;/li&gt;
&lt;li&gt;Create PR from your branch to the default branch&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This is the process to start a release.&lt;/p&gt;
&lt;p&gt;Once all the tests on your PR pass properly (push new commits with fixes if needed), all you have to
do is &lt;strong&gt;add the &lt;code&gt;pr-pull&lt;/code&gt; tag to your PR&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;This will instruct the release action (&lt;code&gt;release.yml&lt;/code&gt;) to start a release process. It relies on
previous actions to be completed, as it will also attach bottles to the release if they were
properly created.&lt;/p&gt;
&lt;p&gt;When the action is done, you will notice your PR was closed without merging. This is working as
expected, and is a side-effect of the brew bot creating additional commits for you and then pushing
them to the target branch directly alongside your original commits, instead of merging from your
branch through the PR.&lt;/p&gt;
&lt;h3&gt;Bottles&lt;/h3&gt;
&lt;p&gt;You might have noticed that in the extra commit that the bot created, it added a bottles section to
the Formula file, which contains the pre-built binaries for your app:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;bottle &lt;/span&gt;&lt;span&gt;do&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  root_url &lt;/span&gt;&lt;span&gt;&quot;https://github.com/chenasraf/homebrew-tap/releases/download/sofmani-1.4.0&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  sha256 &lt;/span&gt;&lt;span&gt;cellar:&lt;/span&gt;&lt;span&gt; :any_skip_relocation&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;arm64_sonoma:&lt;/span&gt;&lt;span&gt; &quot;4941252086eb62660b684f1210dbec8d9bfd7a343f2fca43a8cc19dee64bb38b&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  sha256 &lt;/span&gt;&lt;span&gt;cellar:&lt;/span&gt;&lt;span&gt; :any_skip_relocation&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;ventura:&lt;/span&gt;&lt;span&gt;      &quot;7b7c96d587d956219f30089af88e416adfef5b039f2f844b241a766d6e8016d1&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  sha256 &lt;/span&gt;&lt;span&gt;cellar:&lt;/span&gt;&lt;span&gt; :any_skip_relocation&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;x86_64_linux:&lt;/span&gt;&lt;span&gt; &quot;39fd3e306f66b7c04a3abe0e8e5dbe2070ef9c72fdd1bdefde2a1807d22082ae&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Bottles are simply pre-built binaries that you can install from the tap directly, without building
from source.&lt;/p&gt;
&lt;p&gt;If a user tries to install your tap on one of the supported platforms, they will have a faster
process by downloading the matching binary that was “bottled up” during the release process.&lt;/p&gt;
&lt;p&gt;If they can’t find a proper match, or if they choose to &lt;code&gt;--build-from-source&lt;/code&gt;, then they will build
the app from source for their system, including any necessary dependencies.&lt;/p&gt;
&lt;h2&gt;Addendum: Updating an existing app&lt;/h2&gt;
&lt;p&gt;Some time has passed, and we want to update the app to version 1.5.0.&lt;/p&gt;
&lt;p&gt;For this we need to change 2 things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The download URL for the tar file&lt;/li&gt;
&lt;li&gt;The SHA256 checksum for the tar file&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;-&lt;/span&gt;  url &quot;https://github.com/chenasraf/sofmani/archive/refs/tags/v1.4.0.tar.gz&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;span&gt;-&lt;/span&gt;  sha256 &quot;0cb4d1f51b23f272fd8f5146e30f937e7cd9ff1275fbcc732a6f27e017c0426a&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;span&gt;+&lt;/span&gt;  url &quot;https://github.com/chenasraf/sofmani/archive/refs/tags/v1.5.0.tar.gz&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;span&gt;+&lt;/span&gt;  sha256 &quot;7a488daa01d999ff2e1a310ce351e3d0d3add4e18c709625b61e7b67b554b90b&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To generate a new checksum, you need to download the tar file, and get the checksum for that:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;curl&lt;/span&gt;&lt;span&gt; -L&lt;/span&gt;&lt;span&gt; https://github.com/chenasraf/sofmani/archive/refs/tags/v1.5.0.tar.gz&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; shasum&lt;/span&gt;&lt;span&gt; -a&lt;/span&gt;&lt;span&gt; 256&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once you made these changes, you are ready to push them to a &lt;strong&gt;new branch&lt;/strong&gt;, as before, create the
PR, and then when the actions are completed successfully you just add the &lt;code&gt;pr-pull&lt;/code&gt; tag to your PR.&lt;/p&gt;
&lt;p&gt;And there you go! You have released your app to the world.&lt;/p&gt;
&lt;h2&gt;Addendum: Simplifying the process by automating more of it&lt;/h2&gt;
&lt;p&gt;I like to automate stuff more, so I created 2 scripts to help:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Download the latest release, get a checksum, and update the file&lt;/li&gt;
&lt;li&gt;Create a PR with the proper branch&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Here are the 2 scripts I use:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;#!/usr/bin/env bash&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; [[ &lt;/span&gt;&lt;span&gt;-z&lt;/span&gt;&lt;span&gt; &quot;&lt;/span&gt;&lt;span&gt;$REPO_NAME&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; ]]; &lt;/span&gt;&lt;span&gt;then&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  read&lt;/span&gt;&lt;span&gt; -r&lt;/span&gt;&lt;span&gt; -p&lt;/span&gt;&lt;span&gt; &quot;Enter repository path: &quot;&lt;/span&gt;&lt;span&gt; REPO_NAME&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;fi&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; [[ &lt;/span&gt;&lt;span&gt;-z&lt;/span&gt;&lt;span&gt; &quot;&lt;/span&gt;&lt;span&gt;$REPO_NAME&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; ]]; &lt;/span&gt;&lt;span&gt;then&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  echo&lt;/span&gt;&lt;span&gt; &quot;Repository path is required&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  exit&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;fi&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;VERSION&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span&gt;curl&lt;/span&gt;&lt;span&gt; -s&lt;/span&gt;&lt;span&gt; &quot;https://api.github.com/repos/&lt;/span&gt;&lt;span&gt;$REPO_NAME&lt;/span&gt;&lt;span&gt;/releases/latest&quot;&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; jq&lt;/span&gt;&lt;span&gt; -r&lt;/span&gt;&lt;span&gt; .tag_name&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;URL&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;https://github.com/&lt;/span&gt;&lt;span&gt;$REPO_NAME&lt;/span&gt;&lt;span&gt;/archive/refs/tags/&lt;/span&gt;&lt;span&gt;$VERSION&lt;/span&gt;&lt;span&gt;.tar.gz&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;APP_NAME&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span&gt;basename&lt;/span&gt;&lt;span&gt; &quot;&lt;/span&gt;&lt;span&gt;$REPO_NAME&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;echo&lt;/span&gt;&lt;span&gt; &quot;Version: &lt;/span&gt;&lt;span&gt;$VERSION&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;echo&lt;/span&gt;&lt;span&gt; &quot;URL: &lt;/span&gt;&lt;span&gt;$URL&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;curl&lt;/span&gt;&lt;span&gt; -Ls&lt;/span&gt;&lt;span&gt; &quot;&lt;/span&gt;&lt;span&gt;$URL&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; -o&lt;/span&gt;&lt;span&gt; &quot;&lt;/span&gt;&lt;span&gt;$APP_NAME&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;$VERSION&lt;/span&gt;&lt;span&gt;.tar.gz&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;echo&lt;/span&gt;&lt;span&gt; -n&lt;/span&gt;&lt;span&gt; &quot;SHA256: &quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;hash&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span&gt;sha256sum&lt;/span&gt;&lt;span&gt; &quot;&lt;/span&gt;&lt;span&gt;$APP_NAME&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;$VERSION&lt;/span&gt;&lt;span&gt;.tar.gz&quot;&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; awk&lt;/span&gt;&lt;span&gt; &apos;{print $1}&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;echo&lt;/span&gt;&lt;span&gt; &quot;&lt;/span&gt;&lt;span&gt;$hash&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;rm&lt;/span&gt;&lt;span&gt; &quot;&lt;/span&gt;&lt;span&gt;$APP_NAME&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;$VERSION&lt;/span&gt;&lt;span&gt;.tar.gz&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;sed&lt;/span&gt;&lt;span&gt; -i.bak&lt;/span&gt;&lt;span&gt; &quot;s/sha256 &lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;.*&lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;/sha256 &lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;$hash&lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;/&quot;&lt;/span&gt;&lt;span&gt; &quot;Formula/&lt;/span&gt;&lt;span&gt;$APP_NAME&lt;/span&gt;&lt;span&gt;.rb&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;rm&lt;/span&gt;&lt;span&gt; &quot;Formula/&lt;/span&gt;&lt;span&gt;$APP_NAME&lt;/span&gt;&lt;span&gt;.rb.bak&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;sed&lt;/span&gt;&lt;span&gt; -i.bak&lt;/span&gt;&lt;span&gt; &quot;s| url &lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;.*&lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;| url &lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;$URL&lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;|&quot;&lt;/span&gt;&lt;span&gt; &quot;Formula/&lt;/span&gt;&lt;span&gt;$APP_NAME&lt;/span&gt;&lt;span&gt;.rb&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;rm&lt;/span&gt;&lt;span&gt; &quot;Formula/&lt;/span&gt;&lt;span&gt;$APP_NAME&lt;/span&gt;&lt;span&gt;.rb.bak&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I can either run this directly to get the interactive version or add the &lt;code&gt;REPO_NAME&lt;/code&gt; variable ahead
of time to pre-populate it and make it completely automatic.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;REPO_NAME&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;chenasraf/sofmani&lt;/span&gt;&lt;span&gt; ./scripts/update-hash.sh&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And here is the second step of the process, which is to create the PR properly:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;#!/usr/bin/env bash&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; [[ &lt;/span&gt;&lt;span&gt;-z&lt;/span&gt;&lt;span&gt; &quot;&lt;/span&gt;&lt;span&gt;$REPO_NAME&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; ]]; &lt;/span&gt;&lt;span&gt;then&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  read&lt;/span&gt;&lt;span&gt; -r&lt;/span&gt;&lt;span&gt; -p&lt;/span&gt;&lt;span&gt; &quot;Enter repository path: &quot;&lt;/span&gt;&lt;span&gt; REPO_NAME&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;fi&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; [[ &lt;/span&gt;&lt;span&gt;-z&lt;/span&gt;&lt;span&gt; &quot;&lt;/span&gt;&lt;span&gt;$REPO_NAME&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; ]]; &lt;/span&gt;&lt;span&gt;then&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  echo&lt;/span&gt;&lt;span&gt; &quot;Repository path is required&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  exit&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;fi&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;VERSION&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span&gt;curl&lt;/span&gt;&lt;span&gt; -s&lt;/span&gt;&lt;span&gt; &quot;https://api.github.com/repos/&lt;/span&gt;&lt;span&gt;$REPO_NAME&lt;/span&gt;&lt;span&gt;/releases/latest&quot;&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; jq&lt;/span&gt;&lt;span&gt; -r&lt;/span&gt;&lt;span&gt; .tag_name&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;APP_NAME&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span&gt;basename&lt;/span&gt;&lt;span&gt; &quot;&lt;/span&gt;&lt;span&gt;$REPO_NAME&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;echo&lt;/span&gt;&lt;span&gt; &quot;Version: &lt;/span&gt;&lt;span&gt;$VERSION&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;BRANCH&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;feature/&lt;/span&gt;&lt;span&gt;$APP_NAME&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;$VERSION&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; !&lt;/span&gt;&lt;span&gt; git&lt;/span&gt;&lt;span&gt; switch&lt;/span&gt;&lt;span&gt; -C&lt;/span&gt;&lt;span&gt; &quot;&lt;/span&gt;&lt;span&gt;$BRANCH&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;then&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  echo&lt;/span&gt;&lt;span&gt; &quot;Branch already exists, aborting&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  exit&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;fi&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; add&lt;/span&gt;&lt;span&gt; &quot;Formula/&lt;/span&gt;&lt;span&gt;$APP_NAME&lt;/span&gt;&lt;span&gt;.rb&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; commit&lt;/span&gt;&lt;span&gt; -m&lt;/span&gt;&lt;span&gt; &quot;feat: update &lt;/span&gt;&lt;span&gt;$APP_NAME&lt;/span&gt;&lt;span&gt; to &lt;/span&gt;&lt;span&gt;$VERSION&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; push&lt;/span&gt;&lt;span&gt; --set-upstream&lt;/span&gt;&lt;span&gt; origin&lt;/span&gt;&lt;span&gt; &quot;&lt;/span&gt;&lt;span&gt;$BRANCH&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; gh&lt;/span&gt;&lt;span&gt; pr&lt;/span&gt;&lt;span&gt; create&lt;/span&gt;&lt;span&gt; --fill&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;then&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  open&lt;/span&gt;&lt;span&gt; -u&lt;/span&gt;&lt;span&gt; &quot;$(&lt;/span&gt;&lt;span&gt;gh&lt;/span&gt;&lt;span&gt; pr list &lt;/span&gt;&lt;span&gt;--json&lt;/span&gt;&lt;span&gt; url &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt; jq&lt;/span&gt;&lt;span&gt; -r&lt;/span&gt;&lt;span&gt; &apos;.[0].url&apos;)&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  git&lt;/span&gt;&lt;span&gt; switch&lt;/span&gt;&lt;span&gt; master&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;else&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  echo&lt;/span&gt;&lt;span&gt; &quot;Couldn&apos;t create PR, aborting&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  exit&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;fi&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Again, I can use the variable to make life a bit easier:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;REPO_NAME&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;chenasraf/sofmani&lt;/span&gt;&lt;span&gt; ./scripts/create-pr.sh&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can combine the 2 and automatically get the latest release pushed for any app:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;REPO_NAME&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;chenasraf/sofmani&lt;/span&gt;&lt;span&gt; ./scripts/update-hash.sh&lt;/span&gt;&lt;span&gt; &amp;amp;&amp;amp; &lt;/span&gt;&lt;span&gt;./scripts/create-pr.sh&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;How’s that for making it simple? With one line (which you can wrap in a Makefile or any similar
choice if you want), you can update the tap and create a PR in one step.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;We created a tap, pushed a new app to it, and also pushed an update.&lt;/p&gt;
&lt;p&gt;Now that you have an app on your tap, you can simply instruct your users to use it instead of
downloading the release manually or figuring out what to do with the files.&lt;/p&gt;
&lt;p&gt;It’s also pretty widely used on macOS and so it’s almost guaranteed to simplify things for a lot of
your potential users, or for yourself!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Share your taps with us in the comments, or if you have more tips, or questions feel free to reply
below to let everyone know!&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;Further Reading&lt;/h2&gt;
&lt;p&gt;It’s best to learn directly from the source, so here are some resources to answer more questions,
for example, how to properly manage the dependencies for other types of apps, or how to make more
complex install steps.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.brew.sh/Taps&quot;&gt;Taps (Third-Party Repositories) — Homebrew Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.brew.sh/Formula-Cookbook&quot;&gt;Formula Cookbook — Homebrew Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.brew.sh/Adding-Software-to-Homebrew&quot;&gt;Adding Software to Homebrew — Homebrew Documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item></channel></rss>