<?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: Kairat</title>
    <description>The latest articles on DEV Community by Kairat (@kairatorozobekov).</description>
    <link>https://dev.to/kairatorozobekov</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%2F498400%2Fc2f8e411-b376-41d2-b4e0-eb033d4ee7ec.jpeg</url>
      <title>DEV Community: Kairat</title>
      <link>https://dev.to/kairatorozobekov</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kairatorozobekov"/>
    <language>en</language>
    <item>
      <title>Beyond LIKE: Advanced Text Search and Keyword Matching in Postgres using Full Text Search</title>
      <dc:creator>Kairat</dc:creator>
      <pubDate>Sat, 23 Nov 2024 00:32:38 +0000</pubDate>
      <link>https://dev.to/kairatorozobekov/beyond-like-advanced-text-search-and-keyword-matching-in-postgres-23be</link>
      <guid>https://dev.to/kairatorozobekov/beyond-like-advanced-text-search-and-keyword-matching-in-postgres-23be</guid>
      <description>&lt;p&gt;Imagine you’re collecting video data from YouTube using their API and storing it in a Postgres database. Now, you want to automatically tag certain videos based on specific keywords stored in another table or enable text search functionality.&lt;/p&gt;

&lt;p&gt;The simplest solution would be to create a database view using the basic &lt;strong&gt;LIKE&lt;/strong&gt; operator. You could then set up a cron job to periodically fetch new records and assign tags to them.&lt;/p&gt;

&lt;p&gt;This approach might work for simple use cases, but it has several limitations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;while &lt;strong&gt;LIKE&lt;/strong&gt; supports basic substring matching, it has significant limitations. You could use &lt;strong&gt;ILIKE&lt;/strong&gt; for case-insensitive searches or &lt;strong&gt;~&lt;/strong&gt; for regex matches, but these are still quite basic. Achieving advanced features like ranking, synonym recognition, or phrase-based matching is either extremely difficult or impossible with these tools.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;as your data grows, performance becomes a concern. The engine has to scan through the text to find matches, which can be computationally expensive. Indexing can improve performance to some extent. For example, a &lt;strong&gt;B-tree&lt;/strong&gt; index works well for patterns like "rust%" (starts with). However, searches such as "%rust%" (anywhere in the text) or "%rust" (ends with) cannot leverage the index effectively and will fall back to full-text sequential scans.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because of that, using LIKE and similar operators might not be the most suitable solution for this use case.&lt;/p&gt;




&lt;p&gt;Fortunately, Postgres has built-in full-text search functions that address these limitations.&lt;/p&gt;

&lt;p&gt;To demonstrate how it works, let's create simple &lt;strong&gt;youtube_videos&lt;/strong&gt; table.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CREATE TABLE IF NOT EXISTS
  "public"."youtube_videos" (
    "id" uuid DEFAULT "gen_random_uuid" () NOT NULL PRIMARY KEY,
    "title" text
  );
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It has &lt;strong&gt;title&lt;/strong&gt; column and we gonna run our searches against it.&lt;br&gt;
But to be more specific, tokenized version of it.&lt;/p&gt;

&lt;p&gt;And you might be wondering what do I mean by that?&lt;/p&gt;

&lt;p&gt;Well, Postgres has this function - to_tsvector() which stands for "to text search vector". Using this function we can transform our video title into searchable tokens that can be used for comparisons.&lt;/p&gt;

&lt;p&gt;This conversion involves &lt;a href="https://en.wikipedia.org/wiki/Lemmatization" rel="noopener noreferrer"&gt;lemmatization&lt;/a&gt; which normalizes word. It involves making word lower-cased, removing suffixes (such as &lt;strong&gt;es&lt;/strong&gt; in Eng) and stop words (such as &lt;strong&gt;a&lt;/strong&gt; and &lt;strong&gt;the&lt;/strong&gt; in Eng). This step is done by the use of dictionaries. Various standard dictionaries are provided, and custom ones can be created for specific needs.&lt;/p&gt;

&lt;p&gt;To avoid making this transformation every time we need to search for something, let's store it in the column of type tsvector.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;alter table youtube_videos
add column fts_title tsvector generated always as (to_tsvector('english', title)) stored;

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

&lt;/div&gt;



&lt;p&gt;Running this query will create a new column in our videos table. It will be generated every time we update the title column (on INSERT and UPDATE).&lt;/p&gt;

&lt;p&gt;First parameter passed to function is a dictionary name. In this example as you can see we used basic &lt;strong&gt;english&lt;/strong&gt; dictionary. You can read more about Postgres dictionaries &lt;a href="https://www.postgresql.org/docs/current/textsearch-dictionaries.html" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Also, in this specific use case, we know for sure, every video has a title, but if you are dealing with nullable columns, it's better to use &lt;strong&gt;coalesce(title, '')&lt;/strong&gt; to handle null values.&lt;/p&gt;

&lt;p&gt;To speed up the text search process even more, let's create GIN index on column. As this column is of type tsvector, regular B-Tree index won't work in this case.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;create index title_fts_inx on youtube_videos using gin (fts_title);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, after inserting a few records into the table, we can take a look at what fts_title value looks like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F283xypnmesh9xg63e2ft.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F283xypnmesh9xg63e2ft.png" alt="fts_title example" width="800" height="453"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fedqhqwhw66r0hqz0jvss.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fedqhqwhw66r0hqz0jvss.png" alt="fts_title example 2" width="800" height="439"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see, normalization was applied to the title: ""&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"stocks -&amp;gt; stock" &lt;/li&gt;
&lt;li&gt;"to, you, in" were dropped&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;And now, to actually find keyword matching records, we need to use the to_tsquery() function that converts a query string into tokens to match.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;select
  *
from
  youtube_videos
where
  fts_title @@ to_tsquery('english', 'rust');
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, @@ symbol is the "match" symbol for full-text search.&lt;br&gt;
Execution time for this command is roughly between 0.05 - 0.065 ms.&lt;/p&gt;

&lt;p&gt;For comparison, running search using ILIKE operator on indexed title column, is way slower (1.45 ms - 1.50 ms).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;create index title_inx on youtube_videos(title)

SELECT
  *
FROM
  youtube_videos
WHERE
  title ilike '%rust%';
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or let's say we wanna get all videos about stocks (maybe I wanna buy PLTR or Tesla). &lt;/p&gt;

&lt;p&gt;Running query like that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT
  *
FROM
  youtube_videos
WHERE
  title ilike '%stock%';
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Returns these 2 records:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fx40gx9tx1hezti6qfj2v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fx40gx9tx1hezti6qfj2v.png" alt="Returned Records using ilike operator" width="800" height="133"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Maybe we can use a bit different query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT
  *
FROM
  youtube_videos
WHERE
  title ~* '\ystock\y';

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

&lt;/div&gt;



&lt;p&gt;Returns none as stock is singular.&lt;/p&gt;

&lt;p&gt;But running this query, returns the desired result.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT
  *
FROM
  youtube_videos
WHERE
  title ~* '\ystock\y';
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.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%2F9ilq7t65cd1xmczccz7g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F9ilq7t65cd1xmczccz7g.png" alt="Returned records using tsvector" width="800" height="108"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And let's say we have keywords table. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fnxqptek08mvolg52qzhd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fnxqptek08mvolg52qzhd.png" alt="Keywords table" width="800" height="149"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we can create a view that has all the videos containing any of those keywords:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CREATE OR REPLACE VIEW videos_with_keywords AS
select
  yv.*
from
  youtube_videos yv
  join keywords on (yv.fts_title @@ to_tsquery(keywords.keyword))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.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%2Ffslu0l4cdhectrertm61.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Ffslu0l4cdhectrertm61.png" alt="Videos containing keywords" width="800" height="89"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can also do partial searches:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;select * from youtube_videos where fts_title @@ to_tsquery('english', 'sto:*')
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While this article focused on the basics of full-text search, PostgreSQL offers many advanced features that you can explore further. Here are some great resources to dive deeper:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.postgresql.org/docs/current/textsearch.html" rel="noopener noreferrer"&gt;Docs&lt;/a&gt;&lt;br&gt;
&lt;a href="https://supabase.com/docs/guides/database/full-text-search?queryGroups=example-view&amp;amp;example-view=sql&amp;amp;queryGroups=language&amp;amp;language=sql" rel="noopener noreferrer"&gt;Supabase Article&lt;br&gt;
&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;And that's it, guys.&lt;/p&gt;

&lt;p&gt;I hope that you have learned something new today!&lt;br&gt;
I would appreciate it if you could like this post or leave a comment below!&lt;/p&gt;

&lt;p&gt;Also, feel free to follow me on &lt;a href="https://github.com/Kai4ik" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; and &lt;a href="https://medium.com/@kai4ik" rel="noopener noreferrer"&gt;Medium&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;Adios, mi amigos)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fb4vz350e1u5rlp39p1gt.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fb4vz350e1u5rlp39p1gt.gif" alt="See you later meme" width="400" height="221"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>database</category>
      <category>postgres</category>
      <category>sql</category>
    </item>
    <item>
      <title>Safe Content, Happy Users: Azure AI Content Safety</title>
      <dc:creator>Kairat</dc:creator>
      <pubDate>Tue, 27 Feb 2024 22:06:00 +0000</pubDate>
      <link>https://dev.to/kairatorozobekov/safe-content-happy-users-azure-ai-content-safety-3abp</link>
      <guid>https://dev.to/kairatorozobekov/safe-content-happy-users-azure-ai-content-safety-3abp</guid>
      <description>&lt;p&gt;The Internet is arguably one of the most important inventions in human history. However, since its creation, it has also become a source of concern. Not only is there an abundance of misinformation, but the prevalence of harmful and offensive content has turned it into a daunting place. This issue has reached alarming levels, leading to severe consequences in some cases.&lt;/p&gt;

&lt;p&gt;As a result, major organizations such as Twitch, YouTube, Facebook, and others that rely on user-generated content have been actively working on implementing measures to filter out offensive content. The underlying structures of these platforms are complex and just a few years ago, creating an effective profanity-filtering tool would have been a difficult task.&lt;/p&gt;

&lt;p&gt;Fortunately, with the advent of AI tools such as Gemini and ChatGPT, as well as pre-trained moderation language models (LLMs), integrating content filtering into your application has become much more feasible. &lt;/p&gt;

&lt;p&gt;In this article, I want to explore the Azure AI Content Safety service and demonstrate how it can be used to detect potentially harmful content.&lt;/p&gt;




&lt;p&gt;Whether you are trying to build an app where people can leave comments under articles or aiming to develop a new Instagram to compete with Mister Zuckerberg, you would need to manage user-generated content - uploaded images, comments, etc. And that's where you might benefit from AI Safety Tool - perhaps you want to block people from uploading offensive or harmful content or at least be notified that potentially unsafe content was uploaded.&lt;/p&gt;

&lt;p&gt;This tool is one of Azure's AI Services and it precisely addresses the need discussed above.&lt;/p&gt;

&lt;p&gt;From docs:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;AI Content Safety detects harmful user-generated and AI-generated content in applications and services. Azure AI Content Safety includes text and image APIs that allow you to detect material that is harmful.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It offers 4 types of analysis:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;text analysis&lt;/li&gt;
&lt;li&gt;image analysis&lt;/li&gt;
&lt;li&gt;jailbreak risk detection (scans text for the risk of a jailbreak attack on a Large Language Model)&lt;/li&gt;
&lt;li&gt;protected material text detection (scans AI-generated text for known text content (for example, song lyrics, articles, recipes, selected web content))&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Today, we're only going to focus on the first aspect - text analysis and how you can integrate it into your application.&lt;/p&gt;

&lt;p&gt;But before going into the details of the actual implementation, I'd like to mention one more thing - the interactive &lt;a href="https://contentsafety.cognitive.azure.com/" rel="noopener noreferrer"&gt;Content Safety Studio&lt;/a&gt;.&lt;br&gt;
This platform enables users to view, explore, and experiment with sample code for detecting harmful content across various modalities. Additionally, it empowers you as a developer to monitor key performance indicators (KPIs) such as technical metrics (latency, accuracy, recall) and business metrics (block rate, block volume, category proportions, language proportions, and more).&lt;/p&gt;




&lt;p&gt;To begin the actual implementation, you'll need to have an Azure account with an active Azure Subscription.&lt;/p&gt;

&lt;p&gt;To create a new instance of the AI Content Safety service, navigate to the Azure home page and start typing "content safety" in the search bar. This will bring up relevant services related to content safety.&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%2F6y19olquiz7e8461yw5v.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%2F6y19olquiz7e8461yw5v.png" alt="Azure Home page - typing Content Safety"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's create a new instance.&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%2F5du4j5wi7knxrc26r8rv.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%2F5du4j5wi7knxrc26r8rv.png" alt="Creating a new instance of AI Content Safety service"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Choose the resource group, name, location and pricing tier.&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%2Fw6srromvrop84mjf243s.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%2Fw6srromvrop84mjf243s.png" alt="Choosing the resource group, name, location, pricing tier"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can also configure networking and identity settings if needed. However, if you prefer, you can leave everything by default for simplicity.&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%2Fh1vgk6h00q202dwai6t9.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%2Fh1vgk6h00q202dwai6t9.png" alt="Networking Settings"&gt;&lt;/a&gt;&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%2Fwfw4k4liw6aqnl86jybh.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%2Fwfw4k4liw6aqnl86jybh.png" alt="Identity Settings"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now that everything is set up, go ahead and click on the "Create" button to proceed with creating the instance.&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%2F50z0toe54bakyunz2yxn.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%2F50z0toe54bakyunz2yxn.png" alt="Review and create new instance"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Navigate to the newly created resource, then select "Keys and Endpoint" from the menu. This section will provide you with the necessary authentication keys and endpoints to interact with the AI Content Safety service.&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%2Fk5rmbkdeazw9gst7j26x.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%2Fk5rmbkdeazw9gst7j26x.png" alt="Dashboard for newly created Resource"&gt;&lt;/a&gt;&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%2Fktnag9jdjbc931367evj.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%2Fktnag9jdjbc931367evj.png" alt="API endpoint and keys"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you go to the &lt;a href="https://learn.microsoft.com/en-us/azure/ai-services/content-safety/" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;, you will find a lot of ways to use Content Safety - you can call it directly through REST API or use the language-specific packages (JS, Python, C#).&lt;/p&gt;

&lt;p&gt;I am going to use REST - that's why we needed that endpoint and keys.&lt;/p&gt;

&lt;p&gt;For demonstration purposes, I've created a simple Astro page where users can leave comments. These comments will be validated using the Content Safety service across four harm categories: Self-Harm, Sexual, Violence, and Hate &amp;amp; Fairness.&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%2Flsn6atm260hubbx84w4q.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%2Flsn6atm260hubbx84w4q.png" alt="Astro app for demonstration purpose"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can read more about those &lt;a href="https://learn.microsoft.com/en-us/azure/ai-services/content-safety/concepts/harm-categories?tabs=warning" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;br&gt;
For each category, we will also receive the severity level rating, which indicates the severity of the consequences of displaying the flagged content.&lt;/p&gt;

&lt;p&gt;In the case of text analysis, it supports a full 0-7 severity scale, where 0 represents content deemed safe and 7 signifies a high severity level.&lt;/p&gt;

&lt;p&gt;You don't have to concern yourself with HTML structure and CSS, as we won't be focusing on them in this article. The only code we care about is the script tag where we make a call to the Content Safety endpoint.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

&amp;lt;script&amp;gt;
    interface ContentSafetyResponse {
        blocklistsMatch: {blocklistItemText: string; blocklistItemId: string;blocklistName: string}[];
        categoriesAnalysis: { category: string; severity: number }[];
    }

    const comment : HTMLTextAreaElement = document.getElementById("comment") as HTMLTextAreaElement;
    const submitBtn = document.getElementById("submitBtn");

    submitBtn?.addEventListener("click",async ()=&amp;gt; {
        if (comment) {
            const data = await fetch('your-endpoint', {
                method: "POST",
                headers: {
                    "Content-Type": "application/json",
                    "Ocp-Apim-Subscription-Key": "key"
                },
                body: JSON.stringify({
                    "text": comment.value,
                    "categories": [
                        "Hate", "Sexual", "SelfHarm", "Violence"
                    ],
                    "blocklistNames": [
                        "SwearWords"
                    ],
                    "outputType": "FourSeverityLevels"
                })
            })
            const json: ContentSafetyResponse = await data.json()
            json.blocklistsMatch.map((match)=&amp;gt; {
                const blocklist = document.getElementById("blocklist");
                if (blocklist) {
                    blocklist.innerText = blocklist.innerText + " " + match.blocklistItemText
                }
            })
            json.categoriesAnalysis.map((category)=&amp;gt; {
                if (category.category.toLowerCase() == "hate") {
                    const hateLevel = document.getElementById("hateLevel");
                    if (hateLevel) {
                        hateLevel.innerText = category.severity.toString()
                    }
                }
                if (category.category.toLowerCase() == "violence") {
                    const violenceLevel = document.getElementById("violenceLevel");
                    if (violenceLevel) {
                        violenceLevel.innerText = category.severity.toString()
                    }
                }
                if (category.category.toLowerCase() == "sexual") {
                    const sexualLevel = document.getElementById("sexualLevel");
                    if (sexualLevel) {
                        sexualLevel.innerText = category.severity.toString()
                    }
                }
                if (category.category.toLowerCase() == "selfharm") {
                    const selfharmLevel = document.getElementById("selfharmLevel");
                    if (selfharmLevel) {
                        selfharmLevel.innerText = category.severity.toString()
                    }
                }
            })
        }   
    })
&amp;lt;/script&amp;gt;


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

&lt;/div&gt;

&lt;p&gt;We make a POST request to the endpoint, we'll assign the value "application/json" to the "Content-Type" header and provide "Ocp-Apim-Subscription-Key" as another header. &lt;br&gt;
The request body should contain the following parameters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;text - content that needs to be validated&lt;/li&gt;
&lt;li&gt;categories - all 4 or some of them&lt;/li&gt;
&lt;li&gt;outputType - can be FourSeverityLevels (possible values: 0,2,4,6) or EightSeverityLevels (possible values: 0,1,2,3,4,5,6,7)&lt;/li&gt;
&lt;li&gt;blocklistNames - names of custom blocklists (we will discuss this in more detail later on)&lt;/li&gt;
&lt;li&gt;haltOnBlocklistHit - when set to true, further analyses of harmful content won't be performed in cases where blocklists are hit&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's test it out.&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%2F0lo7bmztfzbf63sldqcj.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%2F0lo7bmztfzbf63sldqcj.png" alt="Test with safe sentence"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see, severity levels for all 4 categories are rated 0 which means content is safe.&lt;/p&gt;

&lt;p&gt;Now let's try something different (only an example, you can do it only in GTA RP)&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%2F6yn6cmfhvo8v79novht2.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%2F6yn6cmfhvo8v79novht2.png" alt="Test with violent sentence"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see here, we changed the context on how we want to use the axe and now we are getting a violence level of 4. It's not just a keyword matching, but it does understand the context as well.&lt;/p&gt;

&lt;h2&gt;
  
  
  You may have already observed the "blockListsMatch" property in the response.
&lt;/h2&gt;

&lt;p&gt;What is that?&lt;/p&gt;

&lt;p&gt;So, for most cases default AI classifiers are good enough, but sometimes you might need more than just the default ones. &lt;br&gt;
This is where blocklists come into play. Essentially, a blocklist is a curated list of words or phrases that, if detected in the text, will trigger a failure in the content check.&lt;/p&gt;

&lt;p&gt;As an example, the word "hoser" is a Canadian playful insult (like a loser). Let's say you do not want any insults on your platform.&lt;br&gt;
If someone gonna use this word, with the default classifiers, it will pass the check (in comparison with the word "loser")&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%2Ffq3vanqpla75d99z9qow.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%2Ffq3vanqpla75d99z9qow.png" alt="Test with word hoser - pass"&gt;&lt;/a&gt;&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%2Fnhxxt1v53n9h048gl26f.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%2Fnhxxt1v53n9h048gl26f.png" alt="Test with word loser - pass"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;How to fix that? &lt;br&gt;
You already know the answer - blocklists. &lt;br&gt;
One way to create a blocklist is through API. We not gonna do that instead let's use Safety Studio. &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%2F59u0cxr037xhi8xs3yfb.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%2F59u0cxr037xhi8xs3yfb.png" alt="Content Safety Studio"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here you can also create a new blocklist.&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%2F1p3pbs2khacu5sy3jhfv.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%2F1p3pbs2khacu5sy3jhfv.png" alt="Creating new blocklist via Content Safety Studio"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I am not creating a new one as I already created one before, so let's just use it. It's called SwearWords - the name is important as we gonna use it as one of the body parameters.&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%2F36erfxhp0sg5lm841i3y.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%2F36erfxhp0sg5lm841i3y.png" alt="SwearWords BlockList"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now let's add the word "hoser" to that list.&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%2Fzo5ccq1v8iu1fbdv64yx.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%2Fzo5ccq1v8iu1fbdv64yx.png" alt="Adding new word to the list"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you were paying attention, in our code above, we specified blocklist names - the "SwearWords" list was there.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

"blocklistNames": ["SwearWords"]


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

&lt;/div&gt;

&lt;p&gt;Now, let's try to validate the last sentence one more time and see the results.&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%2Fl3v40icbq9p0x4ldpevw.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%2Fl3v40icbq9p0x4ldpevw.png" alt="Testing word "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Did something change? Severity levels are still at 0. But look at the second property in the response object - blocklistsMatch. It is no longer empty - instead, it shows us that our text contained the word from the SwearWords list and it should fail the validation.&lt;/p&gt;




&lt;p&gt;That was just a sample of how you can use it in your apps and there is so much more you can do with this tool. This article aims to introduce you to the capabilities of the AI Content Safety service and demonstrate how you can leverage it to enhance your projects. &lt;br&gt;
It's worth noting that the AI Content Safety service has undergone extensive testing and is not merely a rough prototype in the development phase. It has already been successfully integrated into various Microsoft products, including Copilot, showcasing its reliability and effectiveness.&lt;/p&gt;

&lt;p&gt;There is a great video on its functionality with Sarah Bird who was leading the project. &lt;a href="https://www.youtube.com/watch?v=eNyPdCECR90" rel="noopener noreferrer"&gt;Check it out&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;Here is the &lt;a href="https://westus.dev.cognitive.microsoft.com/docs/services/content-safety-service-2023-10-01/operations/TextOperations_AnalyzeText" rel="noopener noreferrer"&gt;API documentation&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;And that's it, guys.&lt;/p&gt;

&lt;p&gt;I hope that you have learned something new today!&lt;br&gt;
I would appreciate it if you could like this post or leave a comment below!&lt;/p&gt;

&lt;p&gt;Also, feel free to follow me on &lt;a href="https://github.com/Kai4ik" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; and &lt;a href="https://medium.com/@kai4ik" rel="noopener noreferrer"&gt;Medium&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;Adios, mi amigos)&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%2Fb4vz350e1u5rlp39p1gt.gif" 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%2Fb4vz350e1u5rlp39p1gt.gif" alt="See you later meme"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>azure</category>
      <category>cloud</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>JavaScript Efficiency War: Astro.js vs Next.js</title>
      <dc:creator>Kairat</dc:creator>
      <pubDate>Wed, 21 Feb 2024 21:51:14 +0000</pubDate>
      <link>https://dev.to/kairatorozobekov/javascript-efficiency-war-astrojs-vs-nextjs-22pm</link>
      <guid>https://dev.to/kairatorozobekov/javascript-efficiency-war-astrojs-vs-nextjs-22pm</guid>
      <description>&lt;p&gt;Astro has been on the market for a while but it's still considered a relatively new library. In 2023 there were a lot of talks about it and I decided to give it a try. &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%2Fscvo2mpforc6jt077ad9.jpg" 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%2Fscvo2mpforc6jt077ad9.jpg" alt="No love for Astro? meme"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Before I was primarily using React and Next 13 but most of the applications I was working on were informative simple websites with mostly static data. That is why Astro seemed very attractive as it is the perfect library for such use cases.&lt;/p&gt;

&lt;p&gt;Like most people who tried Astro, I liked it and in this article, I want to particularly discuss how Astro impacts the performance of your website.&lt;/p&gt;




&lt;p&gt;To start, I want to talk a bit about the new architectural pattern that Astro is trying to popularize. It's called &lt;strong&gt;island architecture&lt;/strong&gt; and Astro docs have a &lt;a href="https://docs.astro.build/en/concepts/islands/" rel="noopener noreferrer"&gt;separate page explaining this concept&lt;/a&gt;.&lt;br&gt;
Here I just want to briefly highlight the main ideas behind it so you have more context on why Astro is so good at performance. &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%2F8zo8ihp0fv6b9howvtec.jpg" 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%2F8zo8ihp0fv6b9howvtec.jpg" alt="Archipelago - island architecture demonstration"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you've never heard of this technique (architectural pattern), there is a chance you might know it by a different name - partial or selective hydration. &lt;/p&gt;

&lt;p&gt;The main idea behind it is relatively simple.&lt;br&gt;
First, let's define what's an island. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Basically, an island is any interactive UI component on the page.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;An island always runs in isolation from other islands on the page, and multiple islands can exist on a page.&lt;/p&gt;

&lt;p&gt;So, now let's move on to non-interactive components on your pages.&lt;br&gt;
All other UI components - non-interactive ones are static, lightweight, server-rendered HTML &amp;amp; CSS.&lt;/p&gt;

&lt;p&gt;And now what we have in the end - static components rendered on the server along with dynamic ones - those that can be "hydrated" on the client. &lt;/p&gt;

&lt;p&gt;What does this all mean? &lt;br&gt;
React, Next.js and other SPAs use a monolithic pattern where the entire website is one large JavaScript application (not in the case of Next.js) and we end up shipping a lot of unused JavaScript to the browser which we don't even gonna use. With these libraries we can't selectively (partially) and strategically hydrate certain components, instead, we hydrate the entire app.&lt;/p&gt;

&lt;p&gt;If all of it does not make sense to you, let me show an example of how it works.&lt;/p&gt;

&lt;p&gt;Let's say we have a web page with the following structure.&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%2Fx4dfxo3s881x463b47xy.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%2Fx4dfxo3s881x463b47xy.png" alt="Web page with dynamic and static components"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If we would build the following static page using Next.js, we would render it on the server and send JavaScript to the client to hydrate dynamic components, right? The catch here is that we not only gonna send the JS required for Header &amp;amp; Carousel components. but also JS for static components as well and we don't need it cause we know those 3 components are static and there is no need for us to hydrate those on the client.&lt;/p&gt;

&lt;p&gt;That's why we have Astro, and what would happen if we were to build this same page with it? &lt;br&gt;
We strip all non-essential JavaScript from the page (it's done automatically) which slows down the website and instead, we only send JS needed for dynamic components' hydration. &lt;/p&gt;

&lt;p&gt;I hope it all makes more sense now, but if you are still feeling a little bit confused, I highly recommend watching &lt;a href="https://www.youtube.com/watch?v=SICd8tTEqvs" rel="noopener noreferrer"&gt;this video from Vite Conference by Nate Moore&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;To give you even more visualization, I built this super simple web page where users can enter their email and after clicking the subscribe button &lt;a href="https://www.npmjs.com/package/canvas-confetti" rel="noopener noreferrer"&gt;confetti&lt;/a&gt; is shown.&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%2Fpw7j27rlg4sn6htk2uqu.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%2Fpw7j27rlg4sn6htk2uqu.png" alt="Web page built using Astro.js and Next.js"&gt;&lt;/a&gt;&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%2F6qi3yniuhxox711nitpm.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%2F6qi3yniuhxox711nitpm.png" alt="Confetti on a web page after clicking submit button"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I built it twice - using Astro.js and Next 13 static export (can be done by specifying output: 'export' in the next configuration file).&lt;/p&gt;

&lt;p&gt;That's how the code looks if using Astro.js&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

&amp;lt;html lang="en"&amp;gt;
    &amp;lt;head&amp;gt;
        &amp;lt;meta charset="utf-8" /&amp;gt;
        &amp;lt;link rel="icon" type="image/svg+xml" href="/favicon.svg" /&amp;gt;
        &amp;lt;meta name="viewport" content="width=device-width" /&amp;gt;
        &amp;lt;meta name="generator" content={Astro.generator} /&amp;gt;
        &amp;lt;title&amp;gt;Astro&amp;lt;/title&amp;gt;
    &amp;lt;/head&amp;gt;
    &amp;lt;body&amp;gt;
        &amp;lt;div class="w-screen h-screen bg-blue-800 flex justify-center items-center"&amp;gt;
            &amp;lt;div class="flex w-[60vw] h-[70vh] bg-neutral-300 rounded-3xl px-8 py-4"&amp;gt;
                &amp;lt;div class="flex flex-col basis-1/2 gap-y-4"&amp;gt;
                    &amp;lt;h1 class="text-4xl"&amp;gt;Stay updated&amp;lt;/h1&amp;gt;
                    &amp;lt;h2&amp;gt;Join 60+ product managers receiving monthly updates on&amp;lt;/h2&amp;gt;
                    &amp;lt;ul class="list-disc list-inside"&amp;gt;
                        &amp;lt;li&amp;gt;Product discovery and building what matters&amp;lt;/li&amp;gt;
                        &amp;lt;li&amp;gt;Measuring to ensure updates and a success&amp;lt;/li&amp;gt;
                        &amp;lt;li&amp;gt; And much more!&amp;lt;/li&amp;gt;
                    &amp;lt;/ul&amp;gt;
                    &amp;lt;h3 class="pt-8"&amp;gt;Email Address&amp;lt;/h3&amp;gt;
                    &amp;lt;form id="newsletterform" class="flex flex-col gap-y-2"&amp;gt;
                        &amp;lt;input required type="email" class="w-[90%] p-4"/&amp;gt;
                        &amp;lt;button id="subscribeBtn" type="submit" class="w-[90%] p-2 bg-red-400 hover:bg-red-500"&amp;gt;
                            Subscribe 
                        &amp;lt;/button&amp;gt;
                    &amp;lt;/form&amp;gt;
                &amp;lt;/div&amp;gt;
                &amp;lt;div class="basis-1/2 rounded-3xl bg-pink-400 overflow-x-hidden"&amp;gt;
                    &amp;lt;img src="TestImg.jpg" alt="Alternative Text for Image" class="h-full object-cover"/&amp;gt;
                &amp;lt;/div&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;

&amp;lt;script&amp;gt;
    import confetti from 'canvas-confetti';
    const form = document.getElementById('newsletterform');
    form?.addEventListener("submit", (e)=&amp;gt; {
        e.preventDefault()
        confetti()
    })
  &amp;lt;/script&amp;gt;


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

&lt;/div&gt;

&lt;p&gt;Here is the Next 13 version:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;page.tsx&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

import Form from "@/components/button";

export default function Home() {
  return (

    &amp;lt;div className="w-screen h-screen bg-blue-800 flex justify-center items-center"&amp;gt;
      &amp;lt;div className="flex w-[60vw] h-[70vh] bg-neutral-300 rounded-3xl px-8 py-4"&amp;gt;
        &amp;lt;div className="flex flex-col basis-1/2 gap-y-4"&amp;gt;
          &amp;lt;h1 className="text-4xl"&amp;gt;Stay updated&amp;lt;/h1&amp;gt;
          &amp;lt;h2&amp;gt;Join 60+ product managers receiving monthly updates on&amp;lt;/h2&amp;gt;
          &amp;lt;ul className="list-disc list-inside"&amp;gt;
            &amp;lt;li&amp;gt;Product discovery and building what matters&amp;lt;/li&amp;gt;
            &amp;lt;li&amp;gt;Measuring to ensure updates and a success&amp;lt;/li&amp;gt;
            &amp;lt;li&amp;gt; And much more!&amp;lt;/li&amp;gt;
          &amp;lt;/ul&amp;gt;
          &amp;lt;h3 className="pt-8"&amp;gt;Email Address&amp;lt;/h3&amp;gt;
          &amp;lt;Form /&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;div className="basis-1/2 rounded-3xl bg-pink-400 overflow-x-hidden"&amp;gt;
          &amp;lt;img src="TestImg.jpg" alt="Alternative Text for Image" className="h-full object-cover" /&amp;gt;
        &amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;

  );
}


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

&lt;/div&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;button.tsx&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

"use client"

import confetti from 'canvas-confetti';

export default function Form(): JSX.Element {
    return &amp;lt;form id="newsletterform" onSubmit={(e) =&amp;gt; {
        e.preventDefault();
        confetti();
    }} className="flex flex-col gap-y-2"&amp;gt;
        &amp;lt;input required type="email" className="w-[90%] p-4" /&amp;gt;
        &amp;lt;button id="subscribeBtn" type="submit" className="w-[90%] p-2 bg-red-400 hover:bg-red-500"&amp;gt;
            Subscribe
        &amp;lt;/button&amp;gt;
    &amp;lt;/form&amp;gt;;
}


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

&lt;/div&gt;

&lt;p&gt;I built it using the &lt;strong&gt;npm run build&lt;/strong&gt; command and then started the application in production mode (&lt;strong&gt;npm run start&lt;/strong&gt; for Next and &lt;strong&gt;npm run preview&lt;/strong&gt; for Astro).&lt;/p&gt;

&lt;p&gt;Now, we go to the network tab to see what requests we make to display this page. Particularly we are interested in JS requests.&lt;br&gt;
And what do we see?&lt;/p&gt;

&lt;p&gt;In Next.js, we have 5 requests &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%2Fhknoa2bv4i5dvbue3e5f.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%2Fhknoa2bv4i5dvbue3e5f.png" alt="Next.js app Network tab in DevTools showing 5 JavaScript requests were made"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While in Astro, it's just one&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%2Fkr1bidaqhkv1mr8ez9dl.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%2Fkr1bidaqhkv1mr8ez9dl.png" alt="Astro.js app Network tab in DevTools showing only 1 JavaScript request was made"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In total, we made 5 requests in Astro (HTML document, 1 CSS file, one JS, 2 images (body and favicon) and in Next 9 - +4 JS files.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;Astro&lt;/strong&gt;&lt;/em&gt;&lt;br&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%2Ffc5qagsxji0pj7d1t4ak.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%2Ffc5qagsxji0pj7d1t4ak.png" alt="Astro.js app Network tab showing DomContentLoaded Time, Load Time, number of requests made, etc."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Next&lt;/em&gt;&lt;/strong&gt;&lt;br&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%2Famjoo6cpu7canvsyhy5l.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%2Famjoo6cpu7canvsyhy5l.png" alt="Next.js app Network tab showing DomContentLoaded Time, Load Time, number of requests made, etc."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you want to see, what those JS files consist of, you can use bundler analyzer tools. &lt;/p&gt;

&lt;p&gt;I used this one for Astro - &lt;a href="https://www.npmjs.com/package/vite-bundle-visualizer" rel="noopener noreferrer"&gt;vite-bundle-visualizer&lt;/a&gt;.&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%2Fd4phct2e52rjri3643qb.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%2Fd4phct2e52rjri3643qb.png" alt="Vite Bundle Visualizer for Astro app"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And for Next, there is actually a package built for it - &lt;a href="https://www.npmjs.com/package/@next/bundle-analyzer" rel="noopener noreferrer"&gt;@next/bundle-analyzer&lt;/a&gt;&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%2F6orgjhvbyiuf0hdvelcc.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%2F6orgjhvbyiuf0hdvelcc.png" alt="Next.js app bundle analysis"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see from this image, Next.js comes with a bunch of default packages it needs for proper work - and we, as developers, could benefit from those a lot but in more complex apps with more features while simple static informative websites don't need those.&lt;/p&gt;

&lt;p&gt;In terms of Lighthouse performance, Astro is better here as well (just a bit and of course it's gonna vary a lot based on multiple factors)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Astro&lt;/em&gt;&lt;/strong&gt;&lt;br&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%2F1sdwrw382n3besdm0btq.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%2F1sdwrw382n3besdm0btq.png" alt="Astro.js app Lighthouse performance score and JS execution time"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Next&lt;/em&gt;&lt;/strong&gt;&lt;br&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%2F4c4b5wiczkcgbb3zt18l.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%2F4c4b5wiczkcgbb3zt18l.png" alt="Next.js app Lighthouse performance score and JS execution time"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I know it is a super simple web page and in reality, you would have much more complex structures but it still shows the difference between these 2 frameworks and how Astro improves performance by stripping non-essential JavaScript code.&lt;/p&gt;




&lt;p&gt;In the end, just wanna say that I am gonna continue using Next 13 as well as Astro. The main point of this article was to show you that as developers we shouldn't rely on one tool and be flexible depending on the situation. &lt;/p&gt;




&lt;p&gt;And that's it, guys.&lt;/p&gt;

&lt;p&gt;I hope that you have learned something new today!&lt;br&gt;
I would appreciate it if you could like this post or leave a comment below!&lt;/p&gt;

&lt;p&gt;Also, feel free to follow me on &lt;a href="https://github.com/Kai4ik" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; and &lt;a href="https://medium.com/@kai4ik" rel="noopener noreferrer"&gt;Medium&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;Adios, mi amigos)&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%2Fz97ggbkhoqrbqq2pf75a.gif" 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%2Fz97ggbkhoqrbqq2pf75a.gif" alt="Seal you later meme"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>astro</category>
      <category>nextjs</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Frustrated with Tracking Grocery Costs? Find Your Answer Here</title>
      <dc:creator>Kairat</dc:creator>
      <pubDate>Sun, 18 Feb 2024 20:31:01 +0000</pubDate>
      <link>https://dev.to/kairatorozobekov/frustrated-with-tracking-grocery-costs-find-your-answer-here-4i3k</link>
      <guid>https://dev.to/kairatorozobekov/frustrated-with-tracking-grocery-costs-find-your-answer-here-4i3k</guid>
      <description>&lt;h2&gt;
  
  
  How much do you spend on groceries per month?
&lt;/h2&gt;

&lt;p&gt;Is it $500? Or maybe $600–700? For some, it might be more than $1k.&lt;/p&gt;

&lt;p&gt;There are a lot of factors that affect this number — your gender (statistically males spend more than females), your diet (if you try to eat healthy, your bill will likely be higher), family size (those who have kids spend much more), area you live in, etc.&lt;/p&gt;

&lt;p&gt;But probably all of us noticed that the cost of the average grocery bill went up a lot. It all makes sense — inflation was crazy in the past few years, constant talks about recession, etc. Of course, it impacted grocery prices as well.&lt;/p&gt;

&lt;h2&gt;
  
  
  But my question here is how do you keep track of your grocery expenses?
&lt;/h2&gt;

&lt;p&gt;Do you use Excel, Google Sheets or any app that helps you with that? There are dozens of shopping list apps out there, budget apps like Mint, YNAB, etc.&lt;br&gt;
&lt;strong&gt;But what is the functionality of these apps?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Shopping list apps simplify your shopping experience, Expense trackers show how much you spend on groceries overall, and coupon apps like Flipp &amp;amp; Reebee help to find the best deals &amp;amp; discounts at your stores.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;All of that sounds great! But do you know how much you spend on a specific grocery item? What is the average price you pay for this product? Where does this item cost less? Did the price for that product go up or down?&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;None of these apps have this specific functionality.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Flipp is relatively close to what I want but it still aims different goal.&lt;/p&gt;

&lt;p&gt;Wouldn’t it be nice to keep track of prices for the items you typically purchase, monitor changes over time, compare prices across different stores, and gain insights into your grocery shopping habits such as identifying the most expensive and cheapest items, the most and least purchased items, the average bill amount, etc?&lt;/p&gt;

&lt;p&gt;There are multiple Reddit threads where people discussed the same thing:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.reddit.com/r/Frugal/comments/11udh7d/appprogram_that_automatically_tracks_which/"&gt;App/program that automatically tracks which grocery store has the best price for an item?&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.reddit.com/r/Frugal/comments/xet5e9/any_apps_to_track_my_grocery_purchases_and_prices/"&gt;Any apps to track my grocery purchases and prices&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.reddit.com/r/PersonalFinanceCanada/comments/16uvecn/receipt_scanning_price_tracking_app_for_groceries/"&gt;Receipt scanning &amp;amp; price tracking app for groceries?&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.reddit.com/r/Frugal/comments/30tx46/simple_price_tracking_app_for_groceries/"&gt;Simple price tracking app for groceries?&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.reddit.com/r/povertyfinance/comments/ae59on/price_book_app_for_tracking_grocery_costs_across/"&gt;Price book app for tracking grocery costs across several stores&lt;br&gt;
&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Seems like most people use spreadsheets for this or even paper books!&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;So, wouldn’t it be great to automate this process and have an app?&lt;/p&gt;

&lt;p&gt;Instead of constantly filling Excel sheets or writing in a paper book, wouldn’t it be much easier to just scan your paper receipt, and get all the necessary data from it such as item name, unit (kg, ea, etc.), price per unit, quantity, and total, save it and then have statistics on this particular purchase, specific items and their price fluctuations?&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%2Fw0x2jwmylovhposqob39.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%2Fw0x2jwmylovhposqob39.png" alt="Loblaws receipt scan using OCR technology&amp;lt;br&amp;gt;
" width="800" height="356"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Using powerful OCR software (Optical Character Recognition), users would be able to scan receipts, and in most cases, the results would be quite accurate. The challenge with paper receipts lies in the quality of the paper they are printed on, which may not always be optimal. As a result, there are instances where OCR software may struggle to scan them accurately or may produce errors in the scan. However, users will always have the option to correct any inaccuracies manually or enter the data manually themselves.&lt;/p&gt;

&lt;p&gt;After saving this data, users will always have access to their purchase history. They can view exactly how many purchases they made and how much they spent in total each month, over the last three months, or within any other custom date range.&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%2Fvcv6aad8ybuzrnircv6b.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%2Fvcv6aad8ybuzrnircv6b.png" alt="Purchase history of a user&amp;lt;br&amp;gt;
" width="800" height="352"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Users will also have access to detailed statistics for each item they’ve ever purchased. This includes the average, lowest, and highest prices for the item, total expenditure on it, the number of times it was purchased, purchase dates, price fluctuations over time, and where the item costs less.&lt;/p&gt;

&lt;p&gt;With this comprehensive data at their disposal, users can make informed, data-driven decisions. For example, they may decide it’s time to switch to another brand to save money, or perhaps it’s best to stop purchasing the item altogether.&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%2Fyifcbhabrm71xvl130j6.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%2Fyifcbhabrm71xvl130j6.png" alt="Insights on a particular item&amp;lt;br&amp;gt;
" width="800" height="356"&gt;&lt;/a&gt;&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%2Ffaiy520kwpni0g9d64xi.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%2Ffaiy520kwpni0g9d64xi.png" alt="Price fluctuations on a particular item&amp;lt;br&amp;gt;
" width="800" height="363"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I’ve been dedicating my free time to developing a rough prototype of this app. While I’m currently in the process of refactoring the backend, I’ve had the chance to use it myself. Despite its current limitations, it has already provided valuable insights into my grocery spending behaviour.&lt;/p&gt;

&lt;p&gt;Using the prototype, I’ve gained a clearer understanding of my spending habits. It has highlighted areas where I was overspending on unnecessary items and revealed price fluctuations in some of my frequently purchased items. This insight made me consider switching to more cost-effective brands. Overall, the prototype has proven to be a useful tool for tracking my grocery expenses.&lt;/p&gt;




&lt;p&gt;As a relatively frugal person who likes tracking each dollar I spend, I found it really helpful and thought maybe other people like me also want to analyze their spending behaviour when making groceries.&lt;br&gt;
That’s why I am here, asking for your honest opinion.&lt;/p&gt;

&lt;h2&gt;
  
  
  Would an app like this be useful to you?
&lt;/h2&gt;

&lt;h2&gt;
  
  
  Do you believe it could help you analyze and manage your grocery spending effectively?
&lt;/h2&gt;

&lt;p&gt;Let me know in the comment section below if you would actually use an app like this or maybe there is already one like that with a good design and all the features mentioned above that you’ve been using for a while.&lt;br&gt;
Feel free to share any additional ideas or suggestions you may have for improving this app.&lt;/p&gt;

&lt;p&gt;Your feedback is greatly appreciated.&lt;/p&gt;




&lt;p&gt;And that's it, guys.&lt;/p&gt;

&lt;p&gt;I hope that you have learned something new today!&lt;br&gt;
I would appreciate it if you could like this post or leave a comment below!&lt;/p&gt;

&lt;p&gt;Feel free to follow me on &lt;a href="https://github.com/Kai4ik"&gt;GitHub&lt;/a&gt; and &lt;a href="https://medium.com/@kai4ik"&gt;Medium&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;Adios, mi amigos)&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%2Fu1v024lcsdub269ioqtk.gif" 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%2Fu1v024lcsdub269ioqtk.gif" alt="Bye bye meme" width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>saas</category>
      <category>inflation</category>
      <category>webdev</category>
    </item>
    <item>
      <title>CSS Stacked Cards: Simple and Easy way</title>
      <dc:creator>Kairat</dc:creator>
      <pubDate>Sat, 27 Jan 2024 23:45:09 +0000</pubDate>
      <link>https://dev.to/kairatorozobekov/css-stacked-cards-simple-and-easy-way-3lm9</link>
      <guid>https://dev.to/kairatorozobekov/css-stacked-cards-simple-and-easy-way-3lm9</guid>
      <description>&lt;p&gt;Probably you've seen this cool effect on some websites where images stack on top of each other, you liked this animation and were wondering how it can be done.&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%2F1w77rni2njv82qnwuaaw.gif" 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%2F1w77rni2njv82qnwuaaw.gif" alt="Stacked Cards using CSS demonstration"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Do I need to use the animation library for that? GSAP? Or maybe Framer Motion.&lt;/p&gt;

&lt;p&gt;And the actual answer is no. It can be done with just plain CSS!&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%2F9ftvd84w74w0cewe3cjl.jpg" 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%2F9ftvd84w74w0cewe3cjl.jpg" alt="What if I told you CSS could do that meme"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is the code sample:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

&amp;lt;div&amp;gt;
    &amp;lt;div id="layer1" class="bg-red-400  h-screen w-screen flex justify-center items-center top-0 sticky"&amp;gt;
        &amp;lt;p&amp;gt;CSS&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;div id="layer2" class="bg-orange-400 h-screen w-screen flex justify-center items-center top-0 sticky"&amp;gt;
        &amp;lt;p&amp;gt;is&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;div id="layer3" class="bg-blue-400 h-screen w-screen flex justify-center items-center top-0 sticky"&amp;gt;
        &amp;lt;p&amp;gt;not&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;div id="layer4" class="bg-green-400 h-screen w-screen flex justify-center items-center top-0 sticky"&amp;gt;
        &amp;lt;p&amp;gt;awesome!&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;


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

&lt;/div&gt;

&lt;p&gt;In this example I used TailwindCSS, but you can use whatever you want - plain CSS or any other library. &lt;br&gt;
So, the most important properties here are these two:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;&lt;strong&gt;top-0&lt;/strong&gt;&lt;/em&gt; that sets top to 0&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;&lt;strong&gt;sticky&lt;/strong&gt;&lt;/em&gt; that sets the position of the card to sticky&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And not to forget, all cards are contained within the div - the containing block-level ancestor.&lt;/p&gt;




&lt;p&gt;And that's it, guys.&lt;/p&gt;

&lt;p&gt;I hope that you have learned something new today!&lt;br&gt;
I would appreciate it if you could like this post or leave a comment below!&lt;/p&gt;

&lt;p&gt;Also, feel free to follow me on &lt;a href="https://github.com/Kai4ik" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; and &lt;a href="https://medium.com/@kai4ik" rel="noopener noreferrer"&gt;Medium&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;Adios, mi amigos)&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%2F02fmxavj6v951cpykmig.gif" 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%2F02fmxavj6v951cpykmig.gif" alt="Bye bye dear friend"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>css</category>
      <category>html</category>
      <category>webdev</category>
      <category>tailwindcss</category>
    </item>
    <item>
      <title>Email Obfuscation: Best Methods</title>
      <dc:creator>Kairat</dc:creator>
      <pubDate>Fri, 19 Jan 2024 22:43:25 +0000</pubDate>
      <link>https://dev.to/kairatorozobekov/email-obfuscation-best-methods-52eh</link>
      <guid>https://dev.to/kairatorozobekov/email-obfuscation-best-methods-52eh</guid>
      <description>&lt;p&gt;If you are a front-end developer, there is a high chance that you've been asked to integrate contact details on the website at least once. It might be social media links, phone numbers, emails, etc.&lt;/p&gt;

&lt;p&gt;And it sounds easy, right? What could go wrong? It should be simple - just use the "a" element and you should be good. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;a href="mailto:randomemail@address.com"&amp;gt;Email&amp;lt;/a&amp;gt;&lt;br&gt;
&lt;/code&gt;&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%2F76y8yljch2za9pngx2kc.jpg" 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%2F76y8yljch2za9pngx2kc.jpg" alt="Easy Peasy Meme"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's say you do that and a month later you end up having dozens of spam emails coming from random places that you never signed up for. &lt;br&gt;
Well, do not be surprised cause the approach we used has one problem - our email address became an easy target for crawlers, spam bots and phishing attacks.&lt;/p&gt;




&lt;h2&gt;
  
  
  What should you do instead?
&lt;/h2&gt;

&lt;p&gt;To avoid this problem, there is a working (not 100%) solution called email obfuscation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is that?&lt;/strong&gt;&lt;br&gt;
So, let's start with the concept of obfuscation. &lt;br&gt;
If you've never heard of it, basically the idea behind it is to make the code difficult for computers / human beings to understand.&lt;br&gt;
In case of email obfuscation, we try to confuse those bots &amp;amp; crawlers and make our email unreadable for them or difficult to understand.&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%2F3oiams7whwlu6e24iwku.jpg" 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%2F3oiams7whwlu6e24iwku.jpg" alt="Not sure if it is a bot meme"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But as I mentioned previously, it not only makes it difficult for computers but also for users as well and can result in a worse user experience.&lt;/p&gt;

&lt;p&gt;That's why our solutions should be relatively effective against bots but at the same time accessible for the end-users and not worsen overall UX. &lt;/p&gt;




&lt;h2&gt;
  
  
  What are the techniques?
&lt;/h2&gt;

&lt;p&gt;Before looking into that, I need to mention that sometimes we do not need email to be a link. Sometimes it can be just part of the plain text and not clickable at all.&lt;br&gt;
Nevertheless, we have to protect these email addresses as well, but just using different techniques.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First, let's start with the clickable emails.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;One way would be to use &lt;strong&gt;HTML (Character) Entities&lt;/strong&gt;. An HTML entity is a piece of text ("string") that begins with an ampersand ( &amp;amp; ) and ends with a semicolon ( ; ). For example, this s entity represents "s".&lt;br&gt;
Usually, they are used to display reserved characters (which would otherwise be interpreted as HTML code), or invisible characters (like non-breaking spaces). &lt;br&gt;
In our case, we use entities to display the email address. Almost any web browser should be able to translate it back into readable format.&lt;/p&gt;

&lt;p&gt;As an example, &lt;strong&gt;&lt;a href="mailto:example@gmail.com"&gt;example@gmail.com&lt;/a&gt;&lt;/strong&gt; would look like this: &lt;code&gt;&amp;amp;#101;&amp;amp;#120;&amp;amp;#097;&amp;amp;#109;&amp;amp;#112;&amp;amp;#108;&amp;amp;#101;&amp;amp;#064;&amp;amp;#103;&amp;amp;#109;&amp;amp;#097;&amp;amp;#105;&amp;amp;#108;&amp;amp;#046;&amp;amp;#099;&amp;amp;#111;&amp;amp;#109;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You can do this manually or using online tools (&lt;a href="https://www.hcidata.info/obfuscate-email-address.htm" rel="noopener noreferrer"&gt;https://www.hcidata.info/obfuscate-email-address.htm&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;While it might help against simple scrapers and bots, it is not the best solution as it is super easy to decode.&lt;/p&gt;




&lt;p&gt;Another way to obfuscate our email would be to &lt;strong&gt;involve JavaScript&lt;/strong&gt; in the process. &lt;br&gt;
Let's say we have the following HTML element:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;a id="emailLinkID" href=""&amp;gt;Send me an Email&amp;lt;/a&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Then we can run the JS script to find that tag by ID and modify its href attribute by decoding base64 encoded string.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

const emailForm = document.getElementById("emailLinkID");
emailForm?.setAttribute("href", "mailto:".concat(window.atob(window.btoa("example@gmail.com"))));


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

&lt;/div&gt;

&lt;p&gt;So, now when the browser finishes loading and the script above executes, the href attribute is populated with the actual email address.&lt;/p&gt;

&lt;p&gt;You can use other encryption algorithms like ROT13 or &lt;a href="https://cryptii.com/pipes/caesar-cipher" rel="noopener noreferrer"&gt;Caesar cipher&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The only major downside of this approach is that it involves JavaScript and if the user disables JS on their browser, the link will be empty.&lt;/p&gt;




&lt;h2&gt;
  
  
  Now let's consider those cases where email does not need to be clickable.
&lt;/h2&gt;

&lt;p&gt;The simplest but at the same time almost worthless solution would be to use HTML comments.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;p&amp;gt;Email address: &amp;lt;!-- randomcomment--&amp;gt; email@&amp;lt;!-- anothercomment--&amp;gt;gma&amp;lt;!-- asjoxp --&amp;gt;il.com&amp;lt;/p&amp;gt;&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
For the end-user comments won't show up and regular email address will be seen.&lt;/p&gt;




&lt;p&gt;A more effective solution would be to use CSS.&lt;br&gt;
&lt;code&gt;&amp;lt;p&amp;gt;Email Address example@&amp;lt;b&amp;gt;hiddentexthere&amp;lt;/b&amp;gt;.com &amp;lt;/p&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Then using CSS hide the content of the "b" tag.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

p.b {
  display: none;
}


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

&lt;/div&gt;

&lt;p&gt;Again the final user will not see "hiddentexthere" part.&lt;/p&gt;




&lt;p&gt;Another solution would be to put an email address into an image which will make bots' lives more difficult but at the same time from a usability point of view, it is super inconvenient for the user as he/she won't be able to just copy it.&lt;/p&gt;




&lt;p&gt;Another quite effective way of obfuscation is the simple replacement of “@” with “AT” and “.” with “DOT”. But again, not good for the final user.&lt;/p&gt;




&lt;p&gt;And that's it, guys.&lt;/p&gt;

&lt;p&gt;I hope that you have learned something new today!&lt;br&gt;
I would appreciate it if you could like this post or leave a comment below!&lt;/p&gt;

&lt;p&gt;Also, feel free to follow me on &lt;a href="https://github.com/Kai4ik" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; and &lt;a href="https://medium.com/@kai4ik" rel="noopener noreferrer"&gt;Medium&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;Adios, mi amigos)&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%2F02fmxavj6v951cpykmig.gif" 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%2F02fmxavj6v951cpykmig.gif" alt="Bye bye dear friend"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>html</category>
      <category>css</category>
    </item>
    <item>
      <title>Unlocking Seamless Deployments: Crafting a CD Pipeline with HyGraph Webhooks, Serverless Functions, and GitHub Actions!</title>
      <dc:creator>Kairat</dc:creator>
      <pubDate>Tue, 14 Nov 2023 23:23:30 +0000</pubDate>
      <link>https://dev.to/kairatorozobekov/unlocking-seamless-deployments-crafting-a-cd-pipeline-with-hygraph-webhooks-serverless-functions-and-github-actions-14i</link>
      <guid>https://dev.to/kairatorozobekov/unlocking-seamless-deployments-crafting-a-cd-pipeline-with-hygraph-webhooks-serverless-functions-and-github-actions-14i</guid>
      <description>&lt;p&gt;So, your company relies on &lt;strong&gt;HyGraph&lt;/strong&gt; as the primary &lt;strong&gt;Content Management System (CMS)&lt;/strong&gt;, a tool you find enjoyable to work with due to its user-friendly interface, GraphQL API, and seamless integrations with Cloudinary, AltText, and more.&lt;/p&gt;

&lt;p&gt;You successfully built and deployed your &lt;strong&gt;static website&lt;/strong&gt;, everything works great, and everyone is happy. &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%2F5rr30jpxh6ov3nbsq55v.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%2F5rr30jpxh6ov3nbsq55v.png" alt="Happy Face Meme Emoji" width="800" height="866"&gt;&lt;/a&gt;&lt;br&gt;
A few days later, someone at your company messages you and says that they updated content on HyGraph and published it, but they don't see any changes on the website.&lt;/p&gt;

&lt;p&gt;And it is the moment when you realize that you need a CD pipeline: all your pages are statically generated at the build time causing the new content from HyGraph not to show up on the website, and you have a choice: you can do it manually - &lt;em&gt;rebuild the pages and upload to your hosting platform or automate it&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;What should you choose? &lt;br&gt;
As developers, we always go with the second option - even if it takes more time than doing stuff manually (not in this case, at least in the long term).&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%2Facsrx6pkmeysotdx9x5i.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%2Facsrx6pkmeysotdx9x5i.png" alt="Developer Automation Meme" width="640" height="640"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  So, you decided to automate it.
&lt;/h2&gt;
&lt;h2&gt;
  
  
  What do you need for this?
&lt;/h2&gt;

&lt;p&gt;First, you need a &lt;strong&gt;GitHub Actions workflow&lt;/strong&gt; that will rebuild our website with the latest data from HyGraph.&lt;br&gt;
If you are not aware of Github workflows, basically it is an automated process that will run one or more jobs when triggered.&lt;br&gt;
You can read more about it in this &lt;a href="https://docs.github.com/en/actions/using-workflows/about-workflows"&gt;article by GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;A simple GitHub workflow can look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: Name of the workflow

# when it should be triggered
on:
  - push
  - workflow_dispatch

jobs:
  # job name
  cypress-run:
    # running tests

  # another job
  build:
    # steps to build your pages
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The logic for the build process would vary based on what library/framework you used.&lt;/p&gt;

&lt;p&gt;For us, the most important part here is when the workflow is triggered. There are a lot of ways to trigger the GitHub Actions workflow - in our example, it starts on the push to the repository and on workflow dispatch.&lt;/p&gt;

&lt;h2&gt;
  
  
  And probably you have a question - what the heck is workflow dispatch?
&lt;/h2&gt;

&lt;p&gt;Basically, workflow dispatch allows us to trigger workflow manually using the GitHub API, GitHub CLI, or GitHub browser interface.&lt;/p&gt;

&lt;p&gt;So, how it can help us achieve our goal? As mentioned above, we can use Github API to trigger our workflow and that is exactly what we gonna do.&lt;/p&gt;




&lt;h2&gt;
  
  
  We have a GitHub Actions workflow that rebuilds our pages and can be manually triggered using &lt;strong&gt;Github API&lt;/strong&gt;. Now what?
&lt;/h2&gt;

&lt;p&gt;The next step would be to somehow trigger it with the help of GitHub API.&lt;br&gt;
How? By making a &lt;strong&gt;POST&lt;/strong&gt; request to this endpoint:&lt;br&gt;
&lt;code&gt;https://api.github.com/repos/OWNER/REPO/actions/workflows/WORKFLOW_ID/dispatches&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You have to replace &lt;strong&gt;OWNER, REPO and WORKFLOW_ID&lt;/strong&gt; with the actual data (workflow id should be the name of a file (with extension) - &lt;strong&gt;deploy.yaml&lt;/strong&gt; as an example)&lt;/p&gt;

&lt;p&gt;Plus, you have to provide one required body parameter - &lt;strong&gt;ref&lt;/strong&gt; which is the git reference for the workflow. The reference can be a branch or tag name.&lt;/p&gt;

&lt;p&gt;It is the simplest way we can use this endpoint but if you want to know more - feel free to go through &lt;a href="https://docs.github.com/en/rest/actions/workflows?apiVersion=2022-11-28#create-a-workflow-dispatch-event"&gt;GitHub docs on this particular topic&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now, we know the endpoint we should use to trigger the workflow and the only problem left is where to make this call.&lt;/p&gt;



&lt;p&gt;Probably while working with HyGraph you've seen the &lt;strong&gt;"Webhooks"&lt;/strong&gt; tab in the menu but never got a chance to learn more about it.&lt;/p&gt;

&lt;p&gt;Probably it would be the perfect time to do this!&lt;br&gt;
Why? &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;HyGraph WebHooks&lt;/strong&gt; is a method designed to detect changes that happen to content within your project.&lt;br&gt;
This means that we can be notified whenever new content is published, or existing is updated, by subscribing to these events, and then perform our own custom logic.&lt;/p&gt;

&lt;p&gt;Cool, does it basically mean that we can just use Webhook, send a POST request to the Github API endpoint we discussed above and that's it, everything would work like a charm?&lt;/p&gt;



&lt;p&gt;In an ideal world, yes! But there is one minor problem that won't make it as easy as you think.&lt;/p&gt;

&lt;p&gt;As was mentioned above, when sending a POST request to this endpoint (&lt;a href="https://api.github.com/repos/OWNER/REPO/actions/workflows/WORKFLOW_ID/dispatches"&gt;https://api.github.com/repos/OWNER/REPO/actions/workflows/WORKFLOW_ID/dispatches&lt;/a&gt;), we also need to send a body with the &lt;strong&gt;ref parameter&lt;/strong&gt; set to either tag or branch name.&lt;/p&gt;

&lt;p&gt;Maybe you worked with other CMS tools such as Contently, and DatoCMS, and with these tools, this logic would work perfectly fine cause their webhooks implementation has a feature of &lt;strong&gt;customizable payload&lt;/strong&gt; - which means you can modify the body (data) you send with your requests.&lt;/p&gt;

&lt;p&gt;Unfortunately, in HyGraph there is no such feature yet (but I got in touch with one of their sales representatives and he confirmed that it should be released soon, but did not give a specific timeline).&lt;/p&gt;



&lt;p&gt;Because of this, we need a &lt;strong&gt;Serverless Function&lt;/strong&gt; that will serve as a middleman whose entire purpose would be to add this &lt;strong&gt;"ref"&lt;/strong&gt; parameter to the body of the request and actually make this POST request&lt;/p&gt;

&lt;p&gt;I hope you are all familiar with the concept of serverless functions, but if not, you can read this &lt;a href="https://www.cloudflare.com/learning/serverless/why-use-serverless/"&gt;short article by Cloudflare&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can use AWS Lambda Functions, Azure Functions, GCP Cloud Functions or any other implementation of serverless function. &lt;br&gt;
Up to you!&lt;/p&gt;

&lt;p&gt;I am going to use Azure Functions, and here is an example of how the implementation of that function should look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; const axios = require("axios");
  let data = JSON.stringify({
    ref: "main",
  });

  let config = {
    method: "post",
    maxBodyLength: Infinity,
    url: "https://api.github.com/repos/OWNER/REPO/actions/workflows/WORKFLOW_ID/dispatches",
    headers: {
      Accept: "application/vnd.github+json",
      "Content-Type": "application/json",
      Authorization:
        "Bearer GITHUB_PERSONAL_ACCESS_TOKEN",
    },
    data: data,
  };

  axios
    .request(config)
    .then((response) =&amp;gt; {
      context.log(JSON.stringify(response.data));
    })
    .catch((error) =&amp;gt; {
      context.log(error);
    });

  return { body: `OK` };
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;My Azure Function uses Node.js as a runtime and thus I use Axios to make a request. But you can use any other programming language based on your preferences.&lt;/p&gt;

&lt;p&gt;The most important part here is that we construct the body of our request and include the &lt;strong&gt;"ref"&lt;/strong&gt; parameter set to &lt;strong&gt;"main"&lt;/strong&gt; (branch name, but can tag name as well).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Don't forget to change the OWNER, REPO, WORKFLOW_ID and GITHUB_PERSONAL_ACCESS_TOKEN with the actual data.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;Now, when you have your serverless function ready and live, it's time to configure HyGraph Webhook.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Go to the &lt;strong&gt;"Webhooks"&lt;/strong&gt; tab and click the &lt;strong&gt;"Add"&lt;/strong&gt; button.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Configure your webhook - give it a &lt;strong&gt;name&lt;/strong&gt;, and a &lt;strong&gt;short description&lt;/strong&gt; of what it does, choose the &lt;strong&gt;method&lt;/strong&gt;, and provide the &lt;strong&gt;serverless function public URL&lt;/strong&gt; (make sure, your serverless function is accessible)&lt;br&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%2F2vg0wnmujwzx2g5b2eun.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%2F2vg0wnmujwzx2g5b2eun.png" alt="HyGraph Webhook Configuration" width="743" height="633"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Choose which models, actions, and stages should trigger this webhook&lt;br&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%2Fnwuximlcmj88mbnfbmna.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%2Fnwuximlcmj88mbnfbmna.png" alt="HyGraph Webhook Configurationn" width="729" height="697"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click the &lt;strong&gt;"Add"&lt;/strong&gt; button at the top.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And here we go! You just created your HyGraph Webhook.&lt;/p&gt;




&lt;p&gt;Now, your CD pipeline is ready!&lt;/p&gt;

&lt;p&gt;Whenever you make a change to your content in the HyGraph and publish it (or unpublish), it triggers a webhook that makes a call to the serverless function, which then sends a POST request to the Github API and triggers workflow which fetches the latest data from HyGraph, rebuilds the pages and pushes updated content to the hosting platform. &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%2Fjurgfj1vymyx0isvgkdj.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%2Fjurgfj1vymyx0isvgkdj.png" alt="CD Pipeline Structure" width="800" height="248"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;And that's it, guys.&lt;/p&gt;

&lt;p&gt;I hope that you have learned something new today!&lt;br&gt;
I would appreciate it if you could like this post or leave a comment below!&lt;/p&gt;

&lt;p&gt;Also, feel free to follow me on &lt;a href="https://github.com/Kai4ik"&gt;GitHub&lt;/a&gt; and &lt;a href="https://medium.com/@kai4ik"&gt;Medium&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;Adios, mi amigos)&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%2F0laqf22pdnmqojy76k04.jpg" 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%2F0laqf22pdnmqojy76k04.jpg" alt="Grumpy cat saying bye bye" width="460" height="567"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>githubactions</category>
      <category>cms</category>
      <category>serverless</category>
    </item>
    <item>
      <title>Setting up TWS &amp; IBC on EC2 instance</title>
      <dc:creator>Kairat</dc:creator>
      <pubDate>Tue, 11 Oct 2022 07:15:09 +0000</pubDate>
      <link>https://dev.to/kairatorozobekov/setting-up-tws-ibc-on-ec2-instance-88b</link>
      <guid>https://dev.to/kairatorozobekov/setting-up-tws-ibc-on-ec2-instance-88b</guid>
      <description>&lt;p&gt;Probably if you are a developer who was always interested in the stock market or any other type of investing, you have at least once thought about creating some sort of system that will help you get the market data and then use it for analysis&lt;/p&gt;

&lt;p&gt;Especially these days when the stock market is crashing, the Japanese yen and British pound are hitting record lows against the dollar, etc.&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%2F8wh5766xwjiti3fs2802.jpg" 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%2F8wh5766xwjiti3fs2802.jpg" alt="I am fine meme"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Probably this is you when you open your portfolio&lt;/p&gt;




&lt;p&gt;And because you definitely do not want to be in such a situation, you made a research on what kind of options you have to get the market data and found something called IB Trader Workstation (or TWS)&lt;/p&gt;

&lt;p&gt;You start digging more and turns out that this TWS is actually exactly what you need - it gives you access not only to historical data but real-time too. Moreover, you get access to analysis tools, interactive, customizable charts, you can execute orders, etc.&lt;/p&gt;

&lt;p&gt;Sounds quite interesting, right?&lt;br&gt;
So, you go and install it on your local machine, play with it for a week as you absolutely loved it&lt;/p&gt;




&lt;p&gt;Then you finally want to start coding and you stumble into TWS API which is a simple interface to TWS through which you can do a lot of stuff - from automating your trading strategies &amp;amp; requesting market data to monitoring your account balance and portfolio in real-time.&lt;/p&gt;

&lt;p&gt;And the main requirement for this API to work is a running instance of Trader Workstation app (or IB Gateway, but we are not here to talk about it)&lt;/p&gt;

&lt;p&gt;As TWS been already installed on your machine, you start writing code using TWS API - either its native version or any third-party libraries such as ib_insync.&lt;/p&gt;

&lt;p&gt;You test your code and it works perfectly fine!&lt;/p&gt;

&lt;p&gt;But then you start thinking that running TWS on your local machine is not a long-term solution and you decide to switch everything to the cloud&lt;/p&gt;




&lt;p&gt;You think about which cloud platform you should use and decide to go with AWS, cause Jeff Bezos is the best&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%2Fz0gwwzhvbph6y4bbhycf.jpg" 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%2Fz0gwwzhvbph6y4bbhycf.jpg" alt="Jeff Bezos stonks meme"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The first thing you have to do is launch the EC2 instance which we will use to run our Trader Workstation app&lt;/p&gt;

&lt;p&gt;We are going to use Ubuntu as our AMI and for the instance type we will choose t2.medium (cause if you choose a less powerful instance type, TWS will work a little bit slow)&lt;/p&gt;

&lt;p&gt;I suppose now you have a running EC2 Ubuntu instance with the instance type mentioned above and now let's start working on the installation of TWS on this EC2 machine&lt;/p&gt;




&lt;p&gt;First, let's connect to your EC2 instance.&lt;br&gt;
One of the easiest ways is through using SSH (you also need to have access to the file with the key pair that you used when creating the EC2 instance)&lt;/p&gt;

&lt;p&gt;Choose your EC2 instance and click on the "Connect" button&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%2F1mf64bppq0vlerdcqqsj.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%2F1mf64bppq0vlerdcqqsj.png" alt="Connect button"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From here, choose "SSH client" and just copy the example code&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%2Fmmehghp9gv0ak8r7n9te.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%2Fmmehghp9gv0ak8r7n9te.png" alt="SSH code"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then, use the terminal of your choice, and run the copied command (make sure that you run this command from the same folder where your key file is located)&lt;/p&gt;

&lt;p&gt;You successfully connected to your EC2 and now let's run the following general commands&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

sudo apt update
sudo apt -y upgrade


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

&lt;/div&gt;

&lt;p&gt;After that, we have to install 2 additional things: XVFB and VNC server&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;XVFB&lt;/strong&gt; - is an X server that can run on machines with no display hardware and no physical input devices. It emulates a dumb framebuffer using virtual memory. &lt;br&gt;
In simple words, we will use it to add GUI to our EC2 instance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;VNC&lt;/strong&gt; - is basically a screen-sharing system that is used to remotely control another computer.&lt;br&gt;
It works on a client/server model which means that we install the VNC server on the remote computer - the one we want to control (in our case the EC2 instance) and on our local machine we install the VNC Client that we use to control another computer&lt;/p&gt;

&lt;p&gt;To install XVFB run this command:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

sudo apt install -y xvfb


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

&lt;/div&gt;

&lt;p&gt;As our VNC server, we will use &lt;strong&gt;x11vnc&lt;/strong&gt; which allows us to view remotely and interact with real X displays (i.e. a display corresponding to a physical monitor, keyboard, and mouse) with any VNC viewer.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

sudo apt install -y x11vnc


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

&lt;/div&gt;

&lt;p&gt;Then, run this command to start virtual frame buffer&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

/usr/bin/Xvfb :0 -ac -screen 0 1024x768x24 &amp;amp;


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

&lt;/div&gt;

&lt;p&gt;When you run this command, you should get the ID of the Xvfb background process&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%2Fzci435rxhp28r3ynj3nl.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%2Fzci435rxhp28r3ynj3nl.png" alt="Xvfb process ID"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To start the VNC server, we will run this command where we specify 2 passwords - full access (keyboard + mouse) and second is view only.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

/usr/bin/x11vnc -ncache 10 -ncache_cr -viewpasswd view_only_password -passwd full_access_password -display :0 -forever -shared -bg -noipv6


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

&lt;/div&gt;

&lt;p&gt;Also, we define what server display it should connect to (in our case, it is :0).&lt;br&gt;
&lt;em&gt;&lt;strong&gt;noipv6&lt;/strong&gt;&lt;/em&gt; option states that it should not try to use IPv6 for any listening or connecting sockets.&lt;br&gt;
The &lt;em&gt;&lt;strong&gt;shared&lt;/strong&gt;&lt;/em&gt; option allows more than one viewer to connect at the same time and the &lt;em&gt;&lt;strong&gt;forever&lt;/strong&gt;&lt;/em&gt; option tells our VNC server to keep listening for more connections rather than exiting as soon as the first client disconnects&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%2Fjlaoivok5rys7qs807nj.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%2Fjlaoivok5rys7qs807nj.png" alt="VNC Server port"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now VNC server is listening on port 5900 and we can connect to it using any VNC client you want &lt;/p&gt;

&lt;p&gt;I am going to use &lt;a href="https://www.tightvnc.com/download.php" rel="noopener noreferrer"&gt;TightVNC&lt;/a&gt; but there are many other options such as TigerVNC, UltraVNC, etc. &lt;/p&gt;

&lt;p&gt;Before connecting to our server, we have to make a few adjustments to the security group assigned to our EC2 instance.&lt;br&gt;
Basically, we need to allow inbound traffic to port 5900 and we can easily do this by going to the AWS console, choosing our instance's security group and editing its inbound rules&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%2Fe906nechlx06mplpenxo.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%2Fe906nechlx06mplpenxo.png" alt="Chooseing EC2 security group"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here we add a new rule of type Custom TCP and define our IP as a source&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%2Fqvnq6ly8f3p6d9brdwj6.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%2Fqvnq6ly8f3p6d9brdwj6.png" alt="Adding new inbound rule"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After that, we connect to the VNC Server using TightVNC Viewer.&lt;br&gt;
In the hostname field, we enter the public DNS of our EC2 instance&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%2Fll4sjpwwwtdwud8zxuq7.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%2Fll4sjpwwwtdwud8zxuq7.png" alt="EC2 public DNS"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And we use the full access password that you entered when starting the VNC Server&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%2Fwpgdrj68pr5gq1byyjly.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%2Fwpgdrj68pr5gq1byyjly.png" alt="VNC authentication"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After connecting to our VNC server, let's make our life a little bit easier and install metacity - a lightweight window manager.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

sudo apt install -y metacity


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

&lt;/div&gt;

&lt;p&gt;And run this command:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

DISPLAY=:0 metacity &amp;amp;


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

&lt;/div&gt;




&lt;p&gt;Now we are ready to install the Trader Workstation app.&lt;br&gt;
First, let's install offline TWS for Linux on our local machine&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.interactivebrokers.com/en/index.php?f=16044&amp;amp;ns=T" rel="noopener noreferrer"&gt;Offline TWS for Linux&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;After installation is complete, we will copy this file to our EC2 instance.&lt;/p&gt;

&lt;p&gt;In order to do that, we will use the &lt;strong&gt;&lt;em&gt;scp&lt;/em&gt;&lt;/strong&gt; CLI tool that allows you to securely copy files and directories between two locations&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

scp -i /path_to_key_file tws-latest-standalone-linux-x64.sh ubuntu@public-ip-address-of-ec2-instance:


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

&lt;/div&gt;

&lt;p&gt;Do not forget to add ":" at the end of a public IP address&lt;/p&gt;

&lt;p&gt;This command should copy the installation file from our local machine to the EC2 instance (to the home directory)&lt;/p&gt;

&lt;p&gt;We can check this by running the &lt;em&gt;&lt;strong&gt;ls&lt;/strong&gt;&lt;/em&gt; command:&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%2Fzao1xsckgph7pflrgo2v.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%2Fzao1xsckgph7pflrgo2v.png" alt="Copied file"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now let's make this file executable &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

chmod a+x ~/tws-latest-standalone-linux-x64.sh


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

&lt;/div&gt;

&lt;p&gt;And as we have Xvfb installed, we can run the TWS installer on display ":0"&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

DISPLAY=:0 ~/tws-latest-standalone-linux-x64.sh


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

&lt;/div&gt;

&lt;p&gt;In TightVNC Viewer, you can see that the installation window is opened&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%2Fgndi4o5uqsut0q7441i6.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%2Fgndi4o5uqsut0q7441i6.png" alt="TWS installer"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Follow the steps to complete the TWS installation.&lt;br&gt;
When you finish installation, you can run the &lt;em&gt;&lt;strong&gt;ls&lt;/strong&gt;&lt;/em&gt; command&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%2Fwde26rc5icux0pu3546i.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%2Fwde26rc5icux0pu3546i.png" alt="New Jts folder"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see, the Jts folder was created which contains all TWS-required files&lt;/p&gt;

&lt;p&gt;Now you just have to use TWS credentials to log in and configure TWS as you want&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%2Fhywctqebnwx4q9ec24fg.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%2Fhywctqebnwx4q9ec24fg.png" alt="TWS login page"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And now you have the Trader Workstation app hosted on an EC2 instance!&lt;/p&gt;




&lt;p&gt;So, let's say you've been using it for a few days and noticed that sometimes your TWS loses connection and asks for manual authentication&lt;br&gt;
And it is definitely not the way you wanted it to work&lt;/p&gt;

&lt;p&gt;Wouldn't it be perfect if there were a tool that can run TWS in 'hands-free' mode (without any manual intervention), right?&lt;/p&gt;

&lt;p&gt;I agree that it would be nice to have such a tool and fortunately for us, there is one called &lt;a href="https://github.com/IbcAlpha/IBC/blob/master/userguide.md" rel="noopener noreferrer"&gt;IBC&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can follow the link above and read more about what IBC can do, but for our purpose, we will use it to start the TWS app and login automatically in case our TWS loses connection&lt;/p&gt;

&lt;p&gt;First, we need to install it by running this command:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

wget https://github.com/IbcAlpha/IBC/releases/download/3.14.0/IBCLinux-3.14.0.zip


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

&lt;/div&gt;

&lt;p&gt;Then, let's install unzip command (if you don't have it installed already)&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

sudo apt install -y unzip


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

&lt;/div&gt;

&lt;p&gt;Before extracting the contents of the downloaded ZIP file, I want you to create a new folder - &lt;em&gt;&lt;strong&gt;/opt/ibc/&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;We do this because several IBC script files assume the default paths used for particular files (config file, TWS &amp;amp; IBC program files) and by using these default paths we will minimize customizing the configuration file and the shell scripts.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

mkdir -p opt/ibc/
cd opt/ibc


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

&lt;/div&gt;

&lt;p&gt;Now let's unzip a file&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

unzip ../../IBCLinux-3.14.0.zip


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

&lt;/div&gt;

&lt;p&gt;Run these 2 commands to make sure all .sh files are executable:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

chmod u+x *.sh
chmod u+x scripts/*.sh


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

&lt;/div&gt;

&lt;p&gt;Let's go back to the home directory and create another new folder - &lt;strong&gt;&lt;em&gt;ibc&lt;/em&gt;&lt;/strong&gt; where we will store the configuration file&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

cd ../..
mkdir ibc


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

&lt;/div&gt;

&lt;p&gt;Copy configuration file from /opt/ibc to the newly created folder&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

cp opt/ibc/config.ini ~/ibc/


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

&lt;/div&gt;

&lt;p&gt;Now we have to make a few changes to that config file (use the editor of your choice):&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

vi ibc/config.ini


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

&lt;/div&gt;

&lt;p&gt;Here we have to change the username &amp;amp; password fields&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%2Fnszwpdte2cjs4979mi4q.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%2Fnszwpdte2cjs4979mi4q.png" alt="Modify config file"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Save the changes and let's go back to the /opt/ibc/ folder&lt;br&gt;
Here open the twsstart.sh file using an editor of your choice&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

cd opt/ibc/
vi twsstart.sh


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

&lt;/div&gt;

&lt;p&gt;For the trading mode, specify the one you need - either live or paper (I will use live)&lt;/p&gt;

&lt;p&gt;And also, we need to change the TWS version.&lt;br&gt;
We can  find the TWS major version number by running the TWS app, clicking the &lt;em&gt;&lt;strong&gt;Help&lt;/strong&gt;&lt;/em&gt; button on the top navigation menu, then &lt;em&gt;&lt;strong&gt;About Trader Workstation&lt;/strong&gt;&lt;/em&gt; option&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%2Fsvy7frijyuoo8ia0xr3m.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%2Fsvy7frijyuoo8ia0xr3m.png" alt="TWS major version"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the displayed information you'll see a TWS version&lt;/p&gt;

&lt;p&gt;Going back to twsstart.sh file, modify the TWS_MAJOR_VRSN property&lt;br&gt;
And for the IBC_PATH parameter add ~ before /opt/ibc&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%2Fjkxv25nu80u2luy509od.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%2Fjkxv25nu80u2luy509od.png" alt="Modify script file"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Save the changes and let's try to start IBC!&lt;br&gt;
Before doing this, make sure that the previously started TWS is no longer running&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

DISPLAY=:0 ~/opt/ibc/twsstart.sh


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

&lt;/div&gt;

&lt;p&gt;After running this command, you should see TWS been started in VNC Viewer&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%2F0ihbe77g9ruile0v73ue.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%2F0ihbe77g9ruile0v73ue.png" alt="IBC started"&gt;&lt;/a&gt;&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%2F07do29pm0j5qqcwtfnwf.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%2F07do29pm0j5qqcwtfnwf.png" alt="TWS started"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And if you want to make IBC run every hour to check whether TWS lost connection and requires re-login, we can use crontab and schedule a cronjob that will run this command (DISPLAY=:0 ~/opt/ibc/twsstart.sh) every hour&lt;/p&gt;

&lt;p&gt;Run this command:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

crontab -e


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

&lt;/div&gt;

&lt;p&gt;And add the following line to the end of the file (you can adjust the time according to your needs)&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

0 * * * * DISPLAY=:0 /home/ubuntu/opt/ibc/twsstart.sh


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

&lt;/div&gt;




&lt;p&gt;And that's it, guys.&lt;/p&gt;

&lt;p&gt;I hope that you have learned something new today!&lt;br&gt;
I would appreciate it if you could like this post or leave a comment below!&lt;/p&gt;

&lt;p&gt;Also, I want to mention that I used this &lt;a href="https://dimon.ca/how-to-setup-ibc-and-tws-on-headless-ubuntu-in-10-minutes/#h.udghm8581dwb" rel="noopener noreferrer"&gt;amazing article&lt;/a&gt; as a reference and my post is just a shorter and easier version of that article&lt;/p&gt;

&lt;p&gt;Also, feel free to follow me on &lt;a href="https://github.com/Kai4ik" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; and &lt;a href="https://medium.com/@kai4ik" rel="noopener noreferrer"&gt;Medium&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;Adios, mi amigos)&lt;/p&gt;

</description>
      <category>aws</category>
      <category>interactivebrokers</category>
      <category>ec2</category>
      <category>tws</category>
    </item>
    <item>
      <title>Migrating data from GCS to AWS S3 using AWS DataSync</title>
      <dc:creator>Kairat</dc:creator>
      <pubDate>Mon, 29 Aug 2022 06:59:00 +0000</pubDate>
      <link>https://dev.to/kairatorozobekov/migrating-data-from-gcs-to-aws-s3-using-aws-datasync-fnc</link>
      <guid>https://dev.to/kairatorozobekov/migrating-data-from-gcs-to-aws-s3-using-aws-datasync-fnc</guid>
      <description>&lt;p&gt;Let's say your company has been using the Google Cloud Platform for a very long time. &lt;/p&gt;

&lt;p&gt;And suddenly, one day, your company's Solutions Architect decided to switch to AWS cause Amazon offers more services (maybe it sounds like an unrealistic situation but let's just imagine)&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%2Fbws024429s1njokbeedp.jpg" 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%2Fbws024429s1njokbeedp.jpg" alt="Imagination maan"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Because of that, you've been given a task to migrate all the data from GCS (Google Cloud Storage) to AWS S3&lt;/p&gt;

&lt;p&gt;You started exploring different possible solutions and found something called AWS DataSync&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%2Foczukds993ik871m6dz6.jpeg" 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%2Foczukds993ik871m6dz6.jpeg" alt="What a heck is that?"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It is actually a good question.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;So, DataSync&lt;/strong&gt; is one of many AWS Services and it allows you to simplify, automate and secure the data transfer process between different storage systems&lt;/p&gt;

&lt;p&gt;Sounds good, right? &lt;br&gt;
&lt;strong&gt;But why would you use it and pay for it, when you can build your own solution for that purpose.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let's start with that building such solution will require a lot of time, and even if you will accomplish that, there is no guarantee that your solution will be cheap,  secure and easy to scale.&lt;/p&gt;

&lt;p&gt;Therefore, maybe it will be just easier and better not to reinvent the wheel and use DataSync that already has such features as data encryption, validation, monitoring, task scheduling, auditing, and scaling. Also, I think it is important to mention that DataSync is available at 24 AWS Regions!&lt;/p&gt;

&lt;p&gt;Hopefully, I convinced you to use DataSync.&lt;br&gt;
&lt;strong&gt;The next question you probably want to ask me is how it works.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And before I will answer this question, I want to introduce you to a few very important concepts that I will refer to in the rest of this article.&lt;/p&gt;




&lt;p&gt;One of them is &lt;strong&gt;DataSync Agent&lt;/strong&gt; which is a VM (virtual machine) owned by you and used to access your self-managed storage and read data from or write data to it (self-managed storage can refer to on-premises or in-cloud storage systems. Transfers with these kinds of systems require using a DataSync agent)&lt;/p&gt;

&lt;p&gt;It can be deployed on VMware, Hyper-V, KVM or as an AWS EC2 instance.&lt;/p&gt;

&lt;p&gt;AWS DataSync provides several types of agents for different storage environments. &lt;br&gt;
For example, you can use a VMware agent to transfer data from an on-premises file system. &lt;br&gt;
And if you're copying data from a cloud-based file share that isn't an AWS storage service, you must deploy your DataSync agent as an Amazon EC2 instance (which is our case)&lt;br&gt;
If you are copying data between AWS storage services, you do not need DataSync agent at all.&lt;/p&gt;




&lt;p&gt;The other important concept I need to mention is a &lt;strong&gt;location&lt;/strong&gt; which basically identifies where you're copying data from or to.&lt;br&gt;
Each DataSync transfer has a source and destination location.&lt;/p&gt;




&lt;p&gt;And last but not least is the &lt;strong&gt;task&lt;/strong&gt; which is simply a DataSync transfer that identifies a source and destination location along with details about how to copy data between those locations.&lt;br&gt;
&lt;strong&gt;Task execution&lt;/strong&gt; is just one individual run of a DataSync task.&lt;/p&gt;




&lt;p&gt;And now, when we are all familiar with the terminology, I am ready to answer the question - "how does DataSync work", and I'll do that by going through the process of migrating data from GCS to AWS S3.&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%2Frmdbgmxtv34q841ewpx1.jpg" 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%2Frmdbgmxtv34q841ewpx1.jpg" alt="Let's start"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;First, we need to &lt;strong&gt;launch an EC2 instance which will be used to deploy the DataSync agent&lt;/strong&gt; (we do this as we use an in-cloud storage system - GCS, otherwise we would use an on-premises hypervisor such as Hyper-V or VMware)&lt;/p&gt;

&lt;p&gt;As we know, the EC2 instance requires &lt;strong&gt;AMI&lt;/strong&gt; to be chosen.&lt;br&gt;
(for those, who do not know, AMI stands for an Amazon Machine Image and it is a template that contains the software configuration (operating system, application server, and applications) required to launch your instance. But we are not here to talk about AMI, therefore here is the &lt;a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/AMIs.html" rel="noopener noreferrer"&gt;link&lt;/a&gt; if you want to know more about it)&lt;/p&gt;

&lt;p&gt;The reason I mentioned AMIs is that we will launch our EC2 instance based on the image provided by DataSync. This image contains the DataSync VM image.&lt;/p&gt;

&lt;p&gt;Agent AMIs vary by region, thus you have to choose the right one for your region.&lt;/p&gt;

&lt;p&gt;In order to get the data about the latest DataSync AMI for a specified region, run this command (AWS CLI has to be &lt;a href="https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html" rel="noopener noreferrer"&gt;installed&lt;/a&gt; on your machine)&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

aws ssm get-parameter --name /aws/service/datasync/ami --region yourRegion



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

&lt;/div&gt;

&lt;p&gt;The result of running this command will be an object containing information about the DataSync image - &lt;strong&gt;AMI ID (value)&lt;/strong&gt;, version, ARM, etc.&lt;/p&gt;

&lt;p&gt;Now, use the following link and replace 2 parameters - source-region and ami-id with your source file system region and AMI ID that we got by running the previous command&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

https://console.aws.amazon.com/ec2/v2/home?region=source-region#LaunchInstanceWizard:ami=ami-id



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

&lt;/div&gt;

&lt;p&gt;By going through this link, "Launch an instance" page will be opened&lt;/p&gt;




&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Enter the name of your EC2 instance.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Do not modify the &lt;strong&gt;"Application and OS image"&lt;/strong&gt; section as AMI has already been chosen&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;For the &lt;strong&gt;"Instance type"&lt;/strong&gt;, you have to remember that the instance size must be at least 2xlarge (recommended ones are m5.2xlarge (for tasks to transfer up to 20 million files) or m5.4xlarge (for tasks to transfer more than 20 million files)&lt;br&gt;
I will choose t2.2xlarge (32 GiB, 8vCPUs, 64-bit)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create a new &lt;strong&gt;key pair&lt;/strong&gt; and save it&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In the &lt;strong&gt;"Network settings"&lt;/strong&gt; section, you can edit your VPC and subnet.&lt;br&gt;
Enable &lt;strong&gt;Auto-assign Public IP&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;For the &lt;strong&gt;"Security group"&lt;/strong&gt; section, make sure that the selected security group allows inbound access to HTTP port 80 from the web browser that you plan to use to activate the agent&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In &lt;strong&gt;"Configure storage"&lt;/strong&gt; section, ensure that there is at least &lt;strong&gt;80&lt;/strong&gt; GB of disk space&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click the &lt;strong&gt;"Launch Instance"&lt;/strong&gt; button&lt;/p&gt;&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%2Fsspsv17w6ve5gvsjhffy.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%2Fsspsv17w6ve5gvsjhffy.png" alt="EC2 instance configuration"&gt;&lt;/a&gt;&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%2F2h2veydgkn814u63muew.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%2F2h2veydgkn814u63muew.png" alt="EC2 instance configuration 2"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The initial state of the EC2 instance will be &lt;strong&gt;pending&lt;/strong&gt;.&lt;br&gt;
You have to wait until its status changes to &lt;strong&gt;running&lt;/strong&gt;.&lt;br&gt;
When your instance is running, it's assigned a public DNS and &lt;strong&gt;IP address&lt;/strong&gt; that we will need later&lt;/p&gt;




&lt;p&gt;Now, as we deployed our DataSync agent, we need to activate it cause activation securely associates the agent that you deployed with your AWS account&lt;/p&gt;

&lt;p&gt;In order to do that, go to the AWS DataSync console, choose Agents on the left-side bar and then click the "Create agent" button.&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%2F6vytoo0wu38e266ad1yl.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%2F6vytoo0wu38e266ad1yl.png" alt="Create agent"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the "Create agent" page the first section you see is &lt;strong&gt;"Deploy agent"&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;As we deployed our DataSync agent as an EC2 instance, choose &lt;strong&gt;"Amazon EC2"&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The second section is the &lt;strong&gt;"Service endpoint"&lt;/strong&gt; that the DataSync agent uses to communicate with AWS.&lt;br&gt;
You have 3 options - public, VPC or FIPS.&lt;br&gt;
Also, I need to mention that after you choose a service endpoint and activate the agent, you won't be able to change the endpoint&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For simplicity, I am going to choose the &lt;strong&gt;public service endpoint&lt;/strong&gt;.&lt;br&gt;
It means that all communication from your DataSync agent to AWS occurs over the public internet&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
The last section of this page is called &lt;strong&gt;"Activation key"&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To obtain the agent's activation key, you have 2 options - do it &lt;strong&gt;automatically or manually&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;We will do this automatically, and thus we need to specify the agent's address (to get the IP address, go to your EC2 running instances, choose the one we deployed earlier in this tutorial, and copy its public IP address)&lt;/p&gt;

&lt;p&gt;Put copied IP to the "agent address" field.&lt;/p&gt;

&lt;p&gt;After that, you can press the &lt;strong&gt;"Get key"&lt;/strong&gt; button.&lt;br&gt;
Your browser will connect to the IP address and get a unique activation key from your agent&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%2F9jy6sicelcff3zs5k4jq.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%2F9jy6sicelcff3zs5k4jq.png" alt="Create agent page"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If activation succeeds, you will see a &lt;strong&gt;"Successfully retrieved activation key from agent"&lt;/strong&gt; message with the activation key itself.&lt;/p&gt;

&lt;p&gt;Then just type the agent name (not required) and press the &lt;strong&gt;"Create agent"&lt;/strong&gt; button&lt;/p&gt;

&lt;p&gt;The DataSync agent was successfully activated!&lt;/p&gt;




&lt;p&gt;We are almost done, guys! &lt;br&gt;
Just a few small steps left - creating source &amp;amp; destination locations and creating the task itself.&lt;/p&gt;

&lt;p&gt;Let's start with the &lt;strong&gt;configuration of a source location&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;On the DataSync console, go to &lt;strong&gt;"Locations"&lt;/strong&gt;, and click the &lt;strong&gt;"Create location"&lt;/strong&gt; button&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
For the location type, choose &lt;strong&gt;"Object storage"&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A lot of new fields appeared.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Choose our previously activated agent in the &lt;strong&gt;"Agents"&lt;/strong&gt; field&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;For &lt;strong&gt;"Server"&lt;/strong&gt;, type “storage.googleapis.com”&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;For &lt;strong&gt;"Bucket name"&lt;/strong&gt;, type the name of the GCS source bucket&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want, you can specify the folder that will be used for the data transfer (I will leave it empty)&lt;/p&gt;

&lt;p&gt;In the &lt;strong&gt;"Authentication"&lt;/strong&gt; section, you have to specify HMAC credentials that include access and secret keys.&lt;/p&gt;

&lt;p&gt;DataSync agent uses an HMAC credential to authenticate to GCP and manage objects in the Cloud Storage bucket&lt;/p&gt;

&lt;p&gt;We are not going to talk about HMAC keys but here is the &lt;a href="https://cloud.google.com/storage/docs/authentication/managing-hmackeys" rel="noopener noreferrer"&gt;link&lt;/a&gt; to how you can create one&lt;/p&gt;

&lt;p&gt;Enter your access &amp;amp; secret keys, and you are ready to press the &lt;strong&gt;"Create location"&lt;/strong&gt; button&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%2Fva8cxlkgd87xoq2d776l.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%2Fva8cxlkgd87xoq2d776l.png" alt="Create source location"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Now, let's go through the same process and create a &lt;strong&gt;destination location&lt;/strong&gt; which is our AWS S3 Bucket.&lt;/p&gt;

&lt;p&gt;Click the &lt;strong&gt;"Create location"&lt;/strong&gt; button on the &lt;strong&gt;"Locations"&lt;/strong&gt; page.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;For the &lt;strong&gt;"Location type"&lt;/strong&gt; field, choose &lt;strong&gt;"Amazon S3"&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;For the &lt;strong&gt;"S3 bucket"&lt;/strong&gt;, select the destination AWS S3 bucket&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;For the &lt;strong&gt;"Storage class"&lt;/strong&gt; you can leave the default value - Standard.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In the &lt;strong&gt;"IAM role"&lt;/strong&gt;, choose a role with appropriate permissions&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;(DataSync needs to access the AWS S3 bucket in order to transfer the data to the destination bucket. This requires DataSync to assume an IAM role with appropriate permission and a trust relationship - here is the &lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-user.html" rel="noopener noreferrer"&gt;link&lt;/a&gt;)&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%2Fqoqt85nnq5v2uzkhr09v.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%2Fqoqt85nnq5v2uzkhr09v.png" alt="Create destination location"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;And finally, we just need to &lt;strong&gt;create the AWS DataSync task&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;On the DataSync console, go to &lt;strong&gt;"Tasks"&lt;/strong&gt;, and click the &lt;strong&gt;"Create task"&lt;/strong&gt; button&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
In step 1 - &lt;strong&gt;"Configure source location"&lt;/strong&gt;, select "Choose an existing location"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For &lt;strong&gt;"Existing locations"&lt;/strong&gt;, choose our previously created GCS location&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%2F7pqanboknxqxo7tdast3.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%2F7pqanboknxqxo7tdast3.png" alt="Configure source location"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
In the second step - &lt;strong&gt;"Configure destination location"&lt;/strong&gt; do the same as in the previous step, but for &lt;strong&gt;"Existing locations"&lt;/strong&gt; choose the S3 location&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%2Faq34a4kyn68b9gaawaro.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%2Faq34a4kyn68b9gaawaro.png" alt="Configure destination location"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
In the third step - &lt;strong&gt;"Configure settings"&lt;/strong&gt;, enter the &lt;strong&gt;task name&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the &lt;strong&gt;"Task execution configuration"&lt;/strong&gt; section, for "&lt;strong&gt;Verify data"&lt;/strong&gt; choose the needed option (I will leave the default one)&lt;/p&gt;

&lt;p&gt;In the &lt;strong&gt;"Data transfer configuration"&lt;/strong&gt; section, leave everything as default values (but you can adjust it if needed)&lt;/p&gt;

&lt;p&gt;In the &lt;strong&gt;"Schedule"&lt;/strong&gt; section, choose a schedule you want this task to be executed on&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%2Fc12kj0ze0zfn5s26sfd5.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%2Fc12kj0ze0zfn5s26sfd5.png" alt="Task configuration"&gt;&lt;/a&gt;&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%2Fl1aaxq8xxpzwd74krbgg.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%2Fl1aaxq8xxpzwd74krbgg.png" alt="Task configuration - schedule"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Go to step 4 - &lt;strong&gt;"Review"&lt;/strong&gt;, and click the &lt;strong&gt;"Create task"&lt;/strong&gt; button (wait for Task status to be Available)&lt;/p&gt;




&lt;p&gt;And that's it, guys.&lt;/p&gt;

&lt;p&gt;It was really long article but I hope you made it to the end and were able to successfully run your first AWS DataSync task!&lt;/p&gt;

&lt;p&gt;I would appreciate it if you could like this post or leave a comment below!&lt;/p&gt;

&lt;p&gt;Also, feel free to follow me on &lt;a href="https://github.com/Kai4ik" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; and &lt;a href="https://medium.com/@kai4ik" rel="noopener noreferrer"&gt;Medium&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;Adios, mi amigos)&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%2F8yjtzz78cwzmfdbvw2mb.gif" 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%2F8yjtzz78cwzmfdbvw2mb.gif" alt="Adios, mi amigos"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cloud</category>
      <category>googlecloud</category>
      <category>programming</category>
    </item>
    <item>
      <title>React Fragments in 5 minutes (with examples)</title>
      <dc:creator>Kairat</dc:creator>
      <pubDate>Sun, 08 May 2022 04:16:02 +0000</pubDate>
      <link>https://dev.to/kairatorozobekov/react-fragments-in-5-minutes-with-examples-3p2a</link>
      <guid>https://dev.to/kairatorozobekov/react-fragments-in-5-minutes-with-examples-3p2a</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%2F7dsdinied57h4vzj5dut.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%2F7dsdinied57h4vzj5dut.png" alt="multiple JSX elements returned error" width="800" height="397"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And you think to yourself: "Oh God, what have I done wrong again?"&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%2Feftyvqiz8j09dw4lxbew.gif" 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%2Feftyvqiz8j09dw4lxbew.gif" alt="oh god, not again meme" width="220" height="293"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But luckily for you (and for all of us - React developers), this time the problem is super simple.&lt;/p&gt;

&lt;p&gt;Let's take a look at the code that caused the problem.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React from "react";

const Fragment = () =&amp;gt; {
  return 
  &amp;lt;div&amp;gt;Fragments&amp;lt;/div&amp;gt;
  &amp;lt;p&amp;gt;in React&amp;lt;/p&amp;gt;
  ;
};

export default Fragment;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, we are trying to return two JSX elements from our Fragment component.&lt;/p&gt;

&lt;p&gt;And, as you probably understand, this is the root of our problem.&lt;/p&gt;

&lt;p&gt;So, why?&lt;/p&gt;

&lt;p&gt;Isn't it a common situation when you need to return multiple elements from a component?&lt;/p&gt;

&lt;p&gt;And you are right  - this is a common pattern in React.&lt;/p&gt;

&lt;p&gt;But you have to remember:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Component must return only one parent element and not more, not less.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  What can we do about this?
&lt;/h2&gt;

&lt;p&gt;So, one solution is to wrap it in a "div" element.&lt;/p&gt;

&lt;p&gt;Let's take a look at whether it works or not!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React from "react";

const Fragment = () =&amp;gt; {
  return (
    &amp;lt;div className="wrapper"&amp;gt;
      &amp;lt;p&amp;gt;Fragments&amp;lt;/p&amp;gt;
      &amp;lt;p&amp;gt;in React&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};

export default Fragment;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Forhoeb5z7zl7xrfes5je.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%2Forhoeb5z7zl7xrfes5je.png" alt="div wrapper as a solution" width="800" height="342"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Hooray! It worked!&lt;/p&gt;

&lt;p&gt;But is this the best solution?&lt;/p&gt;

&lt;p&gt;Nah!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;There are several problems that come with it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;First of all, we add an extra node to our DOM. It takes extra space in memory.&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%2F62zce767i6pkgj7527l0.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%2F62zce767i6pkgj7527l0.png" alt="extra node in the DOM example" width="800" height="325"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Secondly, it's just irrelevant. We need it only for our JSX to work. Kind of ridiculous, right?&lt;/p&gt;

&lt;p&gt;And in addition to that, sometimes this div wrapper can even break our layout and lead to invalid HTML to be rendered!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;You probably think, how this innocent div can break your HTML?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Let me show you a quick example that will demonstrate that!&lt;/p&gt;

&lt;p&gt;Let's imagine that we wanna display a table.&lt;br&gt;
So we define &lt;strong&gt;"table", "tbody", "tr"&lt;/strong&gt; elements.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import Fragment from "./fragment";

function App() {
  return (
    &amp;lt;div className="App"&amp;gt;
      &amp;lt;table&amp;gt;
        &amp;lt;tbody&amp;gt;
          &amp;lt;tr&amp;gt;
            &amp;lt;Fragment /&amp;gt;
          &amp;lt;/tr&amp;gt;
        &amp;lt;/tbody&amp;gt;
      &amp;lt;/table&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default App
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And let's say, we want a separate component for our data cells (&lt;strong&gt;"td"&lt;/strong&gt; elements).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React from "react";

const Fragment = () =&amp;gt; {
  return (
    &amp;lt;div className="wrapper"&amp;gt;
      &amp;lt;td&amp;gt;Team &amp;lt;/td&amp;gt;
      &amp;lt;td&amp;gt;Points&amp;lt;/td&amp;gt;
      &amp;lt;td&amp;gt; Wins&amp;lt;/td&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};

export default Fragment;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We already know that we cannot return multiple values from a component.&lt;/p&gt;

&lt;p&gt;Thus we will wrap our &lt;strong&gt;"td"&lt;/strong&gt; elements in a div with class "wrapper" (no styles applied to this class, just for demonstration purposes)&lt;/p&gt;

&lt;p&gt;Now let's check if it works or not!&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%2Fw28apx4cvx5l7vvtyrqc.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%2Fw28apx4cvx5l7vvtyrqc.png" alt="div wrapper cause problems with tables example" width="800" height="409"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Seems like it works but damn... &lt;br&gt;
Look at the console ...&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%2F0oiy9d12k156ri2snuk2.jpg" 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%2F0oiy9d12k156ri2snuk2.jpg" alt="div wrapper cause problems with tables example" width="750" height="920"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We cannot place &lt;strong&gt;"td"&lt;/strong&gt; inside of a &lt;strong&gt;div&lt;/strong&gt;. &lt;br&gt;
And &lt;strong&gt;"tr"&lt;/strong&gt; cannot contain the &lt;strong&gt;div&lt;/strong&gt; element.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q.E.D.&lt;/strong&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  So, what should we do instead?
&lt;/h2&gt;

&lt;p&gt;You guessed it right - we can use &lt;strong&gt;Fragments&lt;/strong&gt;!&lt;/p&gt;

&lt;p&gt;A fragment is just a syntax that allows you to group a list of children (as our div did) &lt;strong&gt;but&lt;/strong&gt;...&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Super important, Fragment does not add an extra node to the DOM.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Okay, that sounds like a win-win, right?&lt;/p&gt;
&lt;h2&gt;
  
  
  How to use it?
&lt;/h2&gt;

&lt;p&gt;Pretty easy !&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React from "react";

const Fragment = () =&amp;gt; {
  return (
    &amp;lt;React.Fragment&amp;gt;
      &amp;lt;p&amp;gt;Fragments&amp;lt;/p&amp;gt;
      &amp;lt;p&amp;gt;in React&amp;lt;/p&amp;gt;
    &amp;lt;/React.Fragment&amp;gt;
  );
};

export default Fragment;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All we have to do is use &lt;strong&gt;React.Fragment&lt;/strong&gt; instead of div.&lt;/p&gt;

&lt;p&gt;Also, I need to mention that there is another form of React Fragment - short syntax that looks like an &lt;strong&gt;empty tag.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React from "react";

const Fragment = () =&amp;gt; {
  return (
    &amp;lt;&amp;gt;
      &amp;lt;p&amp;gt;Fragments&amp;lt;/p&amp;gt;
      &amp;lt;p&amp;gt;in React&amp;lt;/p&amp;gt;
    &amp;lt;/&amp;gt;
  );
};

export default Fragment;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both of these code examples will result in such DOM:&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%2Fsv53uwrjbx32v56a53u0.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%2Fsv53uwrjbx32v56a53u0.png" alt="React Fragment as a solution example" width="800" height="411"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see no additional JSX elements have been added to the DOM!&lt;/p&gt;

&lt;h2&gt;
  
  
  Is there any difference between React.Fragment and the short syntax?
&lt;/h2&gt;

&lt;p&gt;Actually, there is one small difference&lt;/p&gt;

&lt;p&gt;Look at this code snippet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React from "react";

const Fragment = () =&amp;gt; {
  const arrayOfNumbers = [1, 2, 3, 4, 5];
  return arrayOfNumbers.map((number, index) =&amp;gt; (
    &amp;lt;&amp;gt;
      &amp;lt;p&amp;gt;{number}&amp;lt;/p&amp;gt;
      &amp;lt;p&amp;gt;{index}&amp;lt;/p&amp;gt;
    &amp;lt;/&amp;gt;
  ));
};

export default Fragment;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We iterate through the array and for each element return 2 paragraphs (value itself and its index).&lt;/p&gt;

&lt;p&gt;We put these paragraphs in an empty tag (short syntax of React.Fragment)&lt;/p&gt;

&lt;p&gt;Everything works fine but ...&lt;br&gt;
We got this error in the console ...&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%2F7fgz9i24yje6zpm0qpe4.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%2F7fgz9i24yje6zpm0qpe4.png" alt="Problem with React keys example" width="800" height="410"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you read my previous post about React keys, you know what it's all about and how to deal with it.&lt;br&gt;
If not, &lt;a href="https://dev.to/kairatorozobekov/everything-you-need-to-know-about-keys-in-react-with-examples-4ah4"&gt;check it out&lt;/a&gt;!&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%2Ff6f50o3b8t5vm5i3c00g.jpg" 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%2Ff6f50o3b8t5vm5i3c00g.jpg" alt="I got you bro meme" width="600" height="395"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So, as you understand, we have to add the &lt;strong&gt;key&lt;/strong&gt; to our parent element.&lt;/p&gt;

&lt;p&gt;And here is the problem.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;We cannot do that with the short syntax of React.Fragment.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Instead, we have to use React.Fragment and give it a &lt;strong&gt;key&lt;/strong&gt; attribute.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React from "react";

const Fragment = () =&amp;gt; {
  const arrayOfNumbers = [1, 2, 3, 4, 5];
  return arrayOfNumbers.map((number, index) =&amp;gt; (
    &amp;lt;React.Fragment key={index}&amp;gt;
      &amp;lt;p&amp;gt;{number}&amp;lt;/p&amp;gt;
      &amp;lt;p&amp;gt;{index}&amp;lt;/p&amp;gt;
    &amp;lt;/React.Fragment&amp;gt;
  ));
};

export default Fragment;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fa8ybe3v7h0ikxagafydw.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%2Fa8ybe3v7h0ikxagafydw.png" alt="Key problem resolved example" width="800" height="411"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Brief conclusion:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Fragments can be used whenever you need to return multiple JSX elements from the component.&lt;br&gt;
You should be using them instead of wrapping everything in a &lt;strong&gt;div&lt;/strong&gt; because React.Fragment does not add an extra node to the DOM. There are 2 forms of it: full and short syntax.&lt;br&gt;
The only difference between them is that you cannot use key attribute (if you need one) with short syntax.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;And that's it, guys.&lt;/p&gt;

&lt;p&gt;I hope that you have learned something new today!&lt;br&gt;
I would appreciate it if you could like this post or leave a comment below!&lt;/p&gt;

&lt;p&gt;Also, feel free to follow me on &lt;a href="https://github.com/Kai4ik"&gt;GitHub&lt;/a&gt; and &lt;a href="https://medium.com/@kai4ik"&gt;Medium&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;Adios, mi amigos)&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%2Flqpgj2rwois22i87um5m.jpg" 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%2Flqpgj2rwois22i87um5m.jpg" alt="Baby Yoda saying Goodbye" width="600" height="388"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>react</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>Everything you need to know about keys in React (with examples)</title>
      <dc:creator>Kairat</dc:creator>
      <pubDate>Sat, 19 Mar 2022 22:50:36 +0000</pubDate>
      <link>https://dev.to/kairatorozobekov/everything-you-need-to-know-about-keys-in-react-with-examples-4ah4</link>
      <guid>https://dev.to/kairatorozobekov/everything-you-need-to-know-about-keys-in-react-with-examples-4ah4</guid>
      <description>&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%2Fichlsuodzsu9yw6vn1ba.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%2Fichlsuodzsu9yw6vn1ba.png" alt="Dev Tools Console - warning about unique keys"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I can bet my life that you've seen this warning at least once while building React applications.&lt;/p&gt;

&lt;p&gt;Those who say "no" ...&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%2Fskaocdbjwevresbrn3jv.jpg" 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%2Fskaocdbjwevresbrn3jv.jpg" alt="I know you are lying meme"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So, back to the problem...&lt;/p&gt;

&lt;p&gt;Maybe you thought:&lt;br&gt;
&lt;strong&gt;What on earth is a "key"?&lt;br&gt;
Why it should be unique?&lt;br&gt;
And why should I even care, it's just a warning?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So many questions and I hope I will be able to answer all of them.&lt;/p&gt;

&lt;p&gt;Let's start with the basics ...&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a key?
&lt;/h2&gt;

&lt;p&gt;According to the React documentation,&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;key - is just a special &lt;strong&gt;string&lt;/strong&gt; attribute that must be included when creating &lt;strong&gt;arrays&lt;/strong&gt; of elements.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;What did we get from this definition?&lt;/p&gt;

&lt;p&gt;Weell, first, we now know that key is of type string and&lt;br&gt;
secondly, we should use it when we work with arrays!&lt;/p&gt;




&lt;p&gt;How to use them?&lt;/p&gt;

&lt;p&gt;Super simple!&lt;br&gt;
Let's take a look at this code snippet:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

const [soccerTeams, setSoccerTeams] = useState([
  "Chelsea",
  "Real Madrid",
  "Liverpool",
  "Juventus",
]);

return (
  &amp;lt;div className="App"&amp;gt;
    {soccerTeams.map((team) =&amp;gt; (
      &amp;lt;p&amp;gt; {team}&amp;lt;/p&amp;gt;
    ))}
  &amp;lt;/div&amp;gt;
);


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

&lt;/div&gt;

&lt;p&gt;As you can see we just loop through the array and display the content of each element.&lt;/p&gt;

&lt;p&gt;And this code will produce the warning you've seen in the beginning (that says "each child should have a unique key and blah blah blah")&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;So, the obvious question is how to get rid of this red little dude with the help of keys, right?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Actually, it's not that hard at all&lt;br&gt;
All you have to do is assign the &lt;em&gt;"key"&lt;/em&gt; attribute to array elements that are inside the map() and give it a value of type string.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Probably, your next question could be: what should we provide as a "value" to it?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Hmm, let's think about it ...&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%2F8tl52o7n49xbwoijocoy.gif" 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%2F8tl52o7n49xbwoijocoy.gif" alt="Think about it meme"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As we all know, each element in the array has its own index and this index is always unique.&lt;br&gt;
So why not use it?&lt;/p&gt;

&lt;p&gt;Okay, let's try ...&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

const [soccerTeams, setSoccerTeams] = useState([
  "Chelsea",
  "Real Madrid",
  "Liverpool",
  "Juventus",
]);

return (
  &amp;lt;div className="App"&amp;gt;
    {soccerTeams.map((team, index) =&amp;gt; (
      &amp;lt;p key={index}&amp;gt; {team}&amp;lt;/p&amp;gt;
    ))}
  &amp;lt;/div&amp;gt;
);


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

&lt;/div&gt;

&lt;p&gt;We just added the &lt;em&gt;"key"&lt;/em&gt; attribute with the value of index to each array element. &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%2F3fumswr44dgq1xpvokmz.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%2F3fumswr44dgq1xpvokmz.png" alt="key added - no warning"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see, it worked!&lt;br&gt;
There is no more this freaking warning!&lt;/p&gt;




&lt;p&gt;But perhaps you still ask yourself: &lt;/p&gt;

&lt;h2&gt;
  
  
  Why did we get this warning in the first place and why does React care so much about it?
&lt;/h2&gt;

&lt;p&gt;Well, have you ever heard anything about reconciliation in React?&lt;br&gt;
I guess, it sounds like something familiar, right?&lt;/p&gt;

&lt;p&gt;This post is not about reconciliation and thus we won't spend much time on it but briefly, it is a process that helps React to decide whether Virtual DOM should be updated or not (when component's state changes).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do keys relate to all this reconciliation stuff?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Basically, when the component re-renders, React compares new keys against the old set of keys and identifies which items have been modified, added, or deleted.&lt;br&gt;
And based on that, updates Virtual DOM.&lt;/p&gt;




&lt;p&gt;And that is actually all you have to know about keys in React!&lt;br&gt;
Yeah, dead simple ...&lt;br&gt;
Don't forget to follow me on GitHub and Medium ...&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%2Fyqnwtga45t204p3s8qp2.gif" 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%2Fyqnwtga45t204p3s8qp2.gif" alt="just kidding meme"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ookay, okay guys)&lt;br&gt;
Of course, it was just a joke!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Remember, it is React and it just can't be that simple and easy.&lt;br&gt;
There is always a catch!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let's take a look at this code.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

const [soccerTeams, setSoccerTeams] = useState([
  "Chelsea",
  "Real Madrid",
  "Liverpool",
  "Juventus",
]);

const deleteItem = (index) =&amp;gt; {
   const tempArray = [...soccerTeams];
   tempArray.splice(index, 1);
   setSoccerTeams(tempArray);
 };

return (
  &amp;lt;div className="App"&amp;gt;
    {soccerTeams.map((team, index) =&amp;gt; (
      &amp;lt;div key={index}&amp;gt;
        &amp;lt;p&amp;gt; {team}&amp;lt;/p&amp;gt;
        &amp;lt;button onClick={() =&amp;gt; deleteItem(index)}&amp;gt;
          Delete from array
        &amp;lt;/button&amp;gt;
      &amp;lt;/div&amp;gt;   
    ))}
  &amp;lt;/div&amp;gt;
);


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

&lt;/div&gt;

&lt;p&gt;Everything is almost the same as the previous one - we use &lt;em&gt;"index"&lt;/em&gt; as &lt;em&gt;"key"&lt;/em&gt;, no more warning.&lt;/p&gt;

&lt;p&gt;But we added a button so we can delete the particular items from the array.&lt;/p&gt;

&lt;h2&gt;
  
  
  So, what gonna happen when we delete the item from an array?
&lt;/h2&gt;

&lt;p&gt;Logically, it should just erase this particular node from DOM and that's it, right?&lt;/p&gt;

&lt;p&gt;Other items have already been rendered, and why update them &lt;/p&gt;

&lt;p&gt;Good logic, but let's see what is really going to happen.&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%2Frhaxnfgiakk858wms4ly.gif" 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%2Frhaxnfgiakk858wms4ly.gif" alt="Delete item - demonstration"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Can you see those purple flashes in dev tools?&lt;/p&gt;

&lt;p&gt;They indicate that something has been updated inside the DOM.&lt;/p&gt;

&lt;p&gt;And as you can see with your own eyes, those elements that have already been in DOM were updated too.&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%2Fp17t9ctd8pbkkqsm3c1p.gif" 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%2Fp17t9ctd8pbkkqsm3c1p.gif" alt="wht meme"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Why?&lt;br&gt;
Let's go through this process very carefully.&lt;/p&gt;

&lt;p&gt;We have an array that consists of 4 elements.&lt;br&gt;
The first element has &lt;em&gt;index 0&lt;/em&gt;, the second one - &lt;em&gt;index 1&lt;/em&gt;, and so on.&lt;/p&gt;

&lt;p&gt;Then we delete the first element.&lt;br&gt;
What happens?&lt;/p&gt;

&lt;p&gt;Our second element that had &lt;em&gt;index 1&lt;/em&gt;, now has &lt;em&gt;index 0.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;And as you already understand, the key is also updated because we assigned it to be equal to the index (that was updated) of our element.&lt;/p&gt;

&lt;p&gt;The same nonsense occurs when we add a new element to the array.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Also, I want to bring to your attention the fact, that when we add a new element to the end of the array or delete an item from the end, nothing is gonna happen and everything will work totally fine (DOM won't update other elements)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But that's not always the case.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Therefore, keys always should be stable.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How to make keys constant?
&lt;/h2&gt;

&lt;p&gt;Very good question.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;One of the best ways is the usage of ID as a key's value.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Most of the time when we fetch data from some sort of database, items we fetch have their own unique IDs.&lt;br&gt;
And these IDs can be used as keys.&lt;/p&gt;

&lt;p&gt;Let me show a quick example of how it can be done&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

const [soccerTeams, setSoccerTeams] = useState([
    { team: "Chelsea", id: "667" },
    { team: "Liverpool", id: "545" },
    { team: "Juventus", id: "1393" },
    { team: "Real Madrid", id: "432" },
  ]);

  const deleteItem = (index) =&amp;gt; {
    const tempArray = [...soccerTeams];
    tempArray.splice(index, 1);
    setSoccerTeams(tempArray);
  };

  return (
    &amp;lt;div className="App"&amp;gt;
      {soccerTeams.map((element, index) =&amp;gt; (
        &amp;lt;div key={element.id}&amp;gt;
          &amp;lt;p&amp;gt; {element.team} &amp;lt;/p&amp;gt;
          &amp;lt;button onClick={() =&amp;gt; deleteItem(index)}&amp;gt;
            Delete from array
          &amp;lt;/button&amp;gt;
        &amp;lt;/div&amp;gt;
      ))}
    &amp;lt;/div&amp;gt;
  );


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

&lt;/div&gt;

&lt;p&gt;Now, elements in our fictional database have their own IDs.&lt;/p&gt;

&lt;p&gt;When we loop through the array we use this &lt;em&gt;"id"&lt;/em&gt; property as a value for the &lt;em&gt;"key"&lt;/em&gt; attribute.&lt;/p&gt;

&lt;p&gt;But the main question is what's gonna happen when we will delete the element from an array.&lt;/p&gt;

&lt;p&gt;Intrigued?&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%2Fmtlrlwku2x5knnjaieos.gif" 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%2Fmtlrlwku2x5knnjaieos.gif" alt="ID as a key - demonstration"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Yo ho ho ...&lt;br&gt;
No more purple flashes !&lt;br&gt;
Only parent &lt;em&gt;div&lt;/em&gt; updated as one element was removed in it, but other list items were not re-rendered because we use constant ID as a key.&lt;/p&gt;

&lt;p&gt;So even we delete an item, the keys for other elements stay the same as we don't use indexes as their values.&lt;/p&gt;

&lt;p&gt;Amazing! Problem solved!&lt;/p&gt;




&lt;h2&gt;
  
  
  Buut ... what if your data does not have IDs?
&lt;/h2&gt;

&lt;p&gt;Good question, because not all data has IDs.&lt;/p&gt;

&lt;p&gt;But what if can generate one?&lt;/p&gt;

&lt;p&gt;For example, let's try to use a popular ID generation tool called UUID.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

const [soccerTeams, setSoccerTeams] = useState([
    { team: "Chelsea" },
    { team: "Liverpool" },
    { team: "Juventus" },
    { team: "Real Madrid" },
  ]);

  const deleteItem = (index) =&amp;gt; {
    const tempArray = [...soccerTeams];
    tempArray.splice(index, 1);
    setSoccerTeams(tempArray);
  };

  return (
    &amp;lt;div className="App"&amp;gt;
      {soccerTeams.map((element, index) =&amp;gt; (
        &amp;lt;div key={uuidv4()}&amp;gt;
          &amp;lt;p&amp;gt; {element.team} &amp;lt;/p&amp;gt;
          &amp;lt;button onClick={() =&amp;gt; deleteItem(index)}&amp;gt;
            Delete from array
          &amp;lt;/button&amp;gt;
        &amp;lt;/div&amp;gt;
      ))}
    &amp;lt;/div&amp;gt;
  );


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

&lt;/div&gt;

&lt;p&gt;In this example, we generate value for the key using the UUID() function.&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%2Fukbq8mkfrzwauftacdwm.gif" 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%2Fukbq8mkfrzwauftacdwm.gif" alt="UUID generation for key - demonstration"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Oh no ...&lt;br&gt;
We are back to our initial problem.&lt;br&gt;
DOM is updated every time we delete the item from an array. &lt;/p&gt;

&lt;p&gt;I guess you already understand why.&lt;/p&gt;

&lt;p&gt;Every time a component re-renders, a new ID is generated and assigned to the key.&lt;/p&gt;

&lt;p&gt;So, React thinks it is a brand new element, while it's not.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But there is another way we can use UUID.&lt;/strong&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

const [soccerTeams, setSoccerTeams] = useState([
    { team: "Chelsea", id: uuidv4() },
    { team: "Liverpool", id: uuidv4() },
    { team: "Juventus", id: uuidv4() },
    { team: "Real Madrid", id: uuidv4() },
  ]);

  const deleteItem = (index) =&amp;gt; {
    const tempArray = [...soccerTeams];
    tempArray.splice(index, 1);
    setSoccerTeams(tempArray);
  };

  return (
    &amp;lt;div className="App"&amp;gt;
      {soccerTeams.map((element, index) =&amp;gt; (
        &amp;lt;div key={element.id}&amp;gt;
          &amp;lt;p&amp;gt; {element.team} &amp;lt;/p&amp;gt;
          &amp;lt;p&amp;gt; {element.id} &amp;lt;/p&amp;gt;
          &amp;lt;button onClick={() =&amp;gt; deleteItem(index)}&amp;gt;
            Delete from array
          &amp;lt;/button&amp;gt;
        &amp;lt;/div&amp;gt;
      ))}
    &amp;lt;/div&amp;gt;
  );


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

&lt;/div&gt;

&lt;p&gt;Here we use the UUID() function to generate ID for the &lt;em&gt;id&lt;/em&gt; property.&lt;/p&gt;

&lt;p&gt;This way everything works fine!&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%2Fq3otu6zquq9bqz6z0iq9.gif" 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%2Fq3otu6zquq9bqz6z0iq9.gif" alt="UUID as an ID property"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Is there another way?
&lt;/h2&gt;

&lt;p&gt;Actually, yeah.&lt;br&gt;
We can use some hashing tool to generate a hash from the object and use it as a key's value.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

  const [soccerTeams, setSoccerTeams] = useState([
    { team: "Chelsea" },
    { team: "Liverpool" },
    { team: "Juventus" },
    { team: "Real Madrid" },
  ]);

  const deleteItem = (index) =&amp;gt; {
    const tempArray = [...soccerTeams];
    tempArray.splice(index, 1);
    setSoccerTeams(tempArray);
  };

  return (
    &amp;lt;div className="App"&amp;gt;
      {soccerTeams.map((element, index) =&amp;gt; (
        &amp;lt;div key={hash(element)}&amp;gt;
          &amp;lt;p&amp;gt; {element.team} &amp;lt;/p&amp;gt;
          &amp;lt;p&amp;gt; {hash(element)} &amp;lt;/p&amp;gt;
          &amp;lt;button onClick={() =&amp;gt; deleteItem(index)}&amp;gt;
            Delete from array
          &amp;lt;/button&amp;gt;
        &amp;lt;/div&amp;gt;
      ))}
    &amp;lt;/div&amp;gt;
  );


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

&lt;/div&gt;

&lt;p&gt;Here we use the object-hash package, to generate a hash from the object and use it as a key.&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%2Fpr0ailntydlzrq894rem.gif" 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%2Fpr0ailntydlzrq894rem.gif" alt="hash as a key - demonstration"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see, no problems over here!&lt;/p&gt;

&lt;p&gt;But maybe it's not the best approach as hashes don't ensure uniqueness.&lt;/p&gt;

&lt;p&gt;Moreover, if you have objects with the same contents, it can lead to a problem!&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%2F6oklpwbcou60kbyqvq9i.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%2F6oklpwbcou60kbyqvq9i.png" alt="items with the same keys - demonstration"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  In the end, let me mention a few things we haven't touched before
&lt;/h2&gt;

&lt;p&gt;Take a look at this code:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

 const [soccerTeams, setSoccerTeams] = useState([
    { team: "Chelsea", id: "667" },
    { team: "Liverpool", id: "666" },
  ]);

  const [soccerTeams1, setSoccerTeams1] = useState([
    { team: "Juventus", id: "667" },
    { team: "Arsenal", id: "666" },
  ]);

  return (
    &amp;lt;div className="App"&amp;gt;
      {soccerTeams.map((element) =&amp;gt; (
        &amp;lt;div key={element.id}&amp;gt;
          &amp;lt;p&amp;gt; {element.team} &amp;lt;/p&amp;gt;
        &amp;lt;/div&amp;gt;
      ))}
      {soccerTeams1.map((element) =&amp;gt; (
        &amp;lt;div key={element.id}&amp;gt;
          &amp;lt;p&amp;gt; {element.team} &amp;lt;/p&amp;gt;
        &amp;lt;/div&amp;gt;
      ))}
    &amp;lt;/div&amp;gt;
  );


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

&lt;/div&gt;

&lt;p&gt;We have two different arrays.&lt;/p&gt;

&lt;p&gt;And as you probably noticed their elements have the same IDs.&lt;/p&gt;

&lt;p&gt;Will this cause a problem (like - two children have the same ID  and blah blah)?&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%2Fejlizpdktpcq1qrzgnzw.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%2Fejlizpdktpcq1qrzgnzw.png" alt="unique keys among siblings - demonstration"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nah! Keys could be the same for 2 different arrays&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Just remember, keys must be only unique among siblings.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  One more example and we are done, guys!
&lt;/h2&gt;

&lt;p&gt;What's wrong with this code?&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

&amp;lt;div className="App"&amp;gt;
   {soccerTeams.map((element) =&amp;gt; (
     &amp;lt;div&amp;gt;
       &amp;lt;p key={element.id}&amp;gt;{element.team}&amp;lt;/p&amp;gt;
     &amp;lt;/div&amp;gt;
    ))}
&amp;lt;/div&amp;gt;


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

&lt;/div&gt;

&lt;p&gt;We have a &lt;em&gt;key&lt;/em&gt;, but in our console, we see the warning.&lt;br&gt;
React is not happy!&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%2Fxwyy5guusn4fi5hn58mn.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%2Fxwyy5guusn4fi5hn58mn.png" alt="outer most child key - demonstration"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The problem is simple - the &lt;em&gt;key&lt;/em&gt; needs to go on the outermost returned element!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In our case - on the &lt;em&gt;div&lt;/em&gt; element.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

 &amp;lt;div className="App"&amp;gt;
   {soccerTeams.map((element) =&amp;gt; (
      &amp;lt;div key={element.id}&amp;gt;
        &amp;lt;p&amp;gt;{element.team}&amp;lt;/p&amp;gt;
      &amp;lt;/div&amp;gt;
   ))}
&amp;lt;/div&amp;gt;


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

&lt;/div&gt;

&lt;p&gt;Hah! Problem solved!&lt;/p&gt;

&lt;h2&gt;
  
  
  Is there any default value?
&lt;/h2&gt;

&lt;p&gt;Almost forgot) Yeah, there is.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;According to React docs, if you choose not to assign an explicit key to list items then React will default to using indexes as keys.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But as we already went through this, you understand that maybe it's not a good idea!&lt;/p&gt;




&lt;h2&gt;
  
  
  Brief conclusion:
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Keys are crucially important and you should not neglect using them.&lt;br&gt;
In the best scenario, you should use IDs as values for keys, but if you do not have ones, you can use hashing, ID generation tools.&lt;br&gt;
In some cases, it's totally appropriate to use indexes (if it's just simple data that won't change in the future)&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;And that's it, guys.&lt;/p&gt;

&lt;p&gt;I hope that you have learned something new today!&lt;br&gt;
I would appreciate it if you could like this post or leave a comment below!&lt;/p&gt;

&lt;p&gt;Also, feel free to follow me on &lt;a href="https://github.com/Kai4ik" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; and &lt;a href="https://medium.com/@kai4ik" rel="noopener noreferrer"&gt;Medium&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;Adios, mi amigos)&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%2F5sbdwv2loa6pjnko0fiz.gif" 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%2F5sbdwv2loa6pjnko0fiz.gif" alt="bye bye meme - simpsons"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>react</category>
      <category>javascript</category>
      <category>programming</category>
    </item>
    <item>
      <title>Everything you need to know about React.memo (with examples)</title>
      <dc:creator>Kairat</dc:creator>
      <pubDate>Sat, 05 Mar 2022 05:14:41 +0000</pubDate>
      <link>https://dev.to/kairatorozobekov/everything-you-need-to-know-about-reactmemo-with-examples-57em</link>
      <guid>https://dev.to/kairatorozobekov/everything-you-need-to-know-about-reactmemo-with-examples-57em</guid>
      <description>&lt;p&gt;Probably you have already reached some level at React - you have an understanding of what state and props are, how to use basic React hooks - useState, useEffect.&lt;/p&gt;

&lt;p&gt;And maybe, you've started to notice that sometimes your React components work very slow (especially the heavy ones with a lot of UI elements and other components)&lt;/p&gt;

&lt;p&gt;And you started to ponder on how to fix it and optimize the performance...&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%2Fgb90ntp4increjqi4n19.gif" 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%2Fgb90ntp4increjqi4n19.gif" alt="Pondering about infinity" width="498" height="328"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;After some research, you stumbled upon something called React memo.&lt;/p&gt;

&lt;h2&gt;
  
  
  You could ask yourself: What the heck is that ?
&lt;/h2&gt;

&lt;p&gt;So, React memo is a HOC - higher-order component that allows you to improve the performance of your React app.&lt;br&gt;
It skips rendering the component if passed props have not changed.&lt;/p&gt;
&lt;h2&gt;
  
  
  How does it work?
&lt;/h2&gt;

&lt;p&gt;Super simple. A memo will just memoize the rendered output of the component and before the next render, it will compare props.&lt;br&gt;
If nothing changed, the memo will just reuse the last rendered output.&lt;/p&gt;

&lt;p&gt;Let me show you an easy example that will demonstrate the difference between the component wrapped into React.memo HOC and just plain component.&lt;/p&gt;



&lt;p&gt;We have an "App" component that has 1 state variable "counter".&lt;/p&gt;

&lt;p&gt;Also, it has 2 child components - PlainComponent (that is just a plain component that does not use React.memo HOC) and MemoComponent (that is wrapped into React.memo HOC)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function App() {
  const [counter, setCounter] = useState(1);
  return (
    &amp;lt;div className="App"&amp;gt;
      &amp;lt;p&amp;gt; {counter}&amp;lt;/p&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; setCounter(counter + 1)}&amp;gt; Set Counter&amp;lt;/button&amp;gt;
      &amp;lt;div className="childComponents"&amp;gt;
        &amp;lt;MemoComponent /&amp;gt;
        &amp;lt;PlainComponent /&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const PlainComponent = () =&amp;gt; {
  console.info("Child Component - no memo HOC");
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h3&amp;gt; Plain Component &amp;lt;/h3&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const MemoComponent = React.memo(() =&amp;gt; {
  console.info("Child Component - uses memo HOC");
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h3&amp;gt; Memo Component &amp;lt;/h3&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So, when we will change the state of the "App" component (by incrementing a counter), it should cause its child components to re-render.&lt;/p&gt;

&lt;p&gt;But as you can see, only the plain component re-rendered.&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%2Fwxqkrjiiuoneakxhexg6.gif" 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%2Fwxqkrjiiuoneakxhexg6.gif" alt="Example shown - React.memo vs plain component" width="600" height="338"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  React.memo &amp;amp; props
&lt;/h2&gt;

&lt;p&gt;But we haven't passed any props to our child components.&lt;/p&gt;

&lt;p&gt;What if we gonna pass the prop to the Memo component and this prop will change.&lt;/p&gt;

&lt;p&gt;Is it going to ignore this change or it will re-render the component and reflect the modification?&lt;/p&gt;

&lt;p&gt;Let's take a look at another example!&lt;/p&gt;




&lt;p&gt;We are going to use the same "App" and "MemoComponent" but this time, I added one more state variable to the App component - "&lt;em&gt;passedProp&lt;/em&gt;".&lt;/p&gt;

&lt;p&gt;This variable will change every time the remainder of our "&lt;em&gt;counter&lt;/em&gt;" will be equal to 0. &lt;/p&gt;

&lt;p&gt;And we gonna pass this prop to the "MemoComponent"&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function App() {
  const [counter, setCounter] = useState(1);
  const [passedProp, setPassedProp] = useState(0);

  useEffect(() =&amp;gt; {
    if (counter % 5 === 0) setPassedProp(passedProp + 1);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [counter]);

  return (
    &amp;lt;div className="App"&amp;gt;
      &amp;lt;p&amp;gt; {counter}&amp;lt;/p&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; setCounter(counter + 1)}&amp;gt; Set Counter&amp;lt;/button&amp;gt;
      &amp;lt;div className="childComponents"&amp;gt;
        &amp;lt;MemoComponent prop={passedProp}/&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In MemoComponent we will just display passed prop&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const MemoComponent = React.memo(({prop}) =&amp;gt; {
  console.info("Child Component - uses memo HOC");
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h3&amp;gt; Memo Component &amp;lt;/h3&amp;gt;
      &amp;lt;p&amp;gt; {prop}&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every time "&lt;em&gt;passedProp&lt;/em&gt;" changes, our MemoComponent re-renders.&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%2Ft0ryutfrl0dq9a1etvth.gif" 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%2Ft0ryutfrl0dq9a1etvth.gif" alt="Example Shown - props &amp;amp; React.memo" width="600" height="338"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  React.memo &amp;amp; state of the component
&lt;/h2&gt;

&lt;p&gt;What if the component wrapped into React.memo HOC has its own state and this state changes?&lt;br&gt;
Is it going to re-render or not?&lt;/p&gt;

&lt;p&gt;Now, our MemoComponent has one state variable - "&lt;em&gt;randomNumber&lt;/em&gt;" and the button to modify it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const MemoComponent = React.memo(() =&amp;gt; {
  const [randomNumber, setRandomNumber] = useState(Math.random());
  console.info("Child Component - uses memo HOC");

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h3&amp;gt; Memo Component &amp;lt;/h3&amp;gt;
      &amp;lt;p&amp;gt; {randomNumber}&amp;lt;/p&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; setRandomNumber(Math.random())}&amp;gt;Set random&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every time we change "&lt;em&gt;randomNumber&lt;/em&gt;", our component gonna re-render.&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%2Faabrf0sr95p641cz2p2c.gif" 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%2Faabrf0sr95p641cz2p2c.gif" alt="Example shown - React.memo &amp;amp; state of the component" width="600" height="338"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So, if your component has a useState, useContext, or useReducer in its implementation, it will re-render when the state (context) changes.&lt;/p&gt;




&lt;h2&gt;
  
  
  When to use it ?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;data-heavy components that are provided the same props all the time&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;big size component that has a decent amount of UI elements&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why not use it everywhere ?
&lt;/h2&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%2Fb8vl6x3pa8jdnx62i5ew.jpg" 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%2Fb8vl6x3pa8jdnx62i5ew.jpg" alt="Why not image" width="400" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Probably you thought about that.&lt;/p&gt;

&lt;p&gt;But !!!&lt;/p&gt;

&lt;p&gt;Internally React.memo compares props (their previous and new state) so it can decide whether to re-render component or not (if props changed - it should re-render, otherwise not)&lt;/p&gt;

&lt;p&gt;And most of the time, computation for this comparison can be even more expensive and take even more time than just re-rendering the component&lt;/p&gt;

&lt;h2&gt;
  
  
  That is why you should not use React.memo if:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;component is cheap to re-render&lt;/li&gt;
&lt;li&gt;passed props change often (so there is no meaning to use memo, the component will re-render anyway)&lt;/li&gt;
&lt;li&gt;comparison function is expensive to perform&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Also, you should not use it as a way to "prevent" a render.&lt;br&gt;
It can lead to bugs!&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;And the last thing I want to mention is the &lt;strong&gt;custom comparison function&lt;/strong&gt; that can be passed as the second argument.&lt;/p&gt;

&lt;p&gt;This function can perform a comparison of previous and new props, and determine whether the component should re-render or not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why would we need this?
&lt;/h2&gt;

&lt;p&gt;Consider this example:&lt;/p&gt;

&lt;p&gt;In the "App" component we have an object that consists of 1 property and we pass this object to Memo Component.&lt;br&gt;
We do not modify it anywhere.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function App() {
  const [counter, setCounter] = useState(1);
  const complexObject = useState({ qty: 0 });

  return (
    &amp;lt;div className="App"&amp;gt;
      &amp;lt;p&amp;gt; {counter}&amp;lt;/p&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; setCounter(counter + 1)}&amp;gt; Set Counter&amp;lt;/button&amp;gt;
      &amp;lt;div className="childComponents"&amp;gt;
        &amp;lt;MemoComponent prop={complexObject} /&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const MemoComponent = React.memo(() =&amp;gt; {
  console.info("Child Component - uses memo HOC");

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h3&amp;gt; Memo Component &amp;lt;/h3&amp;gt;
    &amp;lt;/div&amp;gt;
  );
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But every time we gonna change the state by incrementing "counter", our MemoComponent is re-rendered (despite the fact we use React.memo)&lt;/p&gt;

&lt;p&gt;Why is that happening?&lt;/p&gt;

&lt;p&gt;When we change the state of the "App" component, we re-create an object, and React thinks that the passed prop has changed and thus forces MemoComponent to re-render.&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%2Fiddruz2087dyrfmdcw1h.gif" 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%2Fiddruz2087dyrfmdcw1h.gif" alt="Example shown - React.memo &amp;amp; complex objects" width="600" height="338"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  So, how to fix it?
&lt;/h2&gt;

&lt;p&gt;Easy-peasy.&lt;br&gt;
We just have to pass the function as a second argument that will compare 2 states of props.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const MemoComponent = React.memo(
  () =&amp;gt; {
    console.info("Child Component - uses memo HOC");

    return (
      &amp;lt;div&amp;gt;
        &amp;lt;h3&amp;gt; Memo Component &amp;lt;/h3&amp;gt;
      &amp;lt;/div&amp;gt;
    );
  },
  (previousProps, nextProps) =&amp;gt; {
    return previousProps.prop.qty === nextProps.prop.qty;
  }
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So, as you can see we check whether the "&lt;em&gt;qty&lt;/em&gt;" prop of a passed object has changed or not.&lt;/p&gt;

&lt;p&gt;If the state of props is different, we have to return &lt;strong&gt;false&lt;/strong&gt;, and this will cause a component to re-render.&lt;/p&gt;

&lt;p&gt;Otherwise, the function returns &lt;strong&gt;true&lt;/strong&gt; and we gonna use the previously rendered output.&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%2Fxzcbnt5r5l3uegqa2y2i.gif" 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%2Fxzcbnt5r5l3uegqa2y2i.gif" alt="Example shown - React.memo &amp;amp; second argument to it" width="600" height="338"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;And that's it, guys.&lt;br&gt;
Now you are ready to use React.memo in your React projects!&lt;/p&gt;

&lt;p&gt;I hope that you have learned something new today! &lt;br&gt;
I would appreciate it if you could like this post or leave a comment below! &lt;br&gt;
Also, feel free to follow me on &lt;a href="https://github.com/Kai4ik"&gt;GitHub&lt;/a&gt; and &lt;a href="https://medium.com/@kai4ik"&gt;Medium&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;Adios, mi amigos)&lt;/p&gt;

</description>
      <category>react</category>
      <category>javascript</category>
      <category>programming</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
