<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Alexey Antipov</title>
    <description>The latest articles on DEV Community by Alexey Antipov (@aantipov).</description>
    <link>https://dev.to/aantipov</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%2F269042%2Fbe6b322d-344c-4363-a63f-13452f5391a5.jpg</url>
      <title>DEV Community: Alexey Antipov</title>
      <link>https://dev.to/aantipov</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/aantipov"/>
    <language>en</language>
    <item>
      <title>Observability &amp; Source Maps: Through thorns to the stars</title>
      <dc:creator>Alexey Antipov</dc:creator>
      <pubDate>Wed, 14 Feb 2024 14:01:47 +0000</pubDate>
      <link>https://dev.to/aantipov/observability-source-maps-through-thorns-to-the-stars-3pi4</link>
      <guid>https://dev.to/aantipov/observability-source-maps-through-thorns-to-the-stars-3pi4</guid>
      <description>&lt;p&gt;&lt;strong&gt;TLDR&lt;/strong&gt;: &lt;a href="https://sentry.io/"&gt;Sentry&lt;/a&gt; has come up with a simple and reliable way to associate production code with source maps and set it working. No excuses for not using source maps anymore.&lt;/p&gt;

&lt;p&gt;Around this time a year ago I was busy finding my way around a proper observability setup for Flink’s (my employer at that time) microfrontends. The main hurdle was dealing with source maps in &lt;a href="https://www.datadoghq.com/"&gt;Datadog&lt;/a&gt; (an observability tool) which hadn’t supported microfrontends natively and I was looking for a workaround.&lt;/p&gt;




&lt;h3&gt;
  
  
  Why source maps?
&lt;/h3&gt;

&lt;p&gt;Code errors reported to an observability service (e.g. Sentry, Datadog) contain stack traces with a chain of functions calls which lead to the exception. This information includes function name, file name and place in the file where the function is called. That should help developers in debugging their code - locate the problematic code faster.&lt;/p&gt;

&lt;p&gt;The problem though is that our code in production is obfuscated and minified. This means that function names, filenames and function calls locations from stack traces are of no use to us.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv75bqm9r2blnu97gp1jq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv75bqm9r2blnu97gp1jq.png" alt="screenshot of Sentry's representation of an error stacktrace without source maps" width="800" height="303"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To solve that problem observability services use source maps - kind of a mapping between obfuscated production code and raw code. Production errors’ stack traces get translated into true function names and their calls locations in the original raw code. Observability tools even restore the raw code in the vicinity of each function call. As a result, pinpointing the problematic code becomes much easier.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1x8iryhg6qe3n409wa2f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1x8iryhg6qe3n409wa2f.png" alt="screenshot of Sentry's representation of an error stacktrace with source maps" width="800" height="484"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Where to get source maps?
&lt;/h3&gt;

&lt;p&gt;Source maps are files with &lt;code&gt;.js.map&lt;/code&gt; extension. They are generated by code bundlers e.g. Webpack, Vite. For each &lt;code&gt;[filename].js&lt;/code&gt; file an accompanying &lt;code&gt;[filename].js.map&lt;/code&gt; file is generated.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffg4dv18v8z0to2q19jij.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffg4dv18v8z0to2q19jij.png" alt="screenshot of a file tree with scripts and their source maps counterparts" width="688" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When deploying your code to production, you need to upload the associated source maps to your observability service.&lt;/p&gt;

&lt;h3&gt;
  
  
  The problem with source maps
&lt;/h3&gt;

&lt;p&gt;When dealing with source maps, the hurdle lies in association of minified &lt;code&gt;[filename].js&lt;/code&gt; files with their &lt;code&gt;[filename].js.map&lt;/code&gt; source maps. Depending on your setup, filenames may persist but their contents change with every deploy. Your observability service end up having multiple source maps for a single filename. Which one should it use to deobfuscate an error stacktrace at hand?&lt;/p&gt;

&lt;p&gt;To overcome this issue, most observability tools rely on extra information: &lt;em&gt;releases&lt;/em&gt; (versions). It requires you to perform two extra steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;for each production deploy, you should generate a new release number and provide that number when uploading source maps to your observability tool. One release number for all the files per deploy.&lt;/li&gt;
&lt;li&gt;In runtime, when initialising observability service, you tell what release number to use. That information will accompany all reported runtime errors.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Observability services take the release number from the reported errors and use it to look up for the appropriate source map file associated with it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fojrd8lm1zmvnc4i04kew.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fojrd8lm1zmvnc4i04kew.png" alt="a drawing showing how release data is used to associate errors with source map files" width="800" height="534"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That approach is not a silver bullet though and is not 100% reliable because applications in production might use code from different releases. Imagine a long lived large SPA applications with code splitting: a user loads one page with a set of scripts in the morning and another page with another set of scripts in the evening. Two sets of scripts might belong to two different releases, but the running application keeps the same release number since the initialisation time. That means that errors for later loaded scripts might have wrongly associated source maps.&lt;/p&gt;

&lt;p&gt;Beside that problem with reliability, such approach also doesn’t fit the use case of microfrontends. Each team deploys their microfrontend independently and has their our own releases. What release should the final application use when initialised? One can come up with workarounds (that is what I was doing), but that will just add more to the overall complexity around the source maps handling and make the reliability problem even worse.&lt;/p&gt;

&lt;h3&gt;
  
  
  Debug IDs to the Rescue!
&lt;/h3&gt;

&lt;p&gt;Sentry has come up with a really neat and elegant solution to the above problems.&lt;br&gt;
Instead of generating releases and assigning them when uploading source-maps and initialising the application, one simply includes an additional post-bundling step - running the command&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sentry-cli sourcemaps inject ./dist
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;which will generate a unique &lt;code&gt;debug-id&lt;/code&gt; identifier to each script in the output &lt;code&gt;./dist&lt;/code&gt; folder and include it in the script files as well as into the accompanying source map files. And then proceed with normal deployment and source maps upload.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F45y5nryc84nyshn6fwhm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F45y5nryc84nyshn6fwhm.png" alt='a screenshot from the output of "sentry-cli sourcemap inject" command' width="800" height="287"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With that, every stack frame will include &lt;code&gt;debug-id&lt;/code&gt; in addition to function name, filename, and location. It will be used to associate a particular stack frame (i.e. function call) with a particular source map. That solves the reliability problems: all the files self-contain the necessary for the association information. No need for dances around releases.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6w5tm2z2v7zhnctwbp14.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6w5tm2z2v7zhnctwbp14.png" alt="a drawing showing how Sentry associates error stack frames with source map files" width="800" height="501"&gt;&lt;/a&gt;&lt;br&gt;
(Source: sentry.io)&lt;/p&gt;

&lt;p&gt;If you’re using Sentry and have either used the releases approach or haven’t set up the source maps yet, I encourage checking out the new approach. It has never been easier to use source maps with observability tools.&lt;/p&gt;

&lt;p&gt;Not all tools support that &lt;code&gt;debug-id&lt;/code&gt; approach yet. I checked Datadog docs and they still suggest using releases.&lt;/p&gt;

&lt;p&gt;Hopefully situation will change soon and all other observability services adopt similar approach.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>sentry</category>
      <category>observability</category>
    </item>
    <item>
      <title>Cloudflare Workers: Unpacking My Fundamental Misunderstanding</title>
      <dc:creator>Alexey Antipov</dc:creator>
      <pubDate>Fri, 09 Feb 2024 13:49:09 +0000</pubDate>
      <link>https://dev.to/aantipov/cloudflare-workers-unpacking-my-fundamental-misunderstanding-5hjm</link>
      <guid>https://dev.to/aantipov/cloudflare-workers-unpacking-my-fundamental-misunderstanding-5hjm</guid>
      <description>&lt;p&gt;I’ve fundamentally misunderstood how Cloudflare Workers work.&lt;/p&gt;

&lt;p&gt;While setting up Sentry for my &lt;a href="https://notion-google-tasks-sync.com/"&gt;Notion&amp;lt;=&amp;gt;Google Tasks&lt;/a&gt; integration app, I noticed that a single Issue in Sentry contained data (”breadcrumbs”) from different requests to the Cloudflare Worker. This was a startling discovery indicating a data leak between Workers requests.&lt;/p&gt;

&lt;p&gt;That implied either of two things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cloudflare has a bug in their “beta” &lt;a href="https://developers.cloudflare.com/queues/"&gt;Queues Workers&lt;/a&gt; product&lt;/li&gt;
&lt;li&gt;My fundamental knowledge and assumptions about Cloudflare Workers have been wrong for years since their introduction.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After a brief conversation in their Discord channel and with the Director of Product for Cloudflare Workers, I learnt that my understanding had been wrong.&lt;/p&gt;

&lt;p&gt;I had believed that every Worker invocation is executed in a completely isolated environment, with a new V8 isolate created for each call and discarded right after the call is finished. This assumption led me to think that there was no need to worry about possible data leaks between calls, a problem inherent in most (all?) serverless platforms. This feature was one of the primary reasons I was excited about Workers.&lt;/p&gt;

&lt;p&gt;However, the reality is that Workers isolates are actually shared between calls, which explains why a single exception in Sentry contained data from multiple calls.&lt;/p&gt;

&lt;p&gt;Where did I get such misunderstanding from?&lt;/p&gt;

&lt;p&gt;Part of the blame lies with Cloudflare’s &lt;a href="https://developers.cloudflare.com/workers/reference/how-workers-works/"&gt;How Workers works&lt;/a&gt; page, which, up to this day, has contained a wrong statement:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Workers processes are able to run essentially limitless scripts with almost no individual overhead by creating an isolate for each Workers function call”.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fakzy5r3bj3s1zysicrqf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fakzy5r3bj3s1zysicrqf.png" alt="An excerpt from Cloudflare's &amp;quot;How Workers works&amp;quot; page with the highlighted words &amp;quot;creating an isolate for each Workers function call&amp;quot;" width="800" height="290"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I have created an &lt;a href="https://github.com/cloudflare/cloudflare-docs/issues/12885"&gt;issue&lt;/a&gt; in their docs repository to address that misleading statement.&lt;/p&gt;

&lt;p&gt;The other part of the blame is on me for not thoroughly reading that page from top to bottom, failing to spot the contradiction and not validating my assumptions.&lt;/p&gt;

&lt;p&gt;That revelation makes me to rethink my approach to building Workers apps.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>serverless</category>
      <category>javascript</category>
      <category>cloudflare</category>
    </item>
    <item>
      <title>A Tribute to React</title>
      <dc:creator>Alexey Antipov</dc:creator>
      <pubDate>Mon, 29 Jan 2024 15:14:16 +0000</pubDate>
      <link>https://dev.to/aantipov/a-tribute-to-react-2dci</link>
      <guid>https://dev.to/aantipov/a-tribute-to-react-2dci</guid>
      <description>&lt;p&gt;&lt;strong&gt;Attention: This article may cause discomfort, particularly for individuals not from the React community. No offense is intended. Proceed at your own risk.&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Preface&lt;/strong&gt;. There was a surge in criticism towards React over the past year. Complaints ranged from reluctance of React's core members to adopt the trending Reactivity API, to confusion over React Server Components. IMO, the significance of the issues is often exaggerated while the objective proposition value of React is neglected or diminished. In this article, I want to highlight one particular aspect of React that is hard to overestimate but often overlooked and taken for granted.&lt;/p&gt;




&lt;p&gt;Ask yourself: &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What has been the main pain point of frontend development in the past decade?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For many, it's the ever-changing landscape of frontend development and the need to constantly re-learn, rewrite and fight with broken npm updates. It has become normal for a hyped and trendy technology to fall out of fashion within 2-3 years, get abandoned and become a tech debt burden for projects that relied on it.&lt;/p&gt;

&lt;p&gt;We are lucky if our use of the guilty library is limited and the solution is straightforward. But dealing with more complex situations can become a nightmare causing the notorious fatigue. Many of us bear the scars from migrating AngularJS and Vue apps when those frameworks couldn't find a way for further gradual evolution and seamless updates. Those unlucky enough to have bet on these frameworks felt the impact: businesses, developers, library authors.&lt;/p&gt;

&lt;p&gt;Is there any major frontend technology that has stayed relevant for over a decade, never broken its ecosystem, evolved gradually, and continued growing in popularity? Is there such a bastion in this volatile field?&lt;/p&gt;

&lt;p&gt;The obvious answer is &lt;strong&gt;React&lt;/strong&gt;. It's a true phenomenon.&lt;/p&gt;

&lt;p&gt;React has earned a reputation as the least risky technology choice for frontend development. It has so far been the safest bet for business owners, developers and library authors.&lt;/p&gt;

&lt;p&gt;With huge momentum, React continues to evolve, and its ecosystem thrives. IMO, the apprehensions about React's state of affairs are exaggerated.&lt;/p&gt;

&lt;p&gt;Why did React make it where many others fell short? Perhaps it is the initial ingenious design principles of React, that helped it stay flexible and evolve gradually. Or maybe it is the React team's ingenious, pragmatic and cautious architectural decisions along the way. Likely, it's a combination of both.&lt;/p&gt;

&lt;p&gt;Let's appreciate React for being such a phenomenon.&lt;/p&gt;

&lt;p&gt;Let's appreciate the immense achievement of React's team.&lt;/p&gt;

&lt;p&gt;The team has truly deserved better treatment, more respect, more trust and more patience from us.&lt;/p&gt;

&lt;p&gt;Be nice. Be cute.&lt;/p&gt;

&lt;p&gt;THANK YOU REACT! LONG LIVE REACT! 🤘&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>react</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Beyond Frontend vs Backend: The Case for Fullstack Development</title>
      <dc:creator>Alexey Antipov</dc:creator>
      <pubDate>Wed, 24 Jan 2024 15:07:00 +0000</pubDate>
      <link>https://dev.to/aantipov/beyond-frontend-vs-backend-the-case-for-fullstack-development-3dk5</link>
      <guid>https://dev.to/aantipov/beyond-frontend-vs-backend-the-case-for-fullstack-development-3dk5</guid>
      <description>&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fafvlu8islecltw70h3tu.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fafvlu8islecltw70h3tu.jpeg" alt="a meme depicting a person sitting at a desk outdoors with a coffee cup in hand and a sign in front that reads 'Fullstack &amp;gt; Frontend + Backend CHANGE MY MIND.'" width="577" height="432"&gt;&lt;/a&gt;&lt;br&gt;
We are accustomed to the idea of distinct roles for Backend and Frontend development.&lt;/p&gt;

&lt;p&gt;I used to like the idea and never undermined it; Fullstack development never appealed to me and I never believed in its effectiveness. Through most of my career I advocated for specialisation and perfection in a specific area. I chose Frontend Development to put all my efforts into.&lt;/p&gt;

&lt;p&gt;Recently, though, I started getting increasingly convinced that such specialisation often does more harm than benefits to both parties - businesses and developers.&lt;/p&gt;

&lt;p&gt;Why?&lt;/p&gt;

&lt;p&gt;Firstly, that conviction has grown out of frustration of being a highly skilled and specialised Frontend developer. Deep knowledge and skills definitely helped me a lot in passing many interviews and acquiring confidence. But I rarely had a chance to make use of them at work. Most companies need just basic Frontend skills, they want you to build basic UIs, implement form validation, and apply component libraries. They don’t care about how readable, performant, maintainable and scalable code you write. They care only about the end result - delivering features. Honing frontend skills and perfecting in the ever-changing Frontend landscape was apparently not a good investment of my time and efforts.&lt;/p&gt;

&lt;p&gt;Secondly, I’ve got to learn that some (most?) companies are really bad at managing their human resources and work load. I’ve seen many situations when frontend devs were out of work and were tasked to come up with some useless work to make themselves busy or had to learn and switch to backend development for a while until they have some frontend tasks. Examples of backend folks having too much on their plate were common as well. Both types of situations led to frustration and/or developers’ overwork. And I can see Why such situations are common - having separate Frontend and Backend developer roles means a lot of extra work of setting up proper communication and handover processes, separate interview and performance review processes, just to mention a few. There is simply too much overhead.&lt;/p&gt;

&lt;p&gt;The trigger for changing my mind, though, was a story of ArsDigita company and its cofounder Philip Greensup that I read in the “&lt;a href="http://www.foundersatwork.com/"&gt;Founders at work&lt;/a&gt;” book by Jessica Livingston. The company had a quite unique way of treating developers, utilising and growing their skills. Here are some quotes to give you an idea:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;we tried to help each programmer develop an independent, professional reputation. We had this idea that programmers could be professionals, like doctors or lawyers, and, to that end, we wanted the programmers to be real engineers - to sit down face to face with the customer, find out what was needed … and then take a lot of responsibility for making it happen.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;--&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;We pushed the profit-and-loss responsibility down to individual teams. For example, if there were two or three programmers working for HP, then those guys would be solely responsible for the project and making sure that it got delivered on time and that the customer was happy … Implicit in that was that, if it didn’t go well, we’d know whom to blame.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;--&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;For programmers, I had a vision … that I didn’t like the way that programmer careers turned out. Now that I fly airplanes, I realize that the average programmer is really much less happy in his/her job than the average airplane mechanic… For $30,000 and a year and a half, you can become an airplane mechanic. You work in a small group, you meet the customer directly. You don’t have the alienation from the customer….&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;--&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Airplane mechanics have a direct interaction with the customer. A lot of jobs require two to three people, so it’s kind of social, and I noticed that they’re just really happy. Programmers are isolated. They sit in their cubicle; they don’t think about the larger picture. To my mind, a programmer is not an engineer… If you look at civil engineers, architects, they’re all dealing directly with the customer and going through the whole process.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;--&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The programmers were in the corner doing what they were told. That’s one reason they were so easy to outsource. If a programmer really never talks to the customer, never thinks, just solves puzzles, well, that’s a perfect candidate for something to offshore.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;--&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You never really know what most programmers have accomplished. There are a handful of people that you can say that about. Linus Torvalds built the Linux kernel, but it’s hard to say what the average programmer working at a big company has ever accomplished… the projects are so big and their contributions were so small.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;All that reasoning and observation lead me to change my mind. I realise now what most companies really need are fullstack generalists, rather than specialists. They need developers who are responsible for the whole cycle of product development, end-to-end. It’s much easier, cheaper and more efficient to manage a small team of generalists than a bunch of specialists. And developers, having and exercising that greater responsibility would be motivated better to deliver high quality results and would have more opportunities for creative work and clever solutions. It would be much easier for them to prove their accomplishments and grow professionally. And their job would be more satisfying.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>frontend</category>
      <category>backend</category>
    </item>
    <item>
      <title>Handling JSON data in SQL Databases</title>
      <dc:creator>Alexey Antipov</dc:creator>
      <pubDate>Wed, 17 Jan 2024 15:05:06 +0000</pubDate>
      <link>https://dev.to/aantipov/handling-json-data-in-sql-databases-4e8b</link>
      <guid>https://dev.to/aantipov/handling-json-data-in-sql-databases-4e8b</guid>
      <description>&lt;p&gt;I was implementing error handling in my &lt;a href="https://notion-google-tasks-sync.com/"&gt;Notion-Gtasks Sync&lt;/a&gt; app and I needed to store 3 additional data items in the database:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;last error message&lt;/li&gt;
&lt;li&gt;number of consecutive errors&lt;/li&gt;
&lt;li&gt;next retry time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The idea was to implement exponential backoff for synchronisation attempts, limit the total number of attempts to 10 and decrease the number of duplicate errors reported to Sentry (I’m using a free plan). Therefore I had the following requirements for my error data handling:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;be able to filter out rows with more than 10 errors&lt;/li&gt;
&lt;li&gt;be able to reset all the error related data on successful sync&lt;/li&gt;
&lt;li&gt;increment the error count upon encountering a new error&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I’m an amateur SQL engineer and I decided to fulfil data requirements by creating 3 additional columns in the &lt;code&gt;users&lt;/code&gt; table:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;sync_error_msg&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sync_error_num&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sync_error_next_retry_time&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It seemed straightforward, but something didn’t feel right. The thing is that all three fields are, by nature, tightly coupled - when setting, resetting or updating one of them, the others should follow suit. Placing the fields in separate columns breaks their ties at storage level, and we must rely on the code logic exclusively to maintain their relationships. It didn’t seem like a reliable solution; instead, it seemed error-prone. As the code and database schema evolve over time, it can be challenging to maintain those ties. Something more reliable and less error-prone was needed. Something like encapsulation, maybe?&lt;/p&gt;

&lt;p&gt;Reading through my database provider’s docs (Cloudflare D1) I found a &lt;a href="https://developers.cloudflare.com/d1/learning/querying-json/"&gt;page&lt;/a&gt; describing sql functions to handle JSON data (based on &lt;a href="https://www.sqlite.org/json1.html"&gt;SQLite JSON extension&lt;/a&gt;). It seemed perfect for my data handling requirements, allowing me to maintain relationships between the fields. I could store all my sync error-related data in a single column as a JSON object, as a single chunk of data.&lt;/p&gt;

&lt;p&gt;I validated the idea and it yielded positive results!&lt;/p&gt;

&lt;p&gt;Here are some of the implementation details.&lt;/p&gt;

&lt;p&gt;I created a single column &lt;code&gt;sync_error&lt;/code&gt; to store a json object with the error-related data.&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="c1"&gt;// my sql table schema definition using drizzle-orm&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sqliteTable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;users&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="p"&gt;...&lt;/span&gt;
 &lt;span class="na"&gt;syncError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sync_error&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="na"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;json&lt;/span&gt;&lt;span class="dl"&gt;'&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;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;num&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Number of consecutive sync errors&lt;/span&gt;
  &lt;span class="nl"&gt;nextRetry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;number&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Timestamp in ms. Exponential backoff. Null if no retries left. Max 10 retries within 5 days&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;// Last sync error data. Reset to null on successful sync&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 defined code logic to set &lt;code&gt;nextRetry&lt;/code&gt; prop to &lt;code&gt;null&lt;/code&gt; when the prop value reaches &lt;code&gt;10&lt;/code&gt; - such data should not be included in the sync dataset. The query for selecting users for next sync (a simplified version of it):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt;
    &lt;span class="n"&gt;sync_error&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
    &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="n"&gt;json_extract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sync_error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'$.nextRetry'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;strftime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'%s'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'now'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Explanation: If &lt;code&gt;nextRetry&lt;/code&gt; is &lt;code&gt;null&lt;/code&gt;, the &lt;code&gt;json_extract&lt;/code&gt; function returns &lt;code&gt;NULL&lt;/code&gt;. In SQL, any comparison involving &lt;code&gt;NULL&lt;/code&gt; (like &amp;lt; or &amp;gt;) results in &lt;code&gt;NULL&lt;/code&gt; and the row is not included in the dataset for the next sync. Otherwise, if the current time is past the time of the next retry, the row is included in the result dataset and another sync attempt will be made. Pretty neat, isn’t it?&lt;/p&gt;

&lt;p&gt;SQL JSON Extension is very powerful and provides many tools not just for filtering, but also for querying and setting data.&lt;/p&gt;

&lt;p&gt;In my case, to update the JSON data I first query the data, then form a new json object in my code and then set it in my table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;sync_error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'{"msg": "...", ...}'&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If I would need to update just one property inside that json object, I could do it directly using &lt;code&gt;json_insert&lt;/code&gt; function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;sync_error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json_insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sync_error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'$.msg'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'error msg'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In certain situations it might not be performant to use json functions in WHERE or SELECT clauses. To mitigate that performance issue one might create a computed (generated) column to store the extracted value. Computed columns can also be indexed to further improve query performance. It is not needed in my case, so I keep it simple.&lt;/p&gt;

&lt;p&gt;Hope my findings can be useful to some.&lt;/p&gt;

&lt;p&gt;If there are SQL veterans out there who notice issues with my approach and/or have suggestions, I’m keen to know and learn from you - please provide your opinion and share your experience working with JSON functions.&lt;/p&gt;

</description>
      <category>json</category>
      <category>sql</category>
      <category>database</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Prevent ChatGPT from Using Your Data: A Privacy Guide</title>
      <dc:creator>Alexey Antipov</dc:creator>
      <pubDate>Tue, 16 Jan 2024 15:04:00 +0000</pubDate>
      <link>https://dev.to/aantipov/prevent-chatgpt-from-using-your-data-a-privacy-guide-p7g</link>
      <guid>https://dev.to/aantipov/prevent-chatgpt-from-using-your-data-a-privacy-guide-p7g</guid>
      <description>&lt;p&gt;Last week OpenAI &lt;a href="https://openai.com/blog/introducing-chatgpt-team" rel="noopener noreferrer"&gt;released&lt;/a&gt; a new ChatGPT plan - Teams plan. The new plan has one specific feature:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No training on your data&lt;/li&gt;
&lt;/ul&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%2Fuploads%2Farticles%2Fl8hg8ugd3aryz91ztkrf.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%2Fuploads%2Farticles%2Fl8hg8ugd3aryz91ztkrf.png" alt="a screenshot showing comparison between the two ChatGPT plans, Plus and Teams"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It struck me because I had previously assumed that Plus users’ data was not used for training by Open AI. Apparently, I was wrong.&lt;/p&gt;

&lt;p&gt;I use ChatGPT heavily not just for work, but also for personal matters. I don’t feel comfortable knowing that it uses my data for some training and, potentially, shares it with the world.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The data I share with ChatGPT should be private.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I don’t want to use the more expensive Teams plan just for its privacy feature. I wondered if there is any other way of opting out of training?&lt;/p&gt;

&lt;p&gt;I dug in and found two ways.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The first approach&lt;/strong&gt; is using a setting in ChatGPT Data Controls. This is the solution that OpenAI promotes and recommends:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;go to “Settings &amp;amp; Beta” ⇒ “Data Controls” ⇒ “Chat History &amp;amp; training” and turn the setting off.&lt;/li&gt;
&lt;/ul&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%2Fuploads%2Farticles%2F56uufl4fgxct5612paqa.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%2Fuploads%2Farticles%2F56uufl4fgxct5612paqa.png" alt="a screenshot of ChatGPT "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This approach has one significant drawback - it’s coupled with the “Chat History” feature meaning that you can’t turn the training off and keep Chat History. You either have both or don’t. Even more so, this setting is not synced across devices: if you turn it off in your Browser, it won’t apply to you mobile app. More about that setting &lt;a href="https://help.openai.com/en/articles/7730893-data-controls-faq" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Everything suggests that OpenAI intentionally designed this opt-out feature in a way that users won't use it, and even if they do use it, then it will work only partially.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The second approach&lt;/strong&gt;, the real 💪 approach, is not easy to find. It's mentioned only briefly in OpenAI FAQ. What you need is&lt;/p&gt;

&lt;p&gt;• go to &lt;a href="http://privacy.openai.com/policies" rel="noopener noreferrer"&gt;privacy.openai.com/policies&lt;/a&gt; page and submit a “Privacy Request” there.&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%2Fuploads%2Farticles%2Fa9qg56n8992jfchij79k.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%2Fuploads%2Farticles%2Fa9qg56n8992jfchij79k.png" alt="a screenshot of the OpenAI Privacy Request Portal"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After submission you’ll soon be notified that your data is no longer used for training. This is exactly what we need: the ability to opt of Training across all devices, while still retaining all of ChatGPT's features.&lt;/p&gt;

</description>
      <category>chatgpt</category>
      <category>privacy</category>
    </item>
    <item>
      <title>A year as Staff Frontend Engineer</title>
      <dc:creator>Alexey Antipov</dc:creator>
      <pubDate>Fri, 03 Mar 2023 13:00:00 +0000</pubDate>
      <link>https://dev.to/aantipov/a-year-as-staff-frontend-engineer-33a</link>
      <guid>https://dev.to/aantipov/a-year-as-staff-frontend-engineer-33a</guid>
      <description>&lt;p&gt;A year has passed since I joined &lt;a href="https://www.goflink.com/" rel="noopener noreferrer"&gt;Flink&lt;/a&gt; as a Staff Frontend Engineer.&lt;br&gt;&lt;br&gt;
Now is time to look back and reflect.&lt;/p&gt;

&lt;h2&gt;
  
  
  Intro
&lt;/h2&gt;

&lt;p&gt;When I was joining &lt;a href="https://www.goflink.com/" rel="noopener noreferrer"&gt;Flink&lt;/a&gt;, a grocery delivery startup with a &lt;a href="https://techcrunch.com/2021/12/09/flink-the-berlin-based-instant-grocery-startup-is-now-valued-at-2-85b-after-raising-750m-in-a-round-led-by-doordash/" rel="noopener noreferrer"&gt;multi-billion&lt;/a&gt; dollar valuation, I had no idea what the role is about. I bought a &lt;a href="https://staffeng.com/book" rel="noopener noreferrer"&gt;book&lt;/a&gt; that offered some insights about the responsibilities of Staff engineers, but the scope seemed too vague, I wasn't sure what I should actually be doing in practice and whether that position was right for me.  &lt;/p&gt;

&lt;p&gt;The change was intimidating and intriguing at the same time.&lt;/p&gt;

&lt;p&gt;Now, having worked for a year at that position, I can certainly say that it was one of the best decisions I ever made, and it was probably the most fruitful year of my career.  &lt;/p&gt;

&lt;p&gt;Over the course of the year, I constantly dragged myself into new areas of work, and I learned and achieved a lot. I built confidence in areas of work that I previously thought were not for me and not in my comfort zone. I discovered that I enjoy and am very good at a lot of other things besides coding. My appetite increased significantly as I made progress.&lt;/p&gt;

&lt;h2&gt;
  
  
  Learnings
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Build relationships and earn credibility
&lt;/h3&gt;

&lt;p&gt;Right from the beginning, I focused on developing trusting relationships with my colleagues, especially with the frontend engineers.  &lt;/p&gt;

&lt;p&gt;I believed I had to first demonstrate my competence, my ability to write quality code, my ability to provide thoughtful PR reviews, my ability to help find bugs and fix them, and my being a person others can rely upon.&lt;/p&gt;

&lt;p&gt;Regular one-on-ones with developers also helped a lot in building relationships. It gave me the opportunity to learn about their struggles, their perspectives on things, and get their feedback on how I performed and how I could help them.&lt;/p&gt;

&lt;p&gt;That helped me a lot going forward in implementing my initiatives and getting buy-in from developers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Don't be a bottleneck
&lt;/h3&gt;

&lt;p&gt;With a good, strong reputation, it's easy to become a bottleneck when people start approaching you directly looking for help. That way, other developers miss an opportunity to help and learn, and you end up spending more time answering questions of all kinds.&lt;/p&gt;

&lt;p&gt;Knowledge sharing is important, and whenever someone needs help, other colleagues should have the chance to provide help and learn from one another's mistakes.&lt;/p&gt;

&lt;p&gt;I created a dedicated Slack channel for frontend engineers, actively promoted it, and redirected questions there.&lt;/p&gt;

&lt;h3&gt;
  
  
  Knowledge sharing and building a community
&lt;/h3&gt;

&lt;p&gt;Often, organizational structure doesn't facilitate cross-team or cross-department collaboration. Developers work on their projects in isolation, which negatively affects their company because the same solutions are constantly being re-implemented and the same issues occur across different projects. Lack of collaboration also means fewer opportunities to learn and grow, which leads to dissatisfaction and a poor retention rate.&lt;/p&gt;

&lt;p&gt;I had been there myself, and I believed that it's very important to build a strong frontend community where developers could be passionate about their work, share knowledge, support one another, and collaborate on shared libraries and projects.&lt;/p&gt;

&lt;p&gt;I organized regular frontend meetups and encouraged developers to present their topics there - to share what issues they have fixed and things they are excited about. I myself regularly shared important information about the company's web landscape, hosting solutions, caching issues, etc.&lt;/p&gt;

&lt;p&gt;I was also looking for opportunities to standardize infrastructure in use and create libraries and tools used by all projects across the company. Besides the obvious benefits of infrastructure reuse and code deduplication, this would give developers common ground to work on, provide an opportunity to create an impact on the whole organization, and create a space for collaboration with other developers. Additionally, switching between projects and onboarding new developers would be simplified.&lt;/p&gt;

&lt;p&gt;Going together to a conference was another thing I was pushing for. You learn new things, get inspired, and become friends with your colleagues. That, in my opinion, is the best use of the development budget. We attended the React Day Berlin and VueJS Amsterdam conferences.&lt;/p&gt;

&lt;h3&gt;
  
  
  Focus on impact and your expertize
&lt;/h3&gt;

&lt;p&gt;When you are new to the company, it can be challenging to understand where to start and what to work on. Even company veterans might face the same difficulty.&lt;/p&gt;

&lt;p&gt;It happened to me as well. I once joined some backend-focused initiative purely out of interest because I did not see interesting and impactful enough areas in frontend engineering. It was a trap, and I am glad that I escaped it.&lt;/p&gt;

&lt;p&gt;What helped me during such moments of doubt was reminding myself that I am a very knowledgeable and experienced frontend engineer and that it would be a shame for the company not to make use of it. There must be something in my area of expertise and interests for me. With that in mind, I explored what was happening in other domains (departments) and what struggles and issues they had. The result of it was me moving to a department in need. I also challenged the status quo in my former department and came up with organizational changes - formation of a web platform team and the transfer of certain projects under its ownership.&lt;/p&gt;

&lt;h3&gt;
  
  
  Your manager is your advocate
&lt;/h3&gt;

&lt;p&gt;The work you do as a Staff Engineer might not be directly visible to the rest of the company. There can be no direct relationship between your work and the company's performance.&lt;/p&gt;

&lt;p&gt;For example, how can you quantify the impact you are making by building a frontend community, setting up a proper interview process, and hiring best-in-class developers?&lt;/p&gt;

&lt;p&gt;It can also be challenging for your manager to properly assess your work if they are in charge of a specific domain and your work far exceeds the scope of that domain. They won't be a good advocate for you, and it can hinder your growth and career.&lt;/p&gt;

&lt;p&gt;The lesson I learned here is to be conscious of who my manager is and make sure they are interested in the work I do and that my work is transparent to them.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I like about my work
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Freedom
&lt;/h3&gt;

&lt;p&gt;The position gives me freedom - I am in charge of deciding what to work on. I am no longer required to perform tedious tasks, like building ui forms. Instead, I focus on challenging, impactful, and full of learnings tasks that let me fully utilize my knowledge and experience.&lt;/p&gt;

&lt;p&gt;Nobody expects me to write code myself and deliver something every day. That gives me time and peace of mind for deep thinking and evaluation of different ideas, to be creative, to work more with people, and to invest my time in them.&lt;/p&gt;

&lt;p&gt;A dream job, isn't it?&lt;/p&gt;

&lt;h3&gt;
  
  
  Power
&lt;/h3&gt;

&lt;p&gt;I am a member of my domain leadership group and my voice is respected. I can influence teams' roadmaps, I can influence the trajectory of web development at the domain level and even at the company level, and I can come up with initiatives for teams to work on.&lt;/p&gt;

&lt;p&gt;Freedom and Power combined make me extremely happy and enable me to be as impactful as I can.&lt;/p&gt;

&lt;h2&gt;
  
  
  Things I wish I had more time for
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Writing code
&lt;/h3&gt;

&lt;p&gt;All pre-Staff positions add value to a company primarily by writing code and implementing features themselves. Staff Engineer position bestow Freedom and Power on you to bring far more value. How? By implementing things that scale, i.e., enabling engineers to do their job, helping them, couching them, redirecting their focus to more important tasks, coming up with architectural changes, and bringing highly skilled developers aboard.&lt;/p&gt;

&lt;p&gt;As a result, I write less code myself, which is something I wish I could do more of. Although I should be an expert in my field, I don't have as much time for practice now. Finding the right balance is important.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Changing my position from Senior to Staff Frontend engineer was a tremendous shift for me.&lt;/p&gt;

&lt;p&gt;It has been a very fruitful, exciting, and full of learnings experience.&lt;/p&gt;

&lt;p&gt;I was able to apply my knowledge and experience on a large scale, and my skill set expanded.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>discuss</category>
    </item>
    <item>
      <title>Widget Driven Development</title>
      <dc:creator>Alexey Antipov</dc:creator>
      <pubDate>Wed, 15 Dec 2021 10:02:48 +0000</pubDate>
      <link>https://dev.to/aantipov/widget-driven-development-1n5m</link>
      <guid>https://dev.to/aantipov/widget-driven-development-1n5m</guid>
      <description>&lt;h2&gt;
  
  
  Preface
&lt;/h2&gt;

&lt;p&gt;When developing UI applications, we compose them from components. Each UI component is essentially a combination of markup, scoped styles, and some UI logic. Data Management is frequently left out of components control, resulting in a complicated architecture with convoluted data flows.&lt;/p&gt;

&lt;p&gt;In this article, I'll demonstrate how we can transform components into autonomous isolated widgets with complete control over both Data Logic and UI.&lt;/p&gt;

&lt;h2&gt;
  
  
  The History of Components
&lt;/h2&gt;

&lt;p&gt;Widgets, in my opinion, are the natural successors of Components. To see this, I suggest going back in time and look at how our approaches to building UI have evolved over time.&lt;/p&gt;

&lt;p&gt;Many remember the times when all application styles were defined in a single global CSS file. Styles definitions used complex combinations of different &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors" rel="noopener noreferrer"&gt;CSS selectors&lt;/a&gt;. Style collisions were common in apps. The size and complexity of those styles sometimes affected even the performance of websites.&lt;/p&gt;

&lt;p&gt;In 2009 &lt;a href="https://en.bem.info/methodology/" rel="noopener noreferrer"&gt;BEM&lt;/a&gt; was born. BEM provided a set of guidelines for defining styles and naming classes. These rules were intended to address the problem of style collisions and inefficient selectors. BEM encouraged to think of UI in terms of blocks, elements, and modifiers.&lt;/p&gt;

&lt;p&gt;2013-2015 marked the rise of &lt;strong&gt;Components&lt;/strong&gt; approach. React made it simple to divide UI into components that were a combination of markup (HTML) and UI logic (JavaScript). It was a game changer in application development. Other frameworks soon followed suit, adopting a components-based approach as well.&lt;/p&gt;

&lt;p&gt;With the rise of build tools, CSS preprocessors, and techniques like CSS-in-JS and &lt;a href="https://github.com/css-modules/css-modules" rel="noopener noreferrer"&gt;CSS Modules&lt;/a&gt;, it became feasible to make Styling a part of Components.&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%2Fuploads%2Farticles%2Fzh3opup6i4335y20e5x0.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%2Fuploads%2Farticles%2Fzh3opup6i4335y20e5x0.png" alt="a combination of Markup, Styles and UI Logic"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Components playgrounds such as Storybook emerged to help developers build components in an isolated environment and ensure proper styles scoping. They encouraged developers to think of UI as &lt;a href="https://www.kn8.lt/blog/ui-is-a-function-of-data/" rel="noopener noreferrer"&gt;a function of state&lt;/a&gt;: components props values defined the look and behaviour of components.&lt;/p&gt;

&lt;p&gt;Collections of reusable high-quality components became a thing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The unresolved hurdles
&lt;/h2&gt;

&lt;p&gt;Component-driven approach helped break UI into isolated reusable pieces and enabled building large-scale applications using collections of pre-built components.&lt;/p&gt;

&lt;p&gt;What was missing, though, is a way of supplying UI components with data.&lt;/p&gt;

&lt;p&gt;Data management became one of the most difficult tasks in Frontend Engineering and the primary contributor to the complexity of UI apps.&lt;/p&gt;

&lt;p&gt;We learned to split components into two types:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Presentational&lt;/em&gt; components, which are responsible for UI representation and are usually stateless and side-effect free&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Container&lt;/em&gt; components, which deal with data-related logic and pass data down to Presentational components.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All that remains is to define how Container components should work with data.&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%2Fuploads%2Farticles%2Fkbbvb9ncnr6bvhcozu5i.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%2Fuploads%2Farticles%2Fkbbvb9ncnr6bvhcozu5i.png" alt="an image of Container component that includes a Presentational component and Data logic"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Naive approach
&lt;/h2&gt;

&lt;p&gt;The naive approach would be for each Container component to simply fetch data needed by underlying Presentational components.&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%2Fuploads%2Farticles%2Fxq10jejpvyf61rjendjb.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%2Fuploads%2Farticles%2Fxq10jejpvyf61rjendjb.png" alt="an image of Frontend and Backend interaction"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Because the same data is usually needed by multiple different components, implementing such an approach in practice would bring a bunch of problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;duplicated requests and data over-fetching. Slow UI and overloaded server as a result.&lt;/li&gt;
&lt;li&gt;possible data inconsistency between components when requests to the same endpoint result in different data&lt;/li&gt;
&lt;li&gt;complicated data invalidation (think of a case when the data has changed on Backend and you need to make sure every dependent component refetches the data)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Common parent approach
&lt;/h2&gt;

&lt;p&gt;We learnt to overcome the problem by moving data-fetching (and mutation) functionality up to common parent components which pass data down to all underlying components.&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%2Fuploads%2Farticles%2Fefp3s243lqvo1xyykyy2.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%2Fuploads%2Farticles%2Fefp3s243lqvo1xyykyy2.png" alt="an image of Frontend and Backend interaction + Prop Drilling between Components"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We solved the problem of requests duplication and data invalidation. However, we did face new challenges:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the whole application logic became more complex and more coupled&lt;/li&gt;
&lt;li&gt;we were forced to pass data down through multiple components. This issue became notorious and got a name &lt;a href="https://kentcdodds.com/blog/prop-drilling" rel="noopener noreferrer"&gt;“Prop Drilling”&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The State Management approach
&lt;/h2&gt;

&lt;p&gt;To bypass the Prop Drilling problem, we learnt to use State Management libraries and techniques: instead of propagating data down to underlying components, we place data in some Store that is accessible to all the components down the tree, letting them obtain the data directly from there. Components subscribe to the changes in the Store to have the data always up-to-date.&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%2Fuploads%2Farticles%2Fxcimiwli8pey0xk4c6gw.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%2Fuploads%2Farticles%2Fxcimiwli8pey0xk4c6gw.png" alt="an image of Frontend and Backend interaction + Global Store"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Prop Drilling issue was resolved, but not for free:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;we now have to deal with a completely new concept, the Store, and care about a bunch of new things, such as designing and maintaining Store structure, appropriately updating data in the Store, data normalization, mutable vs immutable, a single store vs multiple stores, and so on.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;state management libraries require us to learn a new vocabulary: &lt;em&gt;Actions&lt;/em&gt;, &lt;em&gt;Action Creators&lt;/em&gt;, &lt;em&gt;Reducers&lt;/em&gt;, &lt;em&gt;Middlewares&lt;/em&gt;, &lt;em&gt;Thunks&lt;/em&gt;, and so on.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;the introduced complexity and lack of clarity forced developers to create &lt;a href="https://redux.js.org/style-guide/style-guide" rel="noopener noreferrer"&gt;styleguides&lt;/a&gt; on how to work with the Store, what to do and what to avoid.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;as a result, our applications became very tangled and coupled. Frustrated developers try to mitigate the issues by inventing new state management libraries with different syntax.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Naive approach reimagined
&lt;/h2&gt;

&lt;p&gt;Can we do better? Is there an easier way to approach data management? Can we have the data flows transparent and easy to understand? Can we untangle our apps and boost &lt;a href="https://www.freecodecamp.org/news/orthogonality-in-software-engineering/" rel="noopener noreferrer"&gt;orthogonality&lt;/a&gt;? Can we bring Data Logic under control of Components in the same way that we have done with Markup, Styles and UI Logic?&lt;/p&gt;

&lt;p&gt;We must have gotten too far into the woods and &lt;em&gt;can't see the forest for the trees&lt;/em&gt;. Let's go back to the starting point, to the Naive approach, and see if we can solve its problems differently.&lt;/p&gt;

&lt;p&gt;The main bummers there were requests duplication and data inconsistency.&lt;/p&gt;

&lt;p&gt;What if we could have an intermediate player between our components and Backend, say an API wrapper or interceptor, solving all those problems under the hood:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;deduplicate all the requests&lt;/li&gt;
&lt;li&gt;ensure data consistency: all the components should always have the same data when using the same request&lt;/li&gt;
&lt;li&gt;provide data invalidation ability: if a component changes data on the server, other components that rely on that data should receive the new data&lt;/li&gt;
&lt;li&gt;be transparent to components and not affect their logic in any way (make components think they communicate to Backend directly)&lt;/li&gt;
&lt;/ul&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%2Fuploads%2Farticles%2Fkyhk7wq6t9vw6t72zp7n.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%2Fuploads%2Farticles%2Fkyhk7wq6t9vw6t72zp7n.png" alt="Frontend and Backend interaction using an API Wrapper"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The good news is that we can have it, and there are already libraries providing such solutions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;some GraphQL clients, e.g. &lt;a href="https://relay.dev/" rel="noopener noreferrer"&gt;Relay&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://react-query.tanstack.com/" rel="noopener noreferrer"&gt;React-Query&lt;/a&gt;, &lt;a href="https://swr.vercel.app/" rel="noopener noreferrer"&gt;SWR&lt;/a&gt;, &lt;a href="https://redux-toolkit.js.org/rtk-query/overview" rel="noopener noreferrer"&gt;Redux Toolkit Query&lt;/a&gt;, &lt;a href="https://vue-query.vercel.app/" rel="noopener noreferrer"&gt;Vue Query&lt;/a&gt; for RESTful APIs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All we basically need to do is to wrap every API call with such an API Wrapper. The rest is handled automatically for us.&lt;/p&gt;

&lt;p&gt;The huge benefit of such an approach is that we can finally untangle our applications' data logic, put Data Logic under control of Components, and achieve better orthogonality by combining all pieces together.&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%2Fuploads%2Farticles%2Fhgfzyv0h6wd0hjjlmbdw.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%2Fuploads%2Farticles%2Fhgfzyv0h6wd0hjjlmbdw.png" alt="a triangle including Data Logic, Styles, Markup and UI Logic"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Widget driven development
&lt;/h2&gt;

&lt;p&gt;In my team, we started to use the described above Naive approach together with React Query and we love it. It enabled us to approach building our application differently. Let me call it &lt;em&gt;"Widget Driven Development"&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The idea is that we split every page into so-called &lt;em&gt;widgets&lt;/em&gt;, which behave autonomously and are self-contained.&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%2Fuploads%2Farticles%2F9z42221shfvhv142e0ji.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%2Fuploads%2Farticles%2F9z42221shfvhv142e0ji.png" alt="a Page separated into Widgets"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Every widget is responsible for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;fetching and providing all the required data to its UI&lt;/li&gt;
&lt;li&gt;mutating the related data on server if needed&lt;/li&gt;
&lt;li&gt;data representation in the UI&lt;/li&gt;
&lt;li&gt;UI for Loading state&lt;/li&gt;
&lt;li&gt;(optional) UI for Error state&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Speaking of code organization, we co-locate all the widgets related files:&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%2Fuploads%2Farticles%2Fuietsdgrqj2bavtvoc9d.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%2Fuploads%2Farticles%2Fuietsdgrqj2bavtvoc9d.png" alt="an image of files grouped into widgets"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Usually, the same API endpoint is used across multiple widgets. So we decided to keep all of them in a separate shared folder.&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%2Fuploads%2Farticles%2Fwmhjdnzzbw5xskb2f36x.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%2Fuploads%2Farticles%2Fwmhjdnzzbw5xskb2f36x.png" alt="an image of queries hooks grouped under "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We use React Query library and each file in the &lt;code&gt;queries/&lt;/code&gt; folder exposes fetch and mutation methods wrapped into React Query.&lt;/p&gt;

&lt;p&gt;All Container components have a similar code structure.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useParams&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-router-dom&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useBookQuery&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;queries/useBookQuery&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useAuthorQuery&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;queries/useAuthorQuery&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Presentation&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./Presentation&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Loading&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./Loading&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./Error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nc"&gt;BookDetailsContainer&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;bookId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useParams&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="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;book&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;isError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;isBookError&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useBookQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bookId&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="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;author&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;isError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;isAuthorError&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useAuthorQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;book&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;author&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="nx"&gt;book&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;author&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Presentation&lt;/span&gt; &lt;span class="nx"&gt;book&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;book&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;author&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;author&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isBookError&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;isAuthorError&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;Error&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Loading&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice how easy and declaratively dependant queries are handled. Also the only dependency of our widget is the presence of &lt;code&gt;bookId&lt;/code&gt; in the URL.&lt;/p&gt;

&lt;p&gt;Most of our widgets’ container components have no props and rely on no external state except for URL data.&lt;/p&gt;

&lt;p&gt;Such an approach makes it transparent what API queries our widget relies upon. That transparency combined with near to zero external dependencies makes it easy to test widgets and gives us confidence in our code.&lt;/p&gt;

&lt;p&gt;Usually, changes to a widget are limited by modifications to files under that widget's folder. It significantly limits the risk of breaking any other parts of the application.&lt;/p&gt;

&lt;p&gt;Adding new widgets is also very straightforward: create a new folder for the widget with all required files in it and, if necessary, create a new query in the &lt;code&gt;/queries&lt;/code&gt; folder. Again, the risk of breaking any other parts of the application is very limited.&lt;/p&gt;

&lt;p&gt;Every widget can also be easily reused on different pages thanks to the limited dependency on the context. We usually just need to make sure the URLs of those pages contain the data identifiers needed for the widget.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The Components approach made easy and straightforward creation of reusable independent pieces of UI.&lt;br&gt;
It didn't solve all the problems though and Frontend applications often suffer from convoluted Data Management.&lt;/p&gt;

&lt;p&gt;There are libraries that enable approaching Data Management differently and significantly reduce the complexity of our applications.&lt;/p&gt;

&lt;p&gt;Leveraging those libraries, we can put data logic under control of components and convert an application into a set of reusable self-contained widgets. It makes the data flows transparent, architecture flexible, the code resilient and easy to test.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>react</category>
      <category>statemanagement</category>
    </item>
    <item>
      <title>State Management: Separation of Concerns</title>
      <dc:creator>Alexey Antipov</dc:creator>
      <pubDate>Thu, 11 Nov 2021 13:32:31 +0000</pubDate>
      <link>https://dev.to/aantipov/state-management-separation-of-concerns-1e70</link>
      <guid>https://dev.to/aantipov/state-management-separation-of-concerns-1e70</guid>
      <description>&lt;p&gt;State Management in Frontend is complicated and approaches are not yet settled. New state management libraries keep popping up. In search for a silver bullet, libraries and frameworks authors come up with different brilliant APIs and approaches. Developer Community has produced guidelines for many scenarios.&lt;/p&gt;

&lt;p&gt;Nevertheless, developers continue to struggle. Why is that? What do we miss?&lt;/p&gt;

&lt;h2&gt;
  
  
  The Complexities of State Management
&lt;/h2&gt;

&lt;p&gt;UI is a function of state. We make a minor modification to application state &lt;code&gt;setLoading(true)&lt;/code&gt; and the entire UI changes to show the loading indicator.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ewVvY7j6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v0fo57sk8g5btrifppc0.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ewVvY7j6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v0fo57sk8g5btrifppc0.jpeg" alt="Loading Windows XP screen" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Doing State Management right is a key ingredient in achieving great UX.&lt;/p&gt;

&lt;p&gt;However, it is not a trivial task to do. We need to care about a lot of things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;define the Store structure and what to put in the Store&lt;/li&gt;
&lt;li&gt;how and when to initialize and update the Store&lt;/li&gt;
&lt;li&gt;immutable vs mutable&lt;/li&gt;
&lt;li&gt;local vs global&lt;/li&gt;
&lt;li&gt;how to handle dependent state data&lt;/li&gt;
&lt;li&gt;how to represent all possible states of API requests&lt;/li&gt;
&lt;li&gt;how to mock Store in tests&lt;/li&gt;
&lt;li&gt;etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As a result, we usually get&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a lot of imperative and boilerplate code&lt;/li&gt;
&lt;li&gt;components tightly coupled with the Store&lt;/li&gt;
&lt;li&gt;components logic that is scattered over multiple files&lt;/li&gt;
&lt;li&gt;complicated tests&lt;/li&gt;
&lt;li&gt;complicated refactoring&lt;/li&gt;
&lt;li&gt;decreased developer productivity&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Separation Of Concerns
&lt;/h2&gt;

&lt;p&gt;Developers have created a plethora of libraries, techniques, and guideline to overcome or at least mitigate the challenges. And for many, it introduces a new issue: how to navigate between different libraries and techniques? When to use which?&lt;/p&gt;

&lt;p&gt;I recognize an abstraction (perspective) that can be especially valuable when dealing with the subject. This perspective is often missing in discussions about State Management. I am talking about &lt;strong&gt;Separation of Concerns&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In UI we deal with huge amount of data with different characteristics and of different nature. We often treat all the data the same way and use the same approaches and libraries.&lt;/p&gt;

&lt;p&gt;If we apply the principle of Separating Concerns to state handling, then we discover, that&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;not all data are equal&lt;/li&gt;
&lt;li&gt;some data are simple and trivial to handle&lt;/li&gt;
&lt;li&gt;some data are more complex, nuanced and tricky to handle&lt;/li&gt;
&lt;li&gt;there are often specialised libraries which help dealing with the complex data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Having realised that, we can start segregating data and look for specialised tools to manage complex and nuanced data. Those tools usually automate a lot of things we used to do manually and bring relief.&lt;/p&gt;

&lt;p&gt;I find helpful recognizing the following categories of data:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Server State&lt;/li&gt;
&lt;li&gt;Form State&lt;/li&gt;
&lt;li&gt;UI State (excl. Form State)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  UI State vs Server State
&lt;/h2&gt;

&lt;p&gt;The first and foremost data separation should be made between UI State and Server State data because their characteristics differ greatly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;UI State&lt;/strong&gt; includes all the data that is not persistent and is not stored on Server.&lt;/p&gt;

&lt;p&gt;It is stored &lt;strong&gt;locally in Browser&lt;/strong&gt; and is normally reset on page reload.&lt;/p&gt;

&lt;p&gt;The data is &lt;strong&gt;synchronous&lt;/strong&gt; (mostly). Changes to data are “immediate” and there is no intermediate state, we don’t need to wait for new data to come. Whenever the state change happens, we always know the new state.&lt;/p&gt;

&lt;p&gt;Most UI State changes are triggered by user actions - “click”, “hover”, “scroll”, etc.&lt;/p&gt;

&lt;p&gt;Examples of UI State:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;dark/light mode&lt;/li&gt;
&lt;li&gt;filters state&lt;/li&gt;
&lt;li&gt;forms validation state&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Server State&lt;/strong&gt; data is stored permanently on Server. UI needs to fetch data from Server and send changes back to Server.&lt;/p&gt;

&lt;p&gt;Server State is &lt;strong&gt;Asynchronous&lt;/strong&gt;. UI needs to fetch it and that takes time. We don’t know upfront how long it takes and what the value will be. We don’t even know if the request will be successful. The same applies when we need to update the state and persist it on Server.&lt;/p&gt;

&lt;p&gt;Another major characteristic of Server Data State - it is &lt;strong&gt;remote&lt;/strong&gt; and it is not under our control. It has &lt;strong&gt;shared ownership&lt;/strong&gt;. Anyone and anything can change data on the Server without our knowledge. It means, that we don’t know for sure if the fetched data is up to date.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solutions for UI State
&lt;/h2&gt;

&lt;p&gt;Most of existing state management libraries (e.g. &lt;a href="https://redux.js.org/"&gt;Redux&lt;/a&gt;, &lt;a href="https://mobx.js.org/"&gt;MobX&lt;/a&gt;, &lt;a href="https://zustand.surge.sh/"&gt;Zustand&lt;/a&gt;) and approaches are tailored to handle synchronous UI State data:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;define and initialize the Store&lt;/li&gt;
&lt;li&gt;update data in the Store&lt;/li&gt;
&lt;li&gt;subscribe to changes in the Store&lt;/li&gt;
&lt;li&gt;notify all the subscribed components about the State changes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Developers just need to choose a proper one.&lt;/p&gt;

&lt;p&gt;In my experience, the share of UI state and the code needed to manage it is very small for most applications.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0OAKwUB2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rh2u8gichdoyaq614atr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0OAKwUB2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rh2u8gichdoyaq614atr.png" alt="a share of UI state compared to overall Application state" width="800" height="487"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;That makes the problem of choosing a UI state management library insignificant. If we use those libraries to handle true UI State data only, then most of them work just fine and the switch between any of them does not make a big difference.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solutions for Server State
&lt;/h2&gt;

&lt;p&gt;The characteristics of Server State defined above make the handling of it especially difficult and tricky. And that is where common state management libraries don’t help much.&lt;/p&gt;

&lt;p&gt;Some of the challenges that developers face when working with Server State:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;know if data have already been fetched and is available&lt;/li&gt;
&lt;li&gt;know if fetching is currently in progress&lt;/li&gt;
&lt;li&gt;know if fetching has failed&lt;/li&gt;
&lt;li&gt;deduplicate requests&lt;/li&gt;
&lt;li&gt;re-fetch on error&lt;/li&gt;
&lt;li&gt;cache data and invalidate the cache&lt;/li&gt;
&lt;li&gt;handle mutations with dependent data (think of when changing one entity affects other entities)&lt;/li&gt;
&lt;li&gt;optimistic updates&lt;/li&gt;
&lt;li&gt;reflect Server State in UI&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We all know the cost of attempting to solve these challenges on our own using common state management libraries.&lt;/p&gt;

&lt;p&gt;Fortunately, we are seeing a rise of libraries that specialize in managing Server State and solving all the inherent challenges.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://moiva.io/?npm=@apollo/client+react-query+relay-runtime+swr"&gt;https://moiva.io/?npm=@apollo/client+react-query+relay-runtime+swr&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--bcqczsqc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e3cb371uwipy7t5rp60x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--bcqczsqc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e3cb371uwipy7t5rp60x.png" alt="Npm Download and GitHub Stars trends for ReactQuery, SWR, Relay and ApolloClient" width="800" height="303"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These libraries automate the majority of the tasks, drastically reduce the amount of boilerplate code, and provide declarative APIs with thoughtful defaults.&lt;/p&gt;

&lt;p&gt;Some of &lt;a href="https://moiva.io/?npm=@apollo/client+relay-runtime"&gt;&lt;strong&gt;GraphQL Clients&lt;/strong&gt;&lt;/a&gt; were the first who pioneered the approach. They are designed specifically for GraphQL APIs. Example: &lt;a href="https://www.apollographql.com/docs/react/"&gt;Apollo Client&lt;/a&gt;, &lt;a href="https://relay.dev/"&gt;Relay&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Next came libraries to help manage Server State with REST APIs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://react-query.tanstack.com/"&gt;React Query&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://swr.vercel.app/"&gt;SWR&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://redux-toolkit.js.org/rtk-query/overview"&gt;RTK Query&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At this moment, React Query is probably the most sophisticated and &lt;a href="https://moiva.io/?npm=react-query"&gt;popular&lt;/a&gt; library to handle RESTful Server State.&lt;/p&gt;

&lt;p&gt;React Query is React specific, but its core was fully &lt;a href="https://react-query.tanstack.com/guides/migrating-to-react-query-3#core-separation"&gt;separated&lt;/a&gt; from React and it can be used to build solutions for other frameworks as well. Such solutions have already begun to emerge. For example, &lt;a href="https://github.com/DamianOsipiuk/vue-query/"&gt;Vue Query&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Form State
&lt;/h2&gt;

&lt;p&gt;It is often helpful to separate Form State handling from the rest of the UI state.&lt;/p&gt;

&lt;p&gt;Reason - Form handling is tricky and nuanced. You need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;maintain state of a form as a whole: &lt;code&gt;isDirty&lt;/code&gt;, &lt;code&gt;isValid&lt;/code&gt;, &lt;code&gt;isSubmitting&lt;/code&gt;, &lt;code&gt;dirtyFields&lt;/code&gt;, etc.&lt;/li&gt;
&lt;li&gt;maintain state of each particular field: &lt;code&gt;isDirty&lt;/code&gt;, &lt;code&gt;isValid&lt;/code&gt;, &lt;code&gt;errors&lt;/code&gt;, &lt;code&gt;isDisabled&lt;/code&gt;, &lt;code&gt;currentValue&lt;/code&gt;, &lt;code&gt;initialValue&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;reset form’s and fields’ state&lt;/li&gt;
&lt;li&gt;trigger validation&lt;/li&gt;
&lt;li&gt;etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For simple forms and simple use cases we can manage the state ourselves. But for complex cases it is better to reach out for specialised tools.&lt;/p&gt;

&lt;p&gt;Examples of form handling libraries:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://react-hook-form.com/"&gt;React Hook Form&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://formik.org/"&gt;Formik&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Some JavaScript frameworks have built-in tools to manage forms state.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Separation of concerns is an important concept in State Management topic.&lt;/p&gt;

&lt;p&gt;Different types of data deserve different approaches and specialised tools.&lt;/p&gt;

&lt;p&gt;Using specialized tools often bring huge relief. It’s helpful to be aware of these tools.&lt;/p&gt;

</description>
      <category>statemanagemt</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>redux</category>
    </item>
    <item>
      <title>Moiva August 2021 update</title>
      <dc:creator>Alexey Antipov</dc:creator>
      <pubDate>Tue, 10 Aug 2021 13:45:31 +0000</pubDate>
      <link>https://dev.to/aantipov/moiva-august-2021-update-4m6a</link>
      <guid>https://dev.to/aantipov/moiva-august-2021-update-4m6a</guid>
      <description>&lt;p&gt;Hello everyone!&lt;/p&gt;

&lt;p&gt;I’m Alexey, creator of &lt;a href="http://moiva.io/"&gt;Moiva.io&lt;/a&gt;, The Best Tool to compare Npm Packages.&lt;/p&gt;

&lt;p&gt;This is an August report on the progress of Moiva.io.&lt;/p&gt;

&lt;p&gt;I was on vacation in July, so I had plenty of time to implement some exciting features! Let me share them with you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Copy/download/share charts
&lt;/h2&gt;

&lt;p&gt;This is the feature I’m mostly excited about - all charts now include a popup menu that allows users to copy charts to the clipboard, share charts on Twitter, and download charts.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--PNemPBkX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e189je174g5zpzvitoup.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--PNemPBkX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e189je174g5zpzvitoup.gif" alt="The new charts menu in action" width="640" height="440"&gt;&lt;/a&gt;&lt;br&gt;
It should make it easier for users to use the findings of comparisons elsewhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  A chart for every metric in the Table
&lt;/h2&gt;

&lt;p&gt;A table view is useful for presenting static and numerical data. When the number of libraries in comparison is small, it serves the purpose very well. As the number of libraries increases, it becomes more difficult to make sense of the raw figures. This is where graphical data representation would be very beneficial.&lt;/p&gt;

&lt;p&gt;Thinking that way, I added a small icon to every numeric metric in the Table. Clicking on the icon invokes a pop-up window with a bar chart where all the libraries are sorted by the metric’s value.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--edklril0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3cbhdxfirqzhk2igxli2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--edklril0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3cbhdxfirqzhk2igxli2.png" alt="GitHub Stars chart" width="800" height="594"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  New Suggestions mechanism
&lt;/h2&gt;

&lt;p&gt;When a user evaluates a library, Moiva suggests alternatives to compare.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--p8LS15bn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y3s9c0rezzzz8li6eoux.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--p8LS15bn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y3s9c0rezzzz8li6eoux.png" alt="Moiva’s suggestions" width="800" height="253"&gt;&lt;/a&gt;&lt;br&gt;
In the past, the mechanism of suggestions used Moiva’s Catalog which categorised its libraries. The process of adding new libraries was tiresome because the mechanism had several downsides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;every library had to belong to only one particular category. Choosing a category or creating a new one was a non-trivial task. For example, should React and Vue date-picker libraries belong to one category or different categories? Should Express and NextJS belong to the same category?&lt;/li&gt;
&lt;li&gt;the catalog was maintained in a separate repository, each Category had a separate file, and from time to time I had to compile it and put it to Moiva’s repo.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To facilitate adding new libraries, I refactored the Catalog:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;to use Tags instead of Categories: each library is assigned a number of tags, not a Category&lt;/li&gt;
&lt;li&gt;store the libraries as a plain list in a single file under the Moiva’s repository&lt;/li&gt;
&lt;li&gt;use tags of selected libraries to suggest alternatives&lt;/li&gt;
&lt;li&gt;sort suggestions based on “similarity” to the selected ones. The more tags a library has in common with the tags of selected libraries, the more “similar” it is.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the future, I plan to include GitHub Stars and Npm Downloads numbers, and libraries statuses to improve the sorting algorithm.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tooltips in Suggestions
&lt;/h2&gt;

&lt;p&gt;Before adding suggested alternatives to comparison, it might be helpful to glance quickly at the descriptions and other details of the libraries, such as the number of Stars and Npm downloads.&lt;/p&gt;

&lt;p&gt;On that basis, I added tooltips to the list of suggested alternatives. The tooltips contain the following information:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;name&lt;/li&gt;
&lt;li&gt;description&lt;/li&gt;
&lt;li&gt;stars and npm downloads numbers&lt;/li&gt;
&lt;li&gt;tags
&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lJKwsde3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ccxa35jzwnm6dk16trf1.png" alt="Suggestions tooltip" width="800" height="225"&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  “About” page
&lt;/h2&gt;

&lt;p&gt;Every respectable web app must have an “About” page. And so does Moiva - &lt;a href="https://moiva.io/about/"&gt;https://moiva.io/about/&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;--&lt;/p&gt;

&lt;p&gt;Have any feedback or ideas about the project? I’m keen to know!&lt;/p&gt;

&lt;p&gt;If you find the project interesting, please consider sharing it.&lt;/p&gt;

</description>
      <category>npm</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Vercel Serverless Functions vs Cloudflare Workers</title>
      <dc:creator>Alexey Antipov</dc:creator>
      <pubDate>Thu, 25 Mar 2021 07:12:10 +0000</pubDate>
      <link>https://dev.to/aantipov/vercel-serverless-functions-vs-cloudflare-workers-30cb</link>
      <guid>https://dev.to/aantipov/vercel-serverless-functions-vs-cloudflare-workers-30cb</guid>
      <description>&lt;p&gt;Liquid syntax error: 'raw' tag was never closed&lt;/p&gt;
</description>
      <category>serverless</category>
      <category>vercel</category>
      <category>cloudflare</category>
      <category>api</category>
    </item>
    <item>
      <title>Moiva.io v3: a universal tool to Evaluate, Discover and Compare software</title>
      <dc:creator>Alexey Antipov</dc:creator>
      <pubDate>Fri, 19 Feb 2021 11:58:25 +0000</pubDate>
      <link>https://dev.to/aantipov/moiva-io-v3-a-universal-tool-to-evaluate-discover-and-compare-software-1dhe</link>
      <guid>https://dev.to/aantipov/moiva-io-v3-a-universal-tool-to-evaluate-discover-and-compare-software-1dhe</guid>
      <description>&lt;p&gt;Hi, Alexey is here. I have some exciting news for you!&lt;/p&gt;

&lt;p&gt;I rewrote &lt;a href="https://moiva.io/"&gt;Moiva.io&lt;/a&gt; from scratch and made it a Universal and Flexible tool to suit the taste of every software developer be they a JavaScript, Python or [put your favorite language here] developer.&lt;/p&gt;

&lt;p&gt;This article marks a third major release of Moiva.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--uUUojTyN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t52nqclktj2ztl5cglry.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uUUojTyN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t52nqclktj2ztl5cglry.png" alt="A screenshot of Moiva.io showing a comparison of Vue and Svelte npm packages" width="800" height="668"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What's new (in short)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;ability to search for and get data for any GitHub repository in addition to search and comparison of NPM packages.&lt;/li&gt;
&lt;li&gt;possibility to bring (relatively easy) Search, Suggestion, and Comparison capabilities to other programming languages' package management systems like &lt;a href="https://mvnrepository.com/"&gt;Maven&lt;/a&gt; (Java), &lt;a href="https://pypi.org/"&gt;PIP&lt;/a&gt; (Python), or &lt;a href="https://packagist.org/"&gt;Packagist&lt;/a&gt; (PHP).&lt;/li&gt;
&lt;li&gt;last but not least, Moiva got &lt;a href="https://github.com/aantipov/moiva"&gt;open-sourced&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why I did it
&lt;/h2&gt;

&lt;p&gt;At first, I wanted to focus on JavaScript ecosystem, making npm packages first-class citizens in Moiva.io.&lt;/p&gt;

&lt;p&gt;The goal was to provide developers with a good tool to evaluate and compare npm packages in different dimensions - Popularity, Maintenance, Security, etc.&lt;/p&gt;

&lt;p&gt;But very soon I realized that there are many JavaScript-related projects which don't have any published npm packages.&lt;/p&gt;

&lt;p&gt;Think of, for example, frameworks like &lt;code&gt;Meteor&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Moiva.io could potentially be useful for the evaluation of those projects as well thanks to GitHub charts (Contributors, Issues, Commits Frequency, etc.), but search functionality was limited to npm packages only and everything was built around the concept of npm packages.&lt;/p&gt;

&lt;p&gt;On the other hand, if Moiva gets opened up to the search, evaluation and comparison of &lt;strong&gt;any&lt;/strong&gt; GitHub project, it will essentially convert Moiva into a universal tool and make it useful to many more developers.&lt;/p&gt;

&lt;p&gt;So I got convinced that Moiva should become more Universal and Agile, I just need to come up with a good harmonious concept of how it should look, work and how to implement it.&lt;/p&gt;

&lt;h2&gt;
  
  
  AHA moment
&lt;/h2&gt;

&lt;p&gt;In the beginning, the idea of supporting GitHub looked vague and blurred. I didn't have any good idea how to put together existing functionality for npm packages and the new one for GitHub repositories.&lt;/p&gt;

&lt;p&gt;I could implement separate pages for npm and GitHub, but that was not ideal. Both have a lot in common when comparing JavaScript projects.&lt;/p&gt;

&lt;p&gt;Then the &lt;code&gt;AHA&lt;/code&gt; moment came - everything became clear, I realized how to put together different things and since then I was unstoppable.&lt;/p&gt;

&lt;p&gt;Here is the essence of the solution.&lt;/p&gt;

&lt;h3&gt;
  
  
  One Search for All
&lt;/h3&gt;

&lt;p&gt;The same single search field can be used to search for both npm packages and GitHub repositories. It can be easily achieved via search modifiers (prefixes).&lt;/p&gt;

&lt;p&gt;The default search is for GitHub. &lt;/p&gt;

&lt;p&gt;The search prefixed with &lt;code&gt;n:&lt;/code&gt; is for npm packages.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--xcoE-R9C--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m6wclas9ml0ae2ch03m2.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--xcoE-R9C--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m6wclas9ml0ae2ch03m2.gif" alt="A gif showing how Search field at Moiva.io works: search for NPM packages and GitHub repositories" width="500" height="338"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What I like about that solution is that it can be easily extended in the future to search for other things as well.&lt;/p&gt;

&lt;h3&gt;
  
  
  Show only relevant charts
&lt;/h3&gt;

&lt;p&gt;If a user selects only GitHub repositories without related npm packages, then we can just hide npm-related charts. No reason to show them.&lt;/p&gt;

&lt;p&gt;It's similar to how ThoughtWorks TechRadar and Developer Usage charts work - they are shown only when there is data for the selected npm packages.&lt;/p&gt;

&lt;p&gt;At the same time, if the user selects a mix of npm and Github projects, we will show npm-related charts for the selected npm packages.&lt;/p&gt;

&lt;h3&gt;
  
  
  How about URLs
&lt;/h3&gt;

&lt;p&gt;Every comparison a user makes in Moiva should be easily reproducible via URL.&lt;/p&gt;

&lt;p&gt;It means that Moiva should be able to derive from the URL what information to load, what to put into comparison.&lt;/p&gt;

&lt;p&gt;When npm packages were the only citizens in the Moiva world, the task was solved easily - the selected npm packages' names were listed in a query parameter: &lt;code&gt;https://moiva.io/?compare=react+svelte+vue&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Having 2 types of citizens, npm and Github, where one depends on the other, complicates things a bit. Moreover, we want to build a future-proof solution that can incorporate other types of citizens like PIP and Maven.&lt;/p&gt;

&lt;p&gt;GitHub has a broader scope than npm and my first idea was to replace URL npm identifiers with GitHub identifiers. But there are 2 problems with it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;it's not clear how to derive the npm package from the GitHub repository. At least I couldn't find the solution for that.&lt;/li&gt;
&lt;li&gt;one GitHub repo can be a source of multiple npm packages. There is no 1:1 connection.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It lead me to the conclusion that GitHub and npm should be referenced separately in the URL. &lt;/p&gt;

&lt;p&gt;So I just decided to have separate query parameters: &lt;code&gt;https://moiva.io/?npm=svelte+vue&amp;amp;github=meteor/meteor&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  GitHub and NPM reconciliation
&lt;/h3&gt;

&lt;p&gt;Imagine two situations:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;a user selects Vue as an npm package.&lt;/li&gt;
&lt;li&gt;a user selects Vue as a GitHub repo.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In the first situation Moiva shows npm-related data and charts like npm Downloads. In the second situation, it doesn't.&lt;/p&gt;

&lt;p&gt;But is it fair? Most probably a user would expect to see the same set of information in both cases, right?&lt;/p&gt;

&lt;p&gt;Could we still somehow derive information about the npm package from the GitHub repository? If yes, then we could show npm data for the selected GitHub repository.&lt;/p&gt;

&lt;p&gt;Turns out we can make use of &lt;a href="https://github.com/aantipov/moiva-catalog"&gt;Moiva Catalog&lt;/a&gt; which was built to implement the Suggestions mechanism.&lt;br&gt;
For every listed GitHub repository we can add a name of the npm package if there is one. It means we can solve the problem of the reconciliation for items listed in the catalog. And I think it's a good enough solution with which we can cover the most popular libraries.&lt;/p&gt;

&lt;p&gt;We just need to take care of some details and edge cases.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;If a repository does have an npm package, but that package is just one of the repo's "by-products", then probably it doesn't make sense to show that npm package data when selecting the repository. To solve that problem, an additional flag &lt;code&gt;isNpmCoreArtifact&lt;/code&gt; in the catalog can be used to indicate the "role" of the npm package.&lt;/li&gt;
&lt;li&gt;If we successfully derive npm data from the GitHub repository, it means we essentially display the same information for both npm and GitHub and have different URL identifiers for the same page. It's not good, especially in terms of SEO. So I decided to use npm package's name as a URL identifier in such cases. Try load &lt;code&gt;https://moiva.io/?github=vuejs/vue&lt;/code&gt; URL and see what happens ;=)&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Data model
&lt;/h3&gt;

&lt;p&gt;I mentioned just a few of the problems I had to solve. There were, of course, many others, like duplication handling, aliases, SEO, etc.&lt;/p&gt;

&lt;p&gt;Most of the problems got a straightforward solution once I implemented a proper Data Model - I came up with a new abstraction called "Library" and provided it with certain properties and behavior. &lt;/p&gt;

&lt;p&gt;If you are interested, you can check the &lt;a href="https://github.com/aantipov/moiva/"&gt;repository's readme&lt;/a&gt; for more details about the Library concept.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;I clearly see a huge potential for &lt;a href="https://moiva.io/"&gt;Moiva.io&lt;/a&gt; to become a really useful tool to many developers.&lt;/p&gt;

&lt;p&gt;It can grow and become better in different directions.&lt;br&gt;
I will mention a few of them which look most important to me:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;enable search/suggestion/comparison for more languages' package systems (Maven, PIP, etc.).&lt;/li&gt;
&lt;li&gt;add more useful charts and data, both generic and language/package-system specific.&lt;/li&gt;
&lt;li&gt;improve significantly the alternatives suggestion system. Currently, it's based on &lt;a href="https://github.com/aantipov/moiva-catalog"&gt;Moiva Catalog&lt;/a&gt; and needs a lot of data to be put there. I see a way how the community could help and contribute there.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;I hope I didn't waste your time and you found the reading and the project itself interesting.&lt;/p&gt;

&lt;p&gt;This article was originally published on Moiva Blog &lt;a href="https://moiva.io/blog/universal-tool-to-evaluate-discover-compare-software"&gt;https://moiva.io/blog/universal-tool-to-evaluate-discover-compare-software&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>python</category>
      <category>php</category>
    </item>
  </channel>
</rss>
