<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Max Belsky</title>
    <description>The latest articles on Forem by Max Belsky (@mbelsky).</description>
    <link>https://forem.com/mbelsky</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F198973%2F20be3730-feac-4f54-accf-1c3602bb4722.png</url>
      <title>Forem: Max Belsky</title>
      <link>https://forem.com/mbelsky</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/mbelsky"/>
    <language>en</language>
    <item>
      <title>Dockerizing a Workspaced Node.js Application</title>
      <dc:creator>Max Belsky</dc:creator>
      <pubDate>Wed, 11 Nov 2020 18:32:28 +0000</pubDate>
      <link>https://forem.com/mbelsky/dockerizing-a-workspaced-node-js-application-2pf7</link>
      <guid>https://forem.com/mbelsky/dockerizing-a-workspaced-node-js-application-2pf7</guid>
      <description>&lt;p&gt;Re-usage of build cache is one of the most important things in Docker images creating.&lt;/p&gt;

&lt;p&gt;To efficiently dockerize an app you need to split source code copying and dependencies installation in a few steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Copy dependencies files.&lt;/li&gt;
&lt;li&gt;Install dependencies.&lt;/li&gt;
&lt;li&gt;Copy source code.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For a node.js application these steps look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package.json yarn.lock ./&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;yarn &lt;span class="nb"&gt;install&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, this solution does not work with yarn workspaced application because the root &lt;code&gt;package.json&lt;/code&gt; and &lt;code&gt;yarn.lock&lt;/code&gt; are not enough to install whole project dependencies.&lt;/p&gt;

&lt;p&gt;When I faced this task the first time I thought: what if I find all nested &lt;code&gt;package.json&lt;/code&gt; files and copy them to a &lt;code&gt;src&lt;/code&gt; directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; src/**/package.json src/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;src/**/package.json&lt;/code&gt; pattern matches all &lt;code&gt;package.json&lt;/code&gt;'s that I need. But &lt;a href="https://docs.docker.com/engine/reference/builder/#copy"&gt;&lt;code&gt;COPY&lt;/code&gt;&lt;/a&gt; works as not I expected. And instead of the expected directories structure I've got a single file under the &lt;code&gt;src&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# The original project's tree&lt;/span&gt;
app
├── package.json
├── src
│   ├── backend
│   │   ├── backend.js
│   │   └── package.json
│   ├── notifier
│   │   ├── notifier.js
│   │   └── package.json
│   └── scraper
│       ├── package.json
│       └── scraper.js
└── yarn.lock

&lt;span class="c"&gt;# The expected tree&lt;/span&gt;
app
├── package.json
├── src
│   ├── backend
│   │   └── package.json
│   ├── notifier
│   │   └── package.json
│   └── scraper
│       └── package.json
└── yarn.lock

&lt;span class="c"&gt;# The result tree&lt;/span&gt;
app
├── package.json
├── src
│   └── package.json
└── yarn.lock
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a second I thought I could replace the single pattern line with a &lt;code&gt;COPY&lt;/code&gt; operation for every workspace. But I wanted to have a scalable solution, a solution without duplication.&lt;/p&gt;

&lt;h2&gt;
  
  
  Shell solution
&lt;/h2&gt;

&lt;p&gt;I've googled some alternative &lt;a href="https://stackoverflow.com/a/50010093/1088836"&gt;solutions&lt;/a&gt;. Commonly they suggest wrapping &lt;code&gt;docker build&lt;/code&gt; with a script that creates a &lt;code&gt;tmp&lt;/code&gt; folder, build the expected &lt;code&gt;package.json&lt;/code&gt;'s tree there and &lt;code&gt;COPY&lt;/code&gt; the folder in the image.&lt;/p&gt;

&lt;p&gt;And the "shell solution" is much better than the previous "copy-paste" solution. But it did not make me feel pleased.&lt;/p&gt;

&lt;h2&gt;
  
  
  Multi-stage builds solution
&lt;/h2&gt;

&lt;p&gt;At some point, I thought of &lt;a href="https://docs.docker.com/develop/develop-images/multistage-build/"&gt;multi-stage builds&lt;/a&gt;. I used it in another project to build a tiny production image. "What if I will prepare the tree on a first stage and copy it on a second stage?"&lt;/p&gt;

&lt;p&gt;In addition to the root &lt;code&gt;package.json&lt;/code&gt; and &lt;code&gt;yarn.lock&lt;/code&gt; files I copied the &lt;code&gt;src&lt;/code&gt; directory and removed all not &lt;code&gt;package.json&lt;/code&gt; files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package.json yarn.lock ./&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; src src&lt;/span&gt;

&lt;span class="c"&gt;# Remove not "package.json" files&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;find src &lt;span class="se"&gt;\!&lt;/span&gt; &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s2"&gt;"package.json"&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="nt"&gt;-mindepth&lt;/span&gt; 2 &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="nt"&gt;-maxdepth&lt;/span&gt; 2 &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="nt"&gt;-print&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  | xargs &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On a second stage I copied the tree and installed dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=0 /app .&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;yarn &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--frozen-lockfile&lt;/span&gt; &lt;span class="nt"&gt;--production&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Under the hood &lt;code&gt;yarn workspaces&lt;/code&gt; use symlinks. So it's important to create them after copying &lt;code&gt;src&lt;/code&gt; directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;

&lt;span class="c"&gt;# Restore workspaces symlinks&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;yarn &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--frozen-lockfile&lt;/span&gt; &lt;span class="nt"&gt;--production&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The final solution Dockerfile
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:14.15.0-alpine3.10&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package.json yarn.lock ./&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; src src&lt;/span&gt;

&lt;span class="c"&gt;# Remove not "package.json" files&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;find src &lt;span class="se"&gt;\!&lt;/span&gt; &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s2"&gt;"package.json"&lt;/span&gt; &lt;span class="nt"&gt;-mindepth&lt;/span&gt; 2 &lt;span class="nt"&gt;-maxdepth&lt;/span&gt; 2 &lt;span class="nt"&gt;-print&lt;/span&gt; | xargs &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:14.15.0-alpine3.10&lt;/span&gt;

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; NODE_ENV production&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=0 /app .&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;yarn &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--frozen-lockfile&lt;/span&gt; &lt;span class="nt"&gt;--production&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;

&lt;span class="c"&gt;# Restore workspaces symlinks&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;yarn &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--frozen-lockfile&lt;/span&gt; &lt;span class="nt"&gt;--production&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["yarn", "start"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>javascript</category>
      <category>node</category>
      <category>docker</category>
      <category>yarn</category>
    </item>
    <item>
      <title>React-Redux apps on Rx steroids</title>
      <dc:creator>Max Belsky</dc:creator>
      <pubDate>Sun, 05 Jul 2020 19:47:18 +0000</pubDate>
      <link>https://forem.com/mbelsky/react-redux-apps-on-rx-steroids-55gi</link>
      <guid>https://forem.com/mbelsky/react-redux-apps-on-rx-steroids-55gi</guid>
      <description>&lt;p&gt;Since 2014 almost every week I've heard something about Rx. I've read many Rx docs, articles, and code examples. But I still had no idea why I should move to reactive programming paradigm if I can successfully solve my tasks with &lt;code&gt;element.addEventListener(…)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In 2020 I found an awesome way to make complex react-redux components dumb and testable with Rx. And I'd like to share it with you in this article.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This article doesn't include Rx basics. If need you may find it &lt;a href="https://rxjs.dev/guide/overview" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;To see the Rx power let's start with an example.&lt;/p&gt;

&lt;p&gt;Imagine there is a website that lists famous quotes. This website's users may do the following things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;read quotes&lt;/li&gt;
&lt;li&gt;mark a quote as favorite&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And this website may look like on the image below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fsqbfwf2bd50uolsqpmbn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fsqbfwf2bd50uolsqpmbn.png" alt="website mockup"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you may see this webpage highlight favorite quotes for signed-in users only. So this website also has a sign in popup.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Flbtc207d2ringysgkupc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Flbtc207d2ringysgkupc.png" alt="website sign in popup mockup"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So now we imagine a component connected to redux that has quotes list, user data, and favorite quotes ids. And it makes a request to get favorite quotes list when a user is signed in.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;QuotesComponent&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Component&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;componentDidUpdate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prevProps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;undefiend&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;prevProps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;loadFavorites&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// other methods&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With current realization, this component knows not only about things to render. This component also contains some logic to make requests at the right moment.&lt;/p&gt;

&lt;p&gt;And Rx comes here.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://redux-observable.js.org/" rel="noopener noreferrer"&gt;redux-observable&lt;/a&gt; is an awesome library that allows you to work with redux actions and state like with a stream. The core primitive to do that is &lt;a href="https://redux-observable.js.org/docs/basics/Epics.html" rel="noopener noreferrer"&gt;Epic&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To make the &lt;code&gt;QuotesComponent&lt;/code&gt; simpler we will do the following steps: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;replace the &lt;code&gt;componentDidUpdate&lt;/code&gt; implementation with &lt;code&gt;componentDidMount&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;create an epic to load favorite quotes ids for a user&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;With redux-observable we don't need to track the user prop more. So we may just enqueue favorites loading in &lt;code&gt;componentDidMount&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;QuotesComponent&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Component&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;componentDidMount&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;enqueueLoadFavorites&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// other methods&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why we don't need to track the user prop? Because we will create an epic that will do that for us.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;enqueueLoadFavoritesEpic&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action$&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;state$&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;action$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;enqueueLoadFavorites&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nf"&gt;switchMap&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;selectUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;undefined&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// wait for signing in before loading favorites&lt;/span&gt;

      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;state$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;selectUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
        &lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;take&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nf"&gt;switchMap&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;loadFavorites&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;}),&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;loadFavorites&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;loadFavoritesEpic&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// do some async work, requests, get data and dispatch result action&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;enqueueLoadFavoritesEpic&lt;/code&gt; doing the following steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;wait for enqueue action&lt;/li&gt;
&lt;li&gt;check for signed-in user&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;dispatch load action if a user is signed in&lt;/li&gt;
&lt;li&gt;create a store observer that waits for a signed-in user to dispatch load action&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Well as you see redux-observable is a great tool to handle pending actions depends on the store. redux-observable makes your components dumb and gets a way to test pending actions with unit tests.&lt;/p&gt;

</description>
      <category>react</category>
      <category>redux</category>
      <category>rxjs</category>
    </item>
    <item>
      <title>useHover hooked a fool. The fool was me</title>
      <dc:creator>Max Belsky</dc:creator>
      <pubDate>Sun, 23 Feb 2020 19:37:05 +0000</pubDate>
      <link>https://forem.com/mbelsky/usehover-hooked-a-fool-the-fool-was-me-2fmg</link>
      <guid>https://forem.com/mbelsky/usehover-hooked-a-fool-the-fool-was-me-2fmg</guid>
      <description>&lt;p&gt;TLDR: useHover may trigger unnecessary re-renders: &lt;a href="https://codesandbox.io/s/usehover-sl3br"&gt;demo&lt;/a&gt;, &lt;a href="https://codesandbox.io/s/usehover-v2-jxfdp"&gt;solution&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This weekend I found The Guardian's &lt;a href="https://www.theguardian.com/info/2019/dec/08/migrating-the-guardian-website-to-react"&gt;blog post&lt;/a&gt; about their website migration to React.  At the end of the post, they mention DCR. It's a frontend rendering framework for theguardian.com and it's available on &lt;a href="https://github.com/guardian/dotcom-rendering"&gt;Github&lt;/a&gt;. I was so interested in how it is designed inside so I've started my research.&lt;/p&gt;

&lt;p&gt;Performance is one of things that I was interested in. So I've tried to find any usages of &lt;code&gt;React.memo&lt;/code&gt;, &lt;code&gt;PureComponent&lt;/code&gt; or &lt;code&gt;shouldComponentUpdate&lt;/code&gt;. And I was so surprised when I found nothing.&lt;/p&gt;

&lt;p&gt;I've gone back to their site and started profiling it. And I didn't found any unnecessary re-renders because it just renders article. The data never changes, the page doesn't have any tricky handlers. So any optimizations will be just extra costs here. But then I've found this.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--As3pazzG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/l9wdk57j1tzwvg6datjb.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--As3pazzG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/l9wdk57j1tzwvg6datjb.jpg" alt="Most viewed articles component"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's a simple component to render most viewed articles aside. And it re-renders on a hover event because its content styled programmatically. So it looks logical: you hover X and it re-renders because now it's hovered. After you hover Y and X with Y re-render because the state of both components changed. But what if I show you this?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vHf8K0lR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/vmiqfsy5fsy9s07pe7y1.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vHf8K0lR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/vmiqfsy5fsy9s07pe7y1.gif" alt="Mouse movement inside the component triggers re-renders"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Blue and orange boxes is not a part of the component. React profiler shows it when a component re-renders&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Mouse movement inside the component still trigger re-renders, but I had no idea why. I've wrapped internal components with &lt;code&gt;React.memo&lt;/code&gt;, callbacks with &lt;code&gt;useCallback&lt;/code&gt; and other things that usually help. But this component was still re-render. React profiler showed that props still change. Then I thought maybe &lt;a href="https://github.com/guardian/dotcom-rendering/blob/177833ee516ed7d6cf80075a30faf66123962d07/src/web/lib/useHover.tsx#L1"&gt;&lt;code&gt;useHover&lt;/code&gt;&lt;/a&gt; has some issues. But it didn't.&lt;/p&gt;

&lt;p&gt;So I've written a plain html+css+js &lt;a href="https://jsfiddle.net/mbelsky/831t670y/"&gt;demo&lt;/a&gt; and shared with some friends to whine and complain why mouse movement inside a hovered element triggers &lt;code&gt;mouseout&lt;/code&gt; and &lt;code&gt;mouseover&lt;/code&gt; events. And they helped me. I have forgotten about a core mechanic of javascript events. The mechanic is &lt;a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#Event_bubbling_and_capture"&gt;event bubbling and capture&lt;/a&gt;. The demo extended &lt;a href="https://jsfiddle.net/mbelsky/831t670y/13/"&gt;with extra logging&lt;/a&gt; &lt;code&gt;currentTarget&lt;/code&gt; shows it.&lt;/p&gt;

&lt;p&gt;Unfortunately &lt;code&gt;e.stopPropagation&lt;/code&gt; does not solve current issue, so I've implemented a throttling mechanism for that hook with &lt;code&gt;setTimeout&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;iframe src="https://codesandbox.io/embed/jxfdp"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The original version of the demo is available &lt;a href="https://codesandbox.io/s/usehover-sl3br"&gt;here&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;As before, mouse event handlers trigger immediately (you may track it by console logs), but &lt;code&gt;setState&lt;/code&gt; (prefixed with &lt;code&gt;Deferred&lt;/code&gt;) will be called on the next event loop tick only. So if we have two or more serial events, the hook won't call &lt;code&gt;setState&lt;/code&gt; on every event, it will work only once with the latest &lt;code&gt;true/false&lt;/code&gt; value.&lt;/p&gt;

&lt;p&gt;It was a good reminder to don't forget javascript basics because React is just a library that builded on them.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>react</category>
      <category>hooks</category>
    </item>
    <item>
      <title>Send message as a Telegram bot. What may go wrong?</title>
      <dc:creator>Max Belsky</dc:creator>
      <pubDate>Tue, 17 Dec 2019 13:45:11 +0000</pubDate>
      <link>https://forem.com/mbelsky/send-message-as-a-telegram-bot-what-may-go-wrong-1adf</link>
      <guid>https://forem.com/mbelsky/send-message-as-a-telegram-bot-what-may-go-wrong-1adf</guid>
      <description>&lt;p&gt;Last month I've worked on &lt;a href="https://t.me/hltvFeaturedBot"&gt;@hltvFeatured&lt;/a&gt; – it's a Telegram bot to get notifications about upcoming Counter-Strike: Global Offensive matches featured by HLTV.org. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--D4BLpvxi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/zb9ihaoxjfca6rbgsd7r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--D4BLpvxi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/zb9ihaoxjfca6rbgsd7r.png" alt="Example of @hltvFeatured message"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After a few weeks in production I've got an alert that the bot fails on sending notifications to subscribers. I had no access to my PC, so it was so nervous. I didn't know what may go wrong. &lt;/p&gt;

&lt;p&gt;When I back to home first of all I opened IDE and started debugging. There were no issues with database, the app's code or network. But the Telegram API returns error &lt;code&gt;400: Bad Request: can't parse entities&lt;/code&gt;. I've started analyze what wrong with the messages and why it didn't fail before.&lt;/p&gt;

&lt;p&gt;Telegram API allows to format messages in two styles: &lt;a href="https://core.telegram.org/bots/api#formatting-options"&gt;Markdown and HTML&lt;/a&gt;. I've selected Markdown as less verbose and wrote a small function to convert match data entity to a Markdown string:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;convertToMessage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;stars&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unixTimestamp&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;when&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;unixTimestamp&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toUTCString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;formatUTCString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;when&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;NBSP&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`
[&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;NBSP&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;](&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;)
Rating: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;☆&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;repeat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stars&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;–&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
_&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; @ &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_
`&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That day matches had an interesting event name: &lt;em&gt;cs_summit 5&lt;/em&gt; and I immediately noticed it. As you can see the convert function makes date and event &lt;em&gt;italic&lt;/em&gt;: &lt;code&gt;_${date} @ ${event}_&lt;/code&gt;. So the message contained three underscores. I didn't expect that Telegram API can't parse a message like that, so I've started search for a dependency to escape symbols like &lt;code&gt;_&lt;/code&gt;, &lt;code&gt;*&lt;/code&gt; and &lt;code&gt;[&lt;/code&gt; in matches data before inject it in message template.&lt;/p&gt;

&lt;p&gt;You have no idea how I was surprised when Telegram API answered with the same error for a message with escaped symbols. This time I went to google it. The solution was a suggestion to use HTML markup and escape symbols. I've updated my function and... it works!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;convertToMessage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;stars&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unixTimestamp&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;when&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;unixTimestamp&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toUTCString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;formatUTCString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;when&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;NBSP&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`
&amp;lt;a href="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;escapeHtml&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;NBSP&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/a&amp;gt;
Rating: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;☆&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;repeat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stars&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;–&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
&amp;lt;i&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; @ &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;escapeHtml&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/i&amp;gt;
`&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I can't imagine a case when Markdown is a good choice  to markup messages delivered by a bot. And if you can, please, share in comments :)&lt;/p&gt;

</description>
      <category>telegram</category>
      <category>bots</category>
      <category>node</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Pair husky with Git LFS in your JavaScript project</title>
      <dc:creator>Max Belsky</dc:creator>
      <pubDate>Mon, 14 Oct 2019 14:36:18 +0000</pubDate>
      <link>https://forem.com/mbelsky/pair-husky-with-git-lfs-in-your-javascript-project-2kh0</link>
      <guid>https://forem.com/mbelsky/pair-husky-with-git-lfs-in-your-javascript-project-2kh0</guid>
      <description>&lt;p&gt;As you may know Git has a way to fire off custom scripts when certain important actions occur. This is &lt;a href="https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks"&gt;hooks&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I have worked on a project which uses &lt;a href="https://github.com/typicode/husky"&gt;husky&lt;/a&gt; as tool to run formatters and linters on pre-comimit hooks. One day we have decided to cover our forms with screenshot tests to be sure that our changes do not break UI. We have needed to store binary files in our repo, so we choose &lt;a href="https://git-lfs.github.com"&gt;Git LFS&lt;/a&gt; to make &lt;code&gt;git&lt;/code&gt; operations like &lt;code&gt;git pull&lt;/code&gt; and &lt;code&gt;git clone&lt;/code&gt; faster. If you are not familiar with Git LFS check this awesome Atlassian’s &lt;a href="https://www.atlassian.com/git/tutorials/git-lfs"&gt;guide&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This seemed like a good and simple solution. I’ve started work on integration Git LFS in our project. Its &lt;code&gt;Getting Started&lt;/code&gt; looks so easy: just download and run three commands in your terminal. I did fail on this first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;git lfs &lt;span class="nb"&gt;install
&lt;/span&gt;Hook already exists: pre-push
…
To resolve this, either:
  1: run &lt;span class="sb"&gt;`&lt;/span&gt;git lfs update &lt;span class="nt"&gt;--manual&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt; &lt;span class="k"&gt;for &lt;/span&gt;instructions on how to merge hooks.
  2: run &lt;span class="sb"&gt;`&lt;/span&gt;git lfs update &lt;span class="nt"&gt;--force&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt; to overwrite your hook.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both suggested solutions could fix this issue, but not really. The second overwrites a few husky’s hooks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;post-checkout&lt;/li&gt;
&lt;li&gt;post-commit&lt;/li&gt;
&lt;li&gt;post-merge&lt;/li&gt;
&lt;li&gt;pre-push&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So husky won’t run scripts if you configured any of listed above.&lt;/p&gt;

&lt;p&gt;The first is more compatible with husky. However there is still an issue: anyone who will clone that repo should merge hooks manually. That is why I come with one more solution.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; .git/hooks
&lt;span class="nv"&gt;$ &lt;/span&gt;git lfs &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;mv&lt;/span&gt; .git/hooks ./lfs-hooks
&lt;span class="c"&gt;# Uninstall this dependency to restore husky hooks with `npm install`&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; node_modules/husky
&lt;span class="nv"&gt;$ &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this moment husky hooks will be installed in &lt;code&gt;.git/hooks&lt;/code&gt; and Git LFS hooks in &lt;code&gt;./lfs-hooks&lt;/code&gt;. Now you need to configure Git LFS hooks running with husky:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;husky&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hooks&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;post-checkout&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;echo $HUSKY_GIT_STDIN | lfs-hooks/post-checkout $HUSKY_GIT_PARAMS&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;post-commit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;echo $HUSKY_GIT_STDIN | lfs-hooks/post-commit $HUSKY_GIT_PARAMS&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;post-merge&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;echo $HUSKY_GIT_STDIN | lfs-hooks/post-merge $HUSKY_GIT_PARAMS&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pre-push&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;echo $HUSKY_GIT_STDIN | lfs-hooks/pre-push $HUSKY_GIT_PARAMS&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Thanks @mattrabe for &lt;a href="https://github.com/typicode/husky/issues/108#issuecomment-493582447"&gt;this snippet&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Finish Git LFS installation with &lt;code&gt;git lfs track &amp;lt;binary files&amp;gt;&lt;/code&gt; to set up &lt;code&gt;.gitattributes&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now save, commit and push. Your collaborators will not need anything to do to start with husky and Git LFS.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note:&lt;/em&gt; When someone &lt;em&gt;clones&lt;/em&gt; your repo first of all she need to remove &lt;code&gt;.git/hooks&lt;/code&gt; directory because Git LFS creates some hooks by default and husky does not have an overwrite option yet.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; .git/hooks
&lt;span class="nv"&gt;$ &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is all. Happy coding!&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>husky</category>
      <category>gitlfs</category>
    </item>
    <item>
      <title>From bash to zsh. My little adventure</title>
      <dc:creator>Max Belsky</dc:creator>
      <pubDate>Tue, 01 Oct 2019 11:39:24 +0000</pubDate>
      <link>https://forem.com/mbelsky/from-bash-to-zsh-my-little-adventure-4amb</link>
      <guid>https://forem.com/mbelsky/from-bash-to-zsh-my-little-adventure-4amb</guid>
      <description>&lt;p&gt;A few months ago Apple announced the upcoming version of its desktop OS macOS Catalina, which is going to replace the default command line shell &lt;code&gt;bash&lt;/code&gt; with &lt;code&gt;zsh&lt;/code&gt; for all newly created accounts. This was the start of my “adventure”. It wasn’t so long, however it was full of pitfalls. So I came here in the hope to save a few hours for someone.&lt;/p&gt;

&lt;p&gt;First of all lets change a command line shell for your user. To do that we need locate &lt;code&gt;zsh&lt;/code&gt; and set as default shell:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# get path to `zsh` on your machine and use it with `chsh` command
$ which zsh
/bin/zsh
$ chsh -s /bin/zsh $USER
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Restart Terminal.app and make sure that your changes applied:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ ps -p $$ -oargs=
-zsh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we can make some customizations. Every day I operate with &lt;code&gt;git&lt;/code&gt; from terminal so there are two important things for me: git-prompt and git-autocomplete. Zsh provides git-autocomplete out of the box. So there is some commands to set up prompt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ cd ~/
$ curl -o .git-prompt.sh https://raw.githubusercontent.com/git/git/master/contrib/completion/git-prompt.sh
$ touch ~/.zshrc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We almost done 😁 Open &lt;code&gt;~/.zshrc&lt;/code&gt; in your favourite text editor and put there:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/bin/zsh

# Set up git-prompt
source ~/.git-prompt.sh
setopt PROMPT_SUBST; PS1='%~ $(__git_ps1 "(%s)")\$ '

autoload -U compinit; compinit

# If you have aliases or $PATH extensions  in your .bash_profile paste it below
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Restart your terminal one more time and enjoy your new shell.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0lekV7LD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/pm1cwryg94egexu0gd82.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0lekV7LD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/pm1cwryg94egexu0gd82.png" alt="A terminal's preview after restart"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>macos</category>
      <category>terminal</category>
      <category>bash</category>
      <category>zsh</category>
    </item>
  </channel>
</rss>
