<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
    <channel>
        <title><![CDATA[The garage - All posts]]></title>
        <description><![CDATA[Garage for engineers. Place where you can find notes, tutorials, experiments, concepts explanation and more.]]></description>
        <link>https://frodigo.com</link>
        <image>
            <url>https://frodigo.com/favicon-32.png</url>
            <title>The garage - All posts</title>
            <link>https://frodigo.com</link>
        </image>
        <generator>RSS for Node</generator>
        <lastBuildDate>Fri, 27 Mar 2026 15:54:54 GMT</lastBuildDate>
        <atom:link href="https://rss.frodigo.com/feed.xml" rel="self" type="application/rss+xml"/>
        <pubDate>Fri, 27 Mar 2026 15:54:54 GMT</pubDate>
        <language><![CDATA[en]]></language>
        <ttl>60</ttl>
        <item>
            <title><![CDATA[Closing the Garage]]></title>
            <description><![CDATA[I built the https://frodigo.com/ as a space for programmers who value traditional coding. A place where we write code ourselves, where AI doesn&#39;t use us — we use AI. A small rebellion against the vibe coding era.]]></description>
            <link>https://frodigo.com/Blog/Closing+the+Garage</link>
            <guid isPermaLink="false">https://frodigo.com/Blog/Closing+the+Garage</guid>
            <pubDate>Fri, 27 Mar 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>I built the <a href="https://frodigo.com/">https://frodigo.com/</a> as a space for programmers who value traditional coding. A place where we write code ourselves, where AI doesn&#39;t use us — we use AI. A small rebellion against the vibe coding era.</p>
<p>It was a romantic idea. <em>Eviva l&#39;arte!</em> and all that.</p>
<p>But I&#39;m shutting it down. The website at frodigo.com will no longer be available, and the project won&#39;t be continued. Website content will be available on <a href="https://github.com/RetroModernDev/garage">https://github.com/RetroModernDev/garage</a>.</p>
<h2>Why?</h2>
<p>Because my resistance was pointless.</p>
<p>Big tech companies have already decided for all of us. AI is not a direction for the future. It&#39;s the present. Fighting it felt like standing in front of a train and asking it to slow down. The train doesn&#39;t care.</p>
<p>I still believe that understanding code matters. I still think that writing your first function yourself teaches you something that no AI-generated output ever will. But spending energy on resistance? That&#39;s a losing game. I&#39;d rather spend that energy on creating things.</p>
<h2>What I&#39;m doing now</h2>
<p>I shifted my focus. Instead of fighting AI, I have put my energy into these things:</p>
<ul>
<li><strong>Restoring old computers</strong> — I&#39;m into retro hardware, especially Commodore Amiga. Recapping, repainting cases, bringing old machines back to life.</li>
<li><strong>Playing games</strong> — retro and modern. Rediscovering what made me fall in love with computers in the first place.</li>
<li><strong>Creating games and apps with AI</strong> — instead of resisting the technology, I use it to make things. That feels much better than complaining.</li>
</ul>
<h2>Where to find me</h2>
<p>I started a new brand called <strong>RetroModern.dev</strong> — where retro meets modern. Also I changed my nickname everywhere from Frodigo to RetroModernDev. Yeah, I have started a new digital life. :)</p>
<ul>
<li><strong>YouTube</strong>: <a href="https://www.youtube.com/@RetroModernDev">https://www.youtube.com/@RetroModernDev</a></li>
<li><strong>Website</strong>: <a href="https://retromodern.dev/">https://retromodern.dev/</a></li>
<li><strong>Medium</strong> (English): <a href="https://medium.com/@RetroModernDev">https://medium.com/@RetroModernDev</a></li>
</ul>
<p>The YouTube channel and website are in Polish, but let&#39;s be honest — with today&#39;s translation tools, that&#39;s not really a barrier anymore.</p>
<p>You can also find me on <a href="https://x.com/RetroModernDev">https://x.com/RetroModernDev</a> · <a href="https://instagram.com/retromoderndev">https://instagram.com/retromoderndev</a> · <a href="https://www.tiktok.com/@retromoderndev">https://www.tiktok.com/@retromoderndev</a> · <a href="https://www.facebook.com/retromoderndev/">https://www.facebook.com/retromoderndev/</a></p>
<h2>RSS</h2>
<p>If you followed the Garage via RSS, here are the new feeds:</p>
<ul>
<li>Website: <a href="https://retromodern.dev/feed/">https://retromodern.dev/feed/</a></li>
<li>Medium: <a href="https://medium.com/feed/@RetroModernDev">https://medium.com/feed/@RetroModernDev</a></li>
</ul>
<h2>Good bye</h2>
<p>The Garage was a good idea at a wrong time. Or maybe at the right time — it helped me realize that resisting tools is not the same as preserving craft. You can use AI and still care about quality. You can vibe code and still understand what&#39;s happening under the hood.</p>
<p>I just don&#39;t want to spend my time configuring rebellion anymore. I&#39;d rather restore an Amiga 600 and make a video about it.</p>
<p>See you on the other side. Bye!</p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[AI is taking away our independence]]></title>
            <description><![CDATA[I have been working with Cursor AI for 8 months intensively in my corporate job. Recently, I conducted an experiment that is quite unusual these days. I wanted to solve the task without using any help from AI. I have used web searches, Stack Overflow, and, more importantly, I have started writing code with my hands.]]></description>
            <link>https://frodigo.com/Blog/2025/AI+is+taking+away+our+independence</link>
            <guid isPermaLink="false">https://frodigo.com/Blog/2025/AI+is+taking+away+our+independence</guid>
            <pubDate>Tue, 09 Dec 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>I have been working with Cursor AI for 8 months intensively in my corporate job. Recently, I conducted an experiment that is quite unusual these days. I wanted to solve the task without using any help from AI. I have used web searches, Stack Overflow, and, more importantly, I have started writing code with my hands.</p>
<p>Guess what? I have felt mental pain because I needed to write this code. After a few hours, I was exhausted. Is this me? Am I the same person who some time ago could write code for hours in hyperfocus?</p>
<p>I have unlearned how to solve problems without any help. Am I junior again? Perhaps it’s just me, because when I open LinkedIn, I see how much AI helps, and how people are happier when they use AI tools.</p>
<p>Or maybe people like me are silent because it’s a shame to talk about failures in this successful world? Or perhaps they are quiet because they assume that showing scepticism about AI will create a problem in their current job or hinder their ability to find a new one.</p>
<p>AI doesn’t need to be smarter than us to be dangerous. It just needs to make us dumber. As we increasingly outsource our thinking to machines, the ability to solve problems with our hands and brains is becoming a rare skill.</p>
<h2>The illusion of progress</h2>
<p>People commonly say that the main advantage of using AI is to make things faster. But is it the case for all problems? Do all companies work in an ASAP culture? Is it really developing things with AI faster?</p>
<p>When I started writing code in C#, I used Cursor a lot, because I had a skill issue. I was familiar with general programming principles, and I had written code in a few other languages. But I didn’t know C#. Using AI was magical because I felt that I was more capable than I actually was.</p>
<p>Also, at the beginning of my C# career, I worked on relatively simple tasks that didn’t require advanced .NET/C # skills. After six months, I started feeling frustrated with using Cursor because, in the meantime, I had learned C# a little bit, and things that Cursor did for me started to look less magical than before.</p>
<p>I realized that the code produced by the Cursor is a mess. Additionally, I began working on problems that the Cursor was unable to solve. So, I needed to solve problems by hand, but wait - it wasn&#39;t as easy as I thought. After months of using Cursor, writing code started to be painful. Being focused was aching even more.</p>
<h2>The skills we’re losing</h2>
<p>AI can help us with many things, such as writing, problem-solving, finding information, memorizing, and… thinking. But should we put thinking into AI?</p>
<p>I am confident we shouldn’t, mainly because AI is not capable of thinking. It just produces words. However, it appears to be thinking. This is another illusion that many people buy as fact.</p>
<p>AI helps with current problems, but it can also create a long-term dependence. Take a look:</p>
<ul>
<li>When you use Grammarly, you will not learn grammar</li>
<li>When you use Google Maps, you will not be able to travel anywhere without this app and GPS.</li>
<li>When you ask AI about a solution to each problem, you will not be able to solve the problems by yourself.</li>
</ul>
<p>A person without critical thinking skills becomes as stupid as these language models.</p>
<h2>The counterargument</h2>
<p>You can respond that “AI is just a tool — it’s up to us to use it responsibly,” but this isn’t enough, because using AI is addictive.</p>
<p>As I pointed out before, especially when you do not have the skills for something, help from AI looks like a magical thing. Then you are more and more addicted, and don’t see that you fall into the trap of getting rid of your basic skills.</p>
<p>Theoretically, you can feel that you learn when you use AI, but when you do a real test: disable internet connection, disable AI, have only a problem and a sheet of paper. Then you see a real problem. Then you see, oh, I can’t even start. Where is my AI friend, oh gosh!</p>
<p>You thought you learned a lot and gained new skills thanks to AI, but in reality, you learned almost nothing and lost some of your existing skills. Thanks, AI!</p>
<h2>Reclaiming our skills</h2>
<p>I have experienced all of this that I described in my life and work. I used Cursor for everything at the job, and I even used AI to write my journal. I stopped blogging because I couldn’t find the motivation to write an article on my own.</p>
<p>But the human brain is remarkably capable of learning and adapting throughout life. We can reclaim our independence by consciously training our skills.</p>
<p>You can achieve independence by learning. You can still use AI, but you can also practice skills without AI assistance. You can try first, solve the problem by yourself, and ask AI for help after a few tries.</p>
<p><em>If you’re unsure where to start, I recommend installing NeoVim. I did this, and I have many problems to solve, many opportunities to learn new things, and a lot of fun!</em></p>
<h2>Conclusion</h2>
<p>Artificial intelligence is supposed to improve our quality of life and work, but the hidden cost is that we become dependent and subservient to machines and the corporations that produce them. We’ve outsourced so much thinking that we risk becoming consumers, rather than creators of knowledge. When we use AI, we need to consider whether we are using AI or if AI is actually using us.</p>
<hr>
<p><em>Published: 12/09/2025</em> #blog #AIImpact #FutureOfWork #AISkills #programming #CursorAI #digitalwellbeing</p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The authenticity of leadership]]></title>
            <description><![CDATA[In winter 2021, the CTO of VueStorefront sent me a message, &quot;We would like you to lead the Vue Storefront Magento team.&quot; After years of writing code and being a &quot;pixel-perfect&quot; frontend developer, this was my chance for promotion. However, as I was about to discover, the path from being a skilled programmer to an effective tech lead was far more demanding than I had imagined.]]></description>
            <link>https://frodigo.com/Blog/2025/The+authenticity+of+leadership</link>
            <guid isPermaLink="false">https://frodigo.com/Blog/2025/The+authenticity+of+leadership</guid>
            <pubDate>Tue, 09 Dec 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>In winter 2021, the CTO of VueStorefront sent me a message, &quot;We would like you to lead the Vue Storefront Magento team.&quot; After years of writing code and being a &quot;pixel-perfect&quot; frontend developer, this was my chance for promotion. However, as I was about to discover, the path from being a skilled programmer to an effective tech lead was far more demanding than I had imagined.</p>
<h2>Life as a developer</h2>
<p>When I worked as a programmer, I had everything under control. I could find a solution to almost every problem. I felt like an expert in my field.</p>
<p>I&#39;d never been a project/team leader, so the offer from VueStorefront was quite interesting at the time.</p>
<p>I would have continued coding Magento, but a 30% raise and the move from a software house to a product company also prompted me to make a change.</p>
<hr>
<h2>Team leading</h2>
<p>In the first weeks, I felt like I was wearing someone else&#39;s shoes. The skills that made me an effective programmer—deep focus, technical perfectionism, and independent work—were no longer enough.</p>
<p>I painfully recognized all my weaknesses. I tried to figure it out, caught up, and worked my ass off. Every day was incredibly intense.</p>
<p>For a long time, I tried to fit into the image of the &quot;ideal Tech Lead.&quot; It wasn&#39;t until I stopped pretending and started drawing from my own experiences that I found my authentic voice.</p>
<p>You can read books and learn how to be a perfect manager. But honesty is what matters to people. It&#39;s better to be yourself and often even admit a mistake or weakness than to pretend to be someone you&#39;re not.</p>
<p>I received praise; they said I was doing well and that I was a good leader, but I felt increasingly overwhelmed.</p>
<p>The turning point came when I realized I had started to fear the sight of my computer. The same work that once energized me now left me exhausted and disconnected.</p>
<p>More hours at work === better results. Reality quickly verified this belief. When I was exhausted, I made worse decisions and couldn&#39;t support the team.</p>
<p>In meetings, I caught myself nodding while my mind wandered elsewhere. At home, I couldn&#39;t stop thinking about work, and at work, I couldn&#39;t focus on tasks.</p>
<hr>
<h2>That&#39;s enough</h2>
<p>I needed help. After months of trying to be the perfect technical leader who could do everything, I finally opened up to my boss about my struggles.</p>
<p>After that, I started setting clear boundaries between work time and private time, treating my energy as a limited resource that needed to be carefully managed.</p>
<p>I tried a lot to recover, but finally, after three years, I decided that I needed to step back and stop being a team lead. Anyway, I learned a great deal, and it was worth the time and energy I invested.</p>
<hr>
<h2>Final word</h2>
<p>I spent too long trying to be a tech lead, instead of simply being myself. The most valuable lesson I learned wasn&#39;t about management techniques or project planning.</p>
<p>Actually, it was about the power of authenticity. A team leader doesn&#39;t have to be the same as all other managers. The goal is to build a team around your strengths and be honest about your weaknesses.</p>
<hr>
<p><em>Published: 12/09/2025</em> #blog #leadership</p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[NumPy Essentials]]></title>
            <description><![CDATA[I’m learning machine learning and I’m at level 0, just getting started. Last week, I was exploring things related to NumPy, and the result of my learning is a Jupyter notebook, which I wanted to share with you.]]></description>
            <link>https://frodigo.com/Blog/2025/Numpy+essentials+I+learned+as+beginner</link>
            <guid isPermaLink="false">https://frodigo.com/Blog/2025/Numpy+essentials+I+learned+as+beginner</guid>
            <pubDate>Fri, 07 Nov 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>I’m learning machine learning and I’m at level 0, just getting started. Last week, I was exploring things related to NumPy, and the result of my learning is a Jupyter notebook, which I wanted to share with you.</p>
<p>Note: You can find <a href="https://github.com/Frodigo/garage/blob/main/Atlas/Engineering/machine-learning/level-0-python-and-math-essentials/numpy/numpy-essentials.ipynb">https://github.com/Frodigo/garage/blob/main/Atlas/Engineering/machine-learning/level-0-python-and-math-essentials/numpy/numpy-essentials.ipynb</a> (On blog you can see this notebook converted to text).</p>
<p>NumPy (Numerical Python) is a Python library that makes working with numbers faster and easier. It offers new datatype: ndarray, vectorization and bunch of useful functions. NumPy is a tool that every ML or Data Engineer use daily.In this Notebook, I am going through NumPy features and show examples how it can be used.</p>
<h2>Installation</h2>
<p>Run a cell below to install numpy:</p>
<pre><code class="language-python">%pip install numpy
</code></pre>
<pre><code>Requirement already satisfied: numpy in /opt/anaconda3/envs/mlenv/lib/python3.13/site-packages (2.2.2)
Note: you may need to restart the kernel to use updated packages.
</code></pre>
<p>Run this code to verify NumPy is installed:</p>
<pre><code class="language-python">import numpy as np
print(np.__version__)
</code></pre>
<pre><code>2.2.2
</code></pre>
<p>You should see a version of NumPy.</p>
<p><strong>What this means:</strong></p>
<ul>
<li><code>import numpy</code> - Load the NumPy library so you can use it</li>
<li><code>as np</code> - Create a nickname &quot;np&quot; so you can type <code>np.array()</code> instead of <code>numpy.array()</code> every time (we are lazy and we prefer to write to letters instead of five, yeah?)</li>
<li>This is a convention - most NumPy code uses <code>np</code>, so you&#39;ll see it everywhere</li>
</ul>
<p><strong>Why not just <code>import numpy</code>?</strong> You can! But <code>np</code> is shorter and everyone uses it, so your code will match examples you find online. (and as i said, we are lazy, or at least me).</p>
<h2>NumPy Advantages</h2>
<p>Once NumPy is installed, let&#39;s see it biggest advantages. Vectorization allows you to apply an operation to each element in array without doing a loop. Another thing is a bunch of methods that allows you to do mathematical operations. All of this will not be possible without a <code>ndarray</code>, which is a core data structure in NumPy.</p>
<h3>Vectorization</h3>
<p>Vectorization allows applying an operation to an entire array at once, rather than looping through each element.</p>
<p>In NumPy, you can perform operations on entire arrays without explicit loops. For example <code>arr + 10</code> will add <code>10</code> to each element of <code>arr</code>.</p>
<p>Let&#39;s see this in action:</p>
<pre><code class="language-python">import time  # Built-in Python module for measuring time

# Example: Converting engine RPM to different units
# Let&#39;s say we have RPM readings from a car&#39;s engine
rpm_values = [1000, 2000, 3000, 4000, 5000]

# Method 1: Using Python loop (slow way)
# Convert RPM to radians per second (multiply by 0.10472)
result_python = []
for rpm in rpm_values:
    result_python.append(rpm * 0.10472)
print(f&quot;Using Python loop: {result_python}&quot;)

# Method 2: Using NumPy vectorization (fast way)
rpm_array = np.array(rpm_values)  # Convert list to NumPy array
result_numpy = rpm_array * 0.10472  # Single operation on entire array!
print(f&quot;Using NumPy vectorization: {result_numpy}&quot;)
print(&quot;→ Same result, less code! This is called &#39;vectorization&#39;&quot;)

</code></pre>
<pre><code>Using Python loop: [104.72, 209.44, 314.15999999999997, 418.88, 523.6]
Using NumPy vectorization: [104.72 209.44 314.16 418.88 523.6 ]
→ Same result, less code! This is called &#39;vectorization&#39;
</code></pre>
<p>In example above you can see that we can just do <code>rpm_array * 0.10472</code> and we don&#39;t need looping. That&#39;s great - less code, same results. But let&#39;s see how time consuming are these methods in large datasets. Imagine analyzing RPM data from 1 million readings. Spoiler: NumPy is way more faster!</p>
<pre><code class="language-python"># Create large dataset: 1 million RPM values from 0 to 7000
# Note: range(1000000) creates numbers 0 to 999,999, then we multiply by 7
python_list = [rpm * 7 for rpm in range(1000000)]  # List comprehension
numpy_array = np.array(python_list)  # Convert to NumPy array

# Time the Python loop method
start = time.time()  # Record start time
result_python = []
for rpm in python_list:
    result_python.append(rpm * 0.10472)  # Convert each RPM
python_time = time.time() - start  # Calculate elapsed time

# Time the NumPy vectorization method
start = time.time()
result_numpy = numpy_array * 0.10472  # Single operation!
numpy_time = time.time() - start

print(f&quot;Python loop time: {python_time:.4f} seconds&quot;)
print(f&quot;NumPy vectorized time: {numpy_time:.4f} seconds&quot;)
print(f&quot;NumPy is {python_time/numpy_time:.1f}x faster!&quot;)

</code></pre>
<pre><code>Python loop time: 0.0327 seconds
NumPy vectorized time: 0.0008 seconds
NumPy is 40.7x faster!
</code></pre>
<p>40x faster, amazing, no? Let&#39;s take a look at other vectorization examples:</p>
<pre><code class="language-python">
# Example: Processing speed data from GPS
speeds_mph = np.array([30, 45, 60, 70, 85])  # Speeds in mph
print(f&quot;Original speeds (mph): {speeds_mph}&quot;)

# Convert to km/h (multiply by 1.60934)
speeds_kmh = speeds_mph * 1.60934
print(f&quot;Converted to km/h: {speeds_kmh}&quot;)

# Calculate speeds in m/s (divide km/h by 3.6)
speeds_ms = speeds_kmh / 3.6
print(f&quot;Converted to m/s: {speeds_ms}&quot;)

# Add 5 mph to all speeds (simulating cruise control adjustment)
adjusted_speeds = speeds_mph + 5
print(f&quot;Adjusted speeds (+5 mph): {adjusted_speeds}&quot;)

# Calculate power (simplified: power ≈ speed²)
power = speeds_mph ** 2
print(f&quot;Relative power (speed²): {power}&quot;)
</code></pre>
<pre><code>Original speeds (mph): [30 45 60 70 85]
Converted to km/h: [ 48.2802  72.4203  96.5604 112.6538 136.7939]
Converted to m/s: [13.41116667 20.11675    26.82233333 31.29272222 37.99830556]
Adjusted speeds (+5 mph): [35 50 65 75 90]
Relative power (speed²): [ 900 2025 3600 4900 7225]
</code></pre>
<h3>Mathematical Operations</h3>
<p>NumPy offers built-in functions for linear algebra, statistics, and more. These are essential for analyzing automotive data - like calculating average fuel consumption, analyzing engine performance, or processing sensor readings.</p>
<p>Let&#39;s explore the most useful functions with automotive examples:</p>
<pre><code class="language-python"># Example: Fuel consumption data (L/100km) from 10 different trips
fuel_consumption = np.array([7.2, 8.5, 6.9, 9.1, 7.8, 8.3, 7.5, 6.7, 8.9, 7.4])
print(f&quot;Fuel consumption data (L/100km): {fuel_consumption}&quot;)
</code></pre>
<pre><code>Fuel consumption data (L/100km): [7.2 8.5 6.9 9.1 7.8 8.3 7.5 6.7 8.9 7.4]
</code></pre>
<h4>Statistics</h4>
<p>Based on our example we can easily analyze fuel consumption, calculating average speeds, finding performance metrics, or analyzing sensor data.</p>
<pre><code class="language-python">print(f&quot;Fuel consumption data (L/100km): {fuel_consumption}&quot;)
print(f&quot;\nStatistics:&quot;)
print(f&quot;Mean (average): {np.mean(fuel_consumption):.2f} L/100km&quot;)
print(f&quot;Median (middle value): {np.median(fuel_consumption):.2f} L/100km&quot;)
print(f&quot;Standard deviation (variability): {np.std(fuel_consumption):.2f} L/100km&quot;)
print(f&quot;Variance: {np.var(fuel_consumption):.2f}&quot;)
print(f&quot;Minimum: {np.min(fuel_consumption):.1f} L/100km (best efficiency)&quot;)
print(f&quot;Maximum: {np.max(fuel_consumption):.1f} L/100km (worst efficiency)&quot;)
print(f&quot;Range: {np.max(fuel_consumption) - np.min(fuel_consumption):.1f} L/100km&quot;)
print(f&quot;50th percentile (median): {np.percentile(fuel_consumption, 50):.2f} L/100km&quot;)
</code></pre>
<pre><code>Fuel consumption data (L/100km): [7.2 8.5 6.9 9.1 7.8 8.3 7.5 6.7 8.9 7.4]

Statistics:
Mean (average): 7.83 L/100km
Median (middle value): 7.65 L/100km
Standard deviation (variability): 0.79 L/100km
Variance: 0.63
Minimum: 6.7 L/100km (best efficiency)
Maximum: 9.1 L/100km (worst efficiency)
Range: 2.4 L/100km
50th percentile (median): 7.65 L/100km
</code></pre>
<h4>Basic Math Operations</h4>
<p>Simple but essential operations for automotive calculations.</p>
<pre><code class="language-python"># Example: Engine RPM data
rpm = np.array([1000, 2000, 3000, 4000, 5000])
print(f&quot;Engine RPM: {rpm}&quot;)

# Absolute value
rpm_diff = np.array([-100, 50, -200, 150, -75])
print(f&quot;RPM differences: {rpm_diff}&quot;)
print(f&quot;Absolute differences: {np.abs(rpm_diff)}&quot;)

# Square root
power = np.array([100, 200, 300, 400, 500])  # HP
print(f&quot;\nPower (HP): {power}&quot;)
print(f&quot;Square root of power: {np.sqrt(power)}&quot;)

# Exponentiation
speed = np.array([30, 40, 50, 60, 70])  # mph
print(f&quot;\nSpeed (mph): {speed}&quot;)
print(f&quot;Speed squared (used in power calculations): {speed ** 2}&quot;)
</code></pre>
<pre><code>Engine RPM: [1000 2000 3000 4000 5000]
RPM differences: [-100   50 -200  150  -75]
Absolute differences: [100  50 200 150  75]

Power (HP): [100 200 300 400 500]
Square root of power: [10.         14.14213562 17.32050808 20.         22.36067977]

Speed (mph): [30 40 50 60 70]
Speed squared (used in power calculations): [ 900 1600 2500 3600 4900]
</code></pre>
<h4>Rounding &amp; Precision</h4>
<p>When working with data, you often need to round values for display or calculations.</p>
<pre><code class="language-python"># Example: Precise fuel consumption readings
fuel_consumption = np.array([7.234, 8.567, 6.891, 9.123, 7.456])
print(f&quot;Precise fuel consumption (L/100km): {fuel_consumption}&quot;)

# Round to 2 decimal places (typical for fuel consumption)
rounded = np.round(fuel_consumption, 2)
print(f&quot;Rounded to 2 decimals: {rounded}&quot;)

# Round down (floor) - useful for conservative estimates
floored = np.floor(fuel_consumption)
print(f&quot;Floored (rounded down): {floored}&quot;)

# Round up (ceiling) - useful for worst-case estimates
ceiled = np.ceil(fuel_consumption)
print(f&quot;Ceiled (rounded up): {ceiled}&quot;)

# Example: Speed readings
speed = np.array([45.7, 62.3, 78.9, 91.2])
print(f&quot;\nSpeed readings (mph): {speed}&quot;)
print(f&quot;Rounded speeds: {np.round(speed)}&quot;)
</code></pre>
<pre><code>Precise fuel consumption (L/100km): [7.234 8.567 6.891 9.123 7.456]
Rounded to 2 decimals: [7.23 8.57 6.89 9.12 7.46]
Floored (rounded down): [7. 8. 6. 9. 7.]
Ceiled (rounded up): [ 8.  9.  7. 10.  8.]

Speed readings (mph): [45.7 62.3 78.9 91.2]
Rounded speeds: [46. 62. 79. 91.]
</code></pre>
<h2>Understanding NumPy Arrays (ndarray)</h2>
<p><code>ndarray</code> is a core data structure in NumPy - a multidimensional array that can hold lots of data efficiently. Think of it as a supercharged Python list that&#39;s optimized for math.</p>
<p><strong>Key difference from Python lists:</strong></p>
<ul>
<li>Python lists can contain different data types (heterogeneous): <code>[1, &quot;hello&quot;, 3.5]</code></li>
<li>NumPy arrays must contain the same data type (homogeneous): <code>[1, 2, 3]</code> or <code>[1.0, 2.0, 3.0]</code></li>
</ul>
<h3>Creating arrays</h3>
<pre><code class="language-python">my_arr = np.array([1, 2, 3, 4, 5])
my_arr.dtype

ones = np.ones((3, 4))
zeros = np.zeros((2, 3, 4))
full = np.full((2, 3), 4)
arange = np.arange(10, 100, 10)
linspace = np.linspace(0, 1, 5)

print(ones)
print(zeros)
print(full)
print(arange)
print(linspace)


random_arr = np.random.randint(0, 10, (3, 4))
print(random_arr)

random_arr_2 = np.random.rand(2, 2, 2, 2)
print(random_arr_2)
</code></pre>
<pre><code>[[1. 1. 1. 1.]
 [1. 1. 1. 1.]
 [1. 1. 1. 1.]]
[[[0. 0. 0. 0.]
  [0. 0. 0. 0.]
  [0. 0. 0. 0.]]

 [[0. 0. 0. 0.]
  [0. 0. 0. 0.]
  [0. 0. 0. 0.]]]
[[4 4 4]
 [4 4 4]]
[10 20 30 40 50 60 70 80 90]
[0.   0.25 0.5  0.75 1.  ]
[[6 1 8 4]
 [1 7 3 9]
 [8 3 1 4]]
[[[[0.59130761 0.66421588]
   [0.33191183 0.29026851]]

  [[0.39059243 0.12892054]
   [0.01739604 0.80804896]]]


 [[[0.32469533 0.29820351]
   [0.21903883 0.53010489]]

  [[0.53026239 0.05754576]
   [0.31187082 0.55165342]]]]
</code></pre>
<h4>Array Properties</h4>
<p>Understanding array properties helps you work with your data effectively.</p>
<pre><code class="language-python"># Example: Engine sensor data
sensor_data = np.array([[30, 2500, 7.2],   # [speed, RPM, fuel]
                        [45, 3000, 8.5],
                        [60, 3500, 9.1]])

print(f&quot;Array:\n{sensor_data}&quot;)
print(f&quot;\nArray Properties:&quot;)
print(f&quot;.shape → {sensor_data.shape} (rows, columns)&quot;)
print(f&quot;.ndim → {sensor_data.ndim} (number of dimensions)&quot;)
print(f&quot;.size → {sensor_data.size} (total elements)&quot;)
print(f&quot;.dtype → {sensor_data.dtype} (data type)&quot;)
print(f&quot;.itemsize → {sensor_data.itemsize} bytes per element&quot;)
print(f&quot;.nbytes → {sensor_data.nbytes} bytes total&quot;)

# Example: 1D array
speed_data = np.array([30, 45, 60, 70, 85])
print(f&quot;\n1D array: {speed_data}&quot;)
print(f&quot;Shape: {speed_data.shape} (note the comma - it&#39;s a tuple!)&quot;)
print(f&quot;Dimensions: {speed_data.ndim}D&quot;)
</code></pre>
<pre><code>Array:
[[  30.  2500.     7.2]
 [  45.  3000.     8.5]
 [  60.  3500.     9.1]]

Array Properties:
.shape → (3, 3) (rows, columns)
.ndim → 2 (number of dimensions)
.size → 9 (total elements)
.dtype → float64 (data type)
.itemsize → 8 bytes per element
.nbytes → 72 bytes total

1D array: [30 45 60 70 85]
Shape: (5,) (note the comma - it&#39;s a tuple!)
Dimensions: 1D
</code></pre>
<p>Array properties help you understand your data. <code>.shape</code> shows the structure of the array and it&#39;s helpful to check if the data format is correct. <code>.dtype</code> indicates what type of data is stored in the array. <code>.nbytes</code> displays how much memory the array uses (critical when working with very large arrays).</p>
<h3>ndarray (n-dimensional array)</h3>
<p><code>ndarray</code> is multidimensional array that can be 0D (scalar), 1D (vector), 2D (matrix), or higher dimensions. Let&#39;s take a look at examples.</p>
<h4>0D Array (scalar)</h4>
<pre><code class="language-python">scalar = np.array(42)
print(f&quot;Array: {scalar}&quot;)
print(f&quot;Shape: {scalar.shape}&quot;)
print(f&quot;Dimensions: {scalar.ndim}D&quot;)
print(f&quot;Data type: {scalar.dtype}&quot;)
print(f&quot;Size: {scalar.size}&quot;)
</code></pre>
<pre><code>Array: 42
Shape: ()
Dimensions: 0D
Data type: int64
Size: 1
</code></pre>
<h4>1D array (vector)</h4>
<pre><code class="language-python">vector = np.array([1, 2, 3, 4, 5])
print(f&quot;Array: {vector}&quot;)
print(f&quot;Shape: {vector.shape}&quot;)
print(f&quot;Dimensions: {vector.ndim}D&quot;)
print(f&quot;Data type: {vector.dtype}&quot;)
print(f&quot;Size: {vector.size}&quot;)
</code></pre>
<pre><code>Array: [1 2 3 4 5]
Shape: (5,)
Dimensions: 1D
Data type: int64
Size: 5
</code></pre>
<h4>2D array (matrix)</h4>
<pre><code class="language-python">matrix = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print(f&quot;Array:\n{matrix}&quot;)
print(f&quot;Shape: {matrix.shape} (rows, columns)&quot;)
print(f&quot;Dimensions: {matrix.ndim}D&quot;)
print(f&quot;Data type: {matrix.dtype}&quot;)
print(f&quot;Size: {matrix.size} (total elements)&quot;)
</code></pre>
<pre><code>Array:
[[1 2 3]
 [4 5 6]
 [7 8 9]]
Shape: (3, 3) (rows, columns)
Dimensions: 2D
Data type: int64
Size: 9 (total elements)
</code></pre>
<h4>3D array</h4>
<pre><code class="language-python">array_3d = np.array([[[1, 2], [3, 4]], [[5, 6], [7, 8]]])
print(f&quot;Array:\n{array_3d}&quot;)
print(f&quot;Shape: {array_3d.shape} (depth, rows, columns)&quot;)
print(f&quot;Dimensions: {array_3d.ndim}D&quot;)
print(f&quot;Data type: {array_3d.dtype}&quot;)
print(f&quot;Size: {array_3d.size} (total elements)&quot;)
</code></pre>
<pre><code>Array:
[[[1 2]
  [3 4]]

 [[5 6]
  [7 8]]]
Shape: (2, 2, 2) (depth, rows, columns)
Dimensions: 3D
Data type: int64
Size: 8 (total elements)
</code></pre>
<h4>Creating array from list</h4>
<pre><code class="language-python">list_1d = [1, 2, 3, 4, 5]
list_2d = [[1, 2, 3], [4, 5, 6]]
arr_from_list_1d = np.array(list_1d)
arr_from_list_2d = np.array(list_2d)
print(f&quot;1D list {list_1d} → Array: {arr_from_list_1d}&quot;)
print(f&quot;2D list {list_2d} → Array:\n{arr_from_list_2d}&quot;)
</code></pre>
<pre><code>1D list [1, 2, 3, 4, 5] → Array: [1 2 3 4 5]
2D list [[1, 2, 3], [4, 5, 6]] → Array:
[[1 2 3]
 [4 5 6]]
</code></pre>
<h4>Homogenous data type</h4>
<p><code>ndarray</code> stores the same data type. If needed types will be converted.</p>
<pre><code class="language-python">mixed_list = [1, 2, 3.5, 4]  # Mix of int and float
homogeneous_array = np.array(mixed_list)
print(f&quot;Mixed list: {mixed_list}&quot;)
print(f&quot;NumPy array: {homogeneous_array}&quot;)
print(f&quot;Data type: {homogeneous_array.dtype} (all converted to float)&quot;)
</code></pre>
<pre><code>Mixed list: [1, 2, 3.5, 4]
NumPy array: [1.  2.  3.5 4. ]
Data type: float64 (all converted to float)
</code></pre>
<h3>Broadcasting</h3>
<p><strong>What is broadcasting?</strong> NumPy allows using arrays of different shapes in arithmetic operations thanks to broadcasting rules, which automatically handle shape mismatches. Broadcasting is a powerful mechanism that lets NumPy work with arrays of different shapes when performing operations, making code more concise and readable.</p>
<p>Let&#39;s see this in action with automotive examples:</p>
<h4>Example 1: Adding a calibration offset to all speed readings</h4>
<pre><code class="language-python">import numpy as np

# Speed readings from GPS (mph) - 2D array with multiple trips
speeds = np.array([[30, 45, 60],
                   [40, 55, 70],
                   [35, 50, 65]])

# Calibration offset: GPS is reading 2 mph too low
calibration_offset = 2

print(f&quot;Speed readings (mph):\n{speeds}&quot;)
print(f&quot;\nCalibration offset: +{calibration_offset} mph&quot;)
print(f&quot;\nAdjusted speeds:\n{speeds + calibration_offset}&quot;)
print(&quot;→ The scalar (2) is &#39;broadcast&#39; to every element!&quot;)
</code></pre>
<pre><code>Speed readings (mph):
[[30 45 60]
 [40 55 70]
 [35 50 65]]

Calibration offset: +2 mph

Adjusted speeds:
[[32 47 62]
 [42 57 72]
 [37 52 67]]
→ The scalar (2) is &#39;broadcast&#39; to every element!
</code></pre>
<h4>Example 2: Applying different adjustments per column</h4>
<pre><code class="language-python">speeds = np.array([[30, 45, 60],
                   [40, 55, 70],
                   [35, 50, 65]])

# Different calibration offsets
trip_adjustments = np.array([2, 3, 1])

print(f&quot;Speed readings (mph):\n{speeds}&quot;)
print(f&quot;\nTrip adjustments: {trip_adjustments}&quot;)
print(f&quot;\nAdjusted speeds:\n{speeds + trip_adjustments}&quot;)
</code></pre>
<pre><code>Speed readings (mph):
[[30 45 60]
 [40 55 70]
 [35 50 65]]

Trip adjustments: [2 3 1]

Adjusted speeds:
[[32 48 61]
 [42 58 71]
 [37 53 66]]
</code></pre>
<h4>Example 3: Applying adjustments per row</h4>
<pre><code class="language-python">speeds = np.array([[30, 45, 60],
                   [40, 55, 70]])

time_adjustments = np.array([[1],
                             [2]])

print(f&quot;Speed readings (mph):\n{speeds}&quot;)
print(f&quot;\nTime-based adjustments (column vector):\n{time_adjustments}&quot;)
print(f&quot;\nAdjusted speeds:\n{speeds + time_adjustments}&quot;)
</code></pre>
<pre><code>Speed readings (mph):
[[30 45 60]
 [40 55 70]]

Time-based adjustments (column vector):
[[1]
 [2]]

Adjusted speeds:
[[31 46 61]
 [42 57 72]]
</code></pre>
<h3>Random Number Generation</h3>
<p>NumPy provides functions for generating random numbers, probability distributions, and random sampling.</p>
<pre><code class="language-python"># Simulate random speed readings (mph)
speeds = np.random.randint(30, 90, size=10)  # Random speeds between 30-90 mph
print(f&quot;Random speeds (mph): {speeds}&quot;)
</code></pre>
<pre><code>Random speeds (mph): [53 82 65 69 53 32 51 82 31 53]
</code></pre>
<pre><code class="language-python"># Simulate speed readings with realistic distribution
realistic_speeds = np.random.normal(loc=60, scale=15, size=10)  # Mean 60, std 15
print(f&quot;\nRealistic speeds (normal distribution): {realistic_speeds}&quot;)
print(f&quot;Mean: {realistic_speeds.mean():.1f} mph, Std: {realistic_speeds.std():.1f} mph&quot;)
</code></pre>
<pre><code>Realistic speeds (normal distribution): [52.95788421 68.13840065 53.04873461 53.0140537  63.62943407 31.30079633
 34.12623251 51.56568706 44.80753319 64.71370999]
Mean: 51.7 mph, Std: 11.7 mph
</code></pre>
<pre><code class="language-python"># Simulate fuel consumption (L/100km) - uniform distribution
fuel_consumption = np.random.uniform(low=6.0, high=10.0, size=5)
print(f&quot;Simulated fuel consumption (L/100km): {fuel_consumption}&quot;)
print(f&quot;Average: {fuel_consumption.mean():.2f} L/100km&quot;)
</code></pre>
<pre><code>Simulated fuel consumption (L/100km): [7.16857859 7.46544737 7.82427994 9.14070385 6.79869513]
Average: 7.68 L/100km
</code></pre>
<pre><code class="language-python"># Simulate RPM readings (1000-7000 RPM)
rpm_readings = np.random.randint(1000, 7000, size=5)
print(f&quot;Random RPM readings: {rpm_readings}&quot;)
</code></pre>
<pre><code>Random RPM readings: [6390 6226 6191 4772 4092]
</code></pre>
<pre><code class="language-python"># Simulate RPM with normal distribution (typical cruising RPM)
cruising_rpm = np.random.normal(loc=2500, scale=500, size=5)
print(f&quot;\nCruising RPM (normal distribution): {cruising_rpm}&quot;)
print(f&quot;Mean: {cruising_rpm.mean():.0f} RPM&quot;)
</code></pre>
<pre><code>Cruising RPM (normal distribution): [2382.92331264 2382.93152153 3289.60640775 2883.71736458 2265.26280703]
Mean: 2641 RPM
</code></pre>
<pre><code class="language-python"># Simulate selecting random trips for analysis
trip_ids = [&#39;Trip 1&#39;, &#39;Trip 2&#39;, &#39;Trip 3&#39;, &#39;Trip 4&#39;, &#39;Trip 5&#39;]
selected_trips = np.random.choice(trip_ids, size=3, replace=False)
print(f&quot;Available trips: {trip_ids}&quot;)
print(f&quot;Randomly selected trips: {selected_trips}&quot;)
</code></pre>
<pre><code>Available trips: [&#39;Trip 1&#39;, &#39;Trip 2&#39;, &#39;Trip 3&#39;, &#39;Trip 4&#39;, &#39;Trip 5&#39;]
Randomly selected trips: [&#39;Trip 3&#39; &#39;Trip 2&#39; &#39;Trip 5&#39;]
</code></pre>
<pre><code class="language-python">print(&quot;Setting seed ensures reproducible results (important for testing!):&quot;)
np.random.seed(42)
speed1 = np.random.randint(30, 90, size=3)
print(f&quot;With seed(42): {speed1}&quot;)

np.random.seed(42)
speed2 = np.random.randint(30, 90, size=3)
print(f&quot;With seed(42) again: {speed2}&quot;)
print(&quot;→ Same seed produces same random numbers!&quot;)
</code></pre>
<pre><code>Setting seed ensures reproducible results (important for testing!):
With seed(42): [68 81 58]
With seed(42) again: [68 81 58]
→ Same seed produces same random numbers!
</code></pre>
<hr>
<h2>Common Mistakes</h2>
<p>Here are some common mistakes beginners make when starting with NumPy:</p>
<h3>Mistake 1: Forgetting to convert to NumPy array</h3>
<pre><code class="language-python">my_list = [10, 20, 30]
print(f&quot;Python list: {my_list}&quot;)
print(f&quot;my_list * 2 → {my_list * 2}&quot;)  # Duplicates the list!
print(&quot;⚠️ Python lists multiply by duplicating, not by element!&quot;)

my_array = np.array([10, 20, 30])
print(f&quot;\nNumPy array: {my_array}&quot;)
print(f&quot;my_array * 2 → {my_array * 2}&quot;)  # Multiplies each element!
print(&quot;✅ NumPy arrays multiply each element&quot;)
</code></pre>
<pre><code>Python list: [10, 20, 30]
my_list * 2 → [10, 20, 30, 10, 20, 30]
⚠️ Python lists multiply by duplicating, not by element!

NumPy array: [10 20 30]
my_array * 2 → [20 40 60]
✅ NumPy arrays multiply each element
</code></pre>
<h3>Mistake 2: Mixing up array dimensions</h3>
<pre><code class="language-python"># 1D array (like a row of data)
array_1d = np.array([10, 20, 30])
print(f&quot;1D array: {array_1d}&quot;)
print(f&quot;Shape: {array_1d.shape}&quot;)

# 2D array with 1 row
array_2d_row = np.array([[10, 20, 30]])
print(f&quot;\n2D array with 1 row: {array_2d_row}&quot;)
print(f&quot;Shape: {array_2d_row.shape}&quot;)

# 2D array with 1 column
array_2d_col = np.array([[10], [20], [30]])
print(f&quot;\n2D array with 1 column:\n{array_2d_col}&quot;)
print(f&quot;Shape: {array_2d_col.shape}&quot;)
</code></pre>
<pre><code>1D array: [10 20 30]
Shape: (3,)

2D array with 1 row: [[10 20 30]]
Shape: (1, 3)

2D array with 1 column:
[[10]
 [20]
 [30]]
Shape: (3, 1)
</code></pre>
<h1>Mistake 3: Not understanding data type</h1>
<pre><code class="language-python"># Mixing integers and floats
mixed_list = [10, 20, 30.5, 40]
print(f&quot;Mixed list: {mixed_list}&quot;)
mixed_array = np.array(mixed_list)
print(f&quot;NumPy array: {mixed_array}&quot;)
print(f&quot;Data type: {mixed_array.dtype}&quot;)
print(&quot;⚠️ NumPy converts all to same type (float in this case)&quot;)

# All integers stay integers
int_list = [10, 20, 30, 40]
int_array = np.array(int_list)
print(f&quot;\nInteger list: {int_list}&quot;)
print(f&quot;NumPy array: {int_array}&quot;)
print(f&quot;Data type: {int_array.dtype}&quot;)
print(&quot;✅ All integers stay integers&quot;)
</code></pre>
<pre><code>Mixed list: [10, 20, 30.5, 40]
NumPy array: [10.  20.  30.5 40. ]
Data type: float64
⚠️ NumPy converts all to same type (float in this case)

Integer list: [10, 20, 30, 40]
NumPy array: [10 20 30 40]
Data type: int64
✅ All integers stay integers
</code></pre>
<h2>Summary</h2>
<p>In this notebook, we&#39;ve covered some essential and practical aspects of using NumPy in Python:</p>
<ul>
<li>How to create arrays and the differences between Python lists and NumPy arrays</li>
<li>Common mistakes, including operating on Python lists vs NumPy arrays, mixing data types, and misunderstanding array shapes/dimensions</li>
<li>Useful attributes such as <code>.shape</code> and <code>.dtype</code></li>
<li>Conversion between 1D and 2D arrays and understanding reshape behavior</li>
</ul>
<p>I spent approximatelly 1 hour every day in a week to learn, play, expermiment and write this notebook. If I did it, you can too!</p>
<h2>Topics for Further Learning</h2>
<p>If you wish to deepen your NumPy skills, consider exploring the following:</p>
<ul>
<li>NumPy array broadcasting rules and how they apply to arithmetic operations</li>
<li>Advanced indexing and slicing techniques (fancy indexing, boolean indexing)</li>
<li>Vectorized computations for performance improvements</li>
<li>Useful NumPy functions: <code>np.dot</code>, <code>np.linalg</code>, <code>np.random</code></li>
<li>Memory layout and the difference between <code>np.copy()</code> and view/reference semantics</li>
<li>Integrating NumPy with pandas and plotting libraries like matplotlib</li>
<li>Working with higher-dimensional arrays and practical applications in scientific computing</li>
</ul>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Configuring AI coding tools is a new bike-shedding]]></title>
            <description><![CDATA[Developers used to laugh at those who spent hours configuring Neovim instead of simply using JetBrains (or another VS Code) and writing code. Now, modern developers spend hours selecting and configuring AI coding tools, then pat themselves on the back for creating a prompt, which is supposedly great.]]></description>
            <link>https://frodigo.com/Blog/2025/Configuring+AI+coding+tools+is+a+new+bike-shedding</link>
            <guid isPermaLink="false">https://frodigo.com/Blog/2025/Configuring+AI+coding+tools+is+a+new+bike-shedding</guid>
            <pubDate>Fri, 24 Oct 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Developers used to laugh at those who spent hours configuring Neovim instead of simply using JetBrains (or another VS Code) and writing code. Now, modern developers spend hours selecting and configuring AI coding tools, then pat themselves on the back for creating a prompt, which is supposedly great.</p>
<p>Would Cursor be better for my work? Or Wind Surf. No, man, try Claude Code. Which model? Sonnet 3.7 or 4? I’d suggest GPT-4 or 5. No, no, use GPT-5 for writing CSS, and then Sonnet-4 for writing JavaScript.</p>
<p>Don’t forget about .cursorrules and claude.md. You need to write a ton of documentation so your agents know how to do their jobs. You need to create separate agents for the frontend and backend, and even break them down further to make them work well for you.</p>
<p>The prompt can’t be too large or too small. It has to be just right. You need to test different prompts, in various tasks and with other models, and figure out which one works best when. It’s best to create an Excel prompt library.</p>
<p>A new version of the tool came out, and it stopped working. Oh well, experiment again. You’ll surely find the best way to explain to the bot how to write the code for you.</p>
<p>That’s how I see it, working with AI tools. I’ve been through all of this myself, having tried it from the very beginning. Isn’t that pathological? Isn’t it analogous to those nerds who spent an hour configuring Neo Vim?</p>
<p>Devs say they feel productive and work faster, but is that really the case? The illusion of productivity is certainly compelling, and using these tools can make you feel like magic is happening.</p>
<h2>Productivity illusion</h2>
<p>Developers feel productive when they configure these tools. They feel like they’re riding a wave and doing something super good. I’ve done it myself. Anyone who wants to try AI coding tools has to do it, because in real life, these tools don’t help without all the machinery I described above.</p>
<p>The problem with configuring this tool is that it’s not something you can finish. You’ll spend hours configuring it, but in reality, you can tell the AI ​​to do the same task five times and it’ll do it differently each time. Or you’ll make a tool update, and what you thought worked no longer works.</p>
<p>But configuration and improvement give you a boost. You feel better. You’re working on something that’s mega-popular right now, so you feel satisfied. You can even give presentations and share your knowledge.
In the long run, you’ll deliver just as many features as without it. No one can really measure its actual impact on productivity yet.</p>
<p>We can measure it through the hype of X and YouTube, and we do, which only reinforces the false narrative.</p>
<h2>We measure the wrong things</h2>
<p>A developer sees that an AI tool has generated a ton of code and a dozen or so files and is delighted. Does more lines of code, more files mean better? It should be the other way around: the less code you write to solve a problem, the better for you and others.</p>
<p>AI generates a ton of code, and you can feel the vibe and accept it, but I deeply believe that serious developers who use agents and prompts to generate code, review it, improve it, and refactor it.</p>
<p>It takes time. Sometimes it’s actually faster to refactor something using prompts, but not always. You feel 10x faster, but maybe you’re actually 20–30% faster — we don’t really know how to measure that.</p>
<p>Another thing is that writing code has never been the biggest challenge in software development. It’s much more difficult, for example, to understand what the Product Owner means when presenting a new design.</p>
<p>Or debugging a bug in a distributed architecture, when, at best, it takes you a few hours to find a way to reproduce the problem.</p>
<p>Creating new code is only a small part of everyday software development work. Unfortunately, we have focused so much on optimizing code generation and adding new features that we have forgotten about it, leading us into a vicious cycle of investing time in optimization.</p>
<h2>This is a sandcastle</h2>
<p>Referring back to configuring NeoVim. You know, I started using NeoVim about six months ago. I struggled terribly at first, but once I configured it to my liking, it worked for me, and I didn’t have to change many things later. Configuring AI coding tools is quite the opposite.</p>
<p>Every few months, new models are released, and prompts that worked perfectly yesterday might be fit for the trash today. There are more and more tools, and thanks to powerful marketing, we’re bombarded from every side with gibberish about the superiority of tool A over tool B.</p>
<p>You configure something that works now. In three months, you’ll have to configure it differently. You learn techniques that will be forgotten next year, and you’ll be learning even more. This investment doesn’t pay off.
You pay more for it than you think.</p>
<p>The real cost isn’t just wasted time, but also avoiding difficult work and learning technologies that will truly benefit you when you want to become an expert.</p>
<p>These tools won’t help you when you have a huge codebase and need to fix edge cases. They won’t help you discuss a new feature with the Product Owner or understand another developer’s intentions. They won’t resolve conflicts within the team or conduct a security audit. Those are the complex parts of our daily work, not code generation.</p>
<p>By spending time forcing technology to write code for you, you’re doing something relatively technical and easy, and it’s a form of “productive procrastination.”</p>
<h2>Conclusion</h2>
<p>Living and working in the AI ​​bubble, we need more data and less hype. Each of us needs to experiment and find a way to determine whether something works. Just realizing how much time goes into configuring this technology is eye-opening.</p>
<p>You also need to find a balance in using these tools. You don’t have to use them 100% of the time. Sometimes you just write the damn function yourself. NeoVim users configured it to be better at crafting. We configure AI tools to avoid crafting.</p>
<hr>
<p>#blog #SoftwareEngineering #ArtificialIntelligence #AI #AISkills #Programming #Productivity #AiCoding</p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Omarchy review after a month of using]]></title>
            <description><![CDATA[A month ago, I became the owner of a <em>Framework</em> Laptop 12 and needed to install an operating system on it. I chose Omarchy, and in this article I wanted to describe my experiences with it over the past few weeks.]]></description>
            <link>https://frodigo.com/Blog/2025/Omarchy+review+after+a+month+of+using</link>
            <guid isPermaLink="false">https://frodigo.com/Blog/2025/Omarchy+review+after+a+month+of+using</guid>
            <pubDate>Fri, 17 Oct 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>A month ago, I became the owner of a <em>Framework</em> Laptop 12 and needed to install an operating system on it. I chose Omarchy, and in this article I wanted to describe my experiences with it over the past few weeks.</p>
<h2>Why Omarchy</h2>
<p>On my desktop computer, I use Pop OS, and on my company computer, <em>Ubuntu</em>. Initially, I also wanted to install Ubuntu on my new laptop so I could have a similar system everywhere. I learned about Omarchy by accident. A friend sent me a <a href="https://youtu.be/gcwzWzC7gUA?si=OFmgDrREa3eesvPo">https://youtu.be/gcwzWzC7gUA?si=OFmgDrREa3eesvPo</a> in which <strong>DHH</strong> showed how to install Omarchy and what it is.</p>
<p>Later I watched <a href="https://youtu.be/TcHY0AEd2Uw?si=omIh2Y6188X2w4yq">https://youtu.be/TcHY0AEd2Uw?si=omIh2Y6188X2w4yq</a> from official <a href="https://omarchy.org">https://omarchy.org</a> website and I was so impressed and decided to give it a try.</p>
<p>Basically, I didn&#39;t believe that it is possible to install something based on Arch Linux in a few minutes and it can actually work.</p>
<p>Until now, I&#39;d only had experience with Ubuntu and was satisfied. What prompted me to try Omarchy was the many features preinstalled and prepared for web development, as well as <em>Hyprlnd</em> and <em>Tiling Windows.</em></p>
<h2>Installation</h2>
<p>I&#39;d heard that installing <em>Arch Linux</em> was incredibly difficult. I&#39;d also seen DHH install Omarchy on an instance in minutes, and it worked, even though Omarchy is built on Arch. I said, &quot;I&#39;ll check it out,&quot; downloaded the Omarchy ISO image, burned it to a USB flash drive, and ran Framework 12 on my laptop.</p>
<p>I was very positively surprised because everything worked the first time and I saw the beautiful Omarchy desktop:</p>
<p><img src="x/images/omarchy/Pasted%20image%2020251009194547.png" alt="Omarchy"></p>
<p>I thought, cool, but I definitely won&#39;t be able to connect to Wi-Fi... Well, it worked the first time, amazing!</p>
<p><img src="x/images/omarchy/Pasted%20image%2020251009194726.png" alt="Omarchy"></p>
<p>BTW, you can see that the name of one of my Wi-Fi networks is &quot;ReactJS&quot;; it&#39;s from when I worked primarily as a frontend developer and I actually liked React. Nowadays, I don&#39;t like React as much as I did then, but I am too lazy to change the name of the network.</p>
<h2>First feelings</h2>
<p>When it comes to aesthetics, I&#39;m a bit of a weird person. On the one hand, I like simplicity, which is why I feel so comfortable in the Terminal. On the other hand, I&#39;m also drawn to all sorts of graphical flourishes.</p>
<p>The Windows look is awful, I can&#39;t stand it. Mac OS looks better, but you have to ask Apple for permission to do anything, which is annoying.</p>
<p>And Omarchy is nice, it&#39;s beautiful. It&#39;s functional. It combines two worlds – TUI apps and Web Apps. DHH mentioned this in his talk, and I buy it:</p>
<p><img src="x/images/omarchy/Pasted%20image%2020251009201106.png" alt="Omarchy">
On one side, Neovim in Terminal, full of old-school features, and on the other, Spotify, a modern invention. These two apps can be open side by side. Omarchy allows you to define your own apps, including native apps, web apps, and TUI apps.</p>
<p>Another example: on the left, Lazy Docker as a TUI app, and on the right, my website as a web app:</p>
<p><img src="x/images/omarchy/Pasted%20image%2020251009202000.png" alt="Omarchy"></p>
<h2>Default apps</h2>
<p>Omarchy comes with a ton of preinstalled software, especially for web developers. So immediately after install, I could use Neovim, Alactricity, and Docker. But not only apps for development. You can also find other useful apps installed by default, like Spotify, Signal, and Obsidian. On the other hand, I found apps that I don’t want to use, such as 1Password. The only minor issue is that you get a lot of stuff installed—some of which you might not like. It&#39;s not a big deal because you can easily uninstall it (which I&#39;ll talk about later).</p>
<h2>Keybindings</h2>
<p>Omarchy focuses on using the keyboard as often as possible and provides several useful Key Bindings. SUPER key + Enter opens the terminal, SUPER + number from 1 to 5 switches the workspace. You can open many web and TUI apps using key bindings like SUPER + B, which opens the Browser. Key bindings are well documented. By pressing SUPER + K, you can see instant keybinding documentation:
<img src="x/images/omarchy/Pasted%20image%2020251016091143.png" alt="Omarchy">
You can also see them, edit, and add new bindings in the <code>~/.config/hypr/nindings.conf</code> file:</p>
<p><img src="x/images/omarchy/Pasted%20image%2020251016091357.png" alt="Omarchy"></p>
<h2>Tilling windows</h2>
<p>Tilling windows automatically arrange themselves in a grid or mosaic pattern on your screen, rather than overlapping freely. Instead of windows floating around independently, they tile to fill the available space without overlapping.</p>
<p><img src="x/images/omarchy/Pasted%20image%2020251016091751.png" alt="Omarchy"></p>
<p>After just five minutes of using tiling windows, I knew I&#39;d never go back to the &quot;normal&quot; windows I was used to from Ubuntu, <em>Windows</em>, or <em>Mac</em>. Thanks to Tilling Windows, I can manage my windows and feel more productive.</p>
<p>I can have multiple windows open simultaneously and keep them well-organized. When needed, I can enlarge the active window to full screen size by pressing F11. I can also move the selected window to a new workspace with SUPER + SHIFT + workspace number.</p>
<h2>Speed</h2>
<p>I won&#39;t go into too much detail about the system&#39;s speed: on a laptop with an Intel i5, 32GB RAM, it works like Max Verstappen in a Formula 1 car.</p>
<p>Apart from that, I&#39;ve never had anything freeze, and on my Ubuntu computer, it happens quite often (even several times a week)</p>
<h2>Daily usage</h2>
<p>The system is stable; I haven&#39;t had a crash yet. What I usually do is development in Python and Django. So, I have a database open in Docker, a development server running, Neovim, a few terminals, Spotify, Signal, and other such trivia.
<img src="x/images/omarchy/Pasted%20image%2020251010153456.png" alt="Omarchy"></p>
<p>Besides, sometimes I just write (like now), I use Obsidian and a browser for research. It works very well for me, so I&#39;d give it a 10/10. But it can&#39;t be that great. There&#39;s one thing that really annoys me. I&#39;m talking about the display settings depending on whether I&#39;m working on my laptop or an external screen. Another thing that annoys me, but not terribly, is the occasional problem with installing packages.</p>
<h2>Monitors issue</h2>
<p>The first unpleasant thing is that every time I connect my laptop to an external monitor, I have to change the settings in <code>.config/hypr/monitors.conf</code>. If I don&#39;t do this, everything is too small on the external monitor. Similarly, when I disconnect the laptop from the monitor and want to work on the laptop screen, I have to change the monitor settings, because otherwise, everything on the screen is too large.</p>
<p>The second problem is that when I have my laptop connected to my computer, the workspaces don&#39;t function correctly. The thing is, you have five workspaces, and logic tells me that there should be separate workspaces for the external monitor and the laptop screen.</p>
<p>But the workspaces are shared, and it can get confusing. For example, you&#39;re on workspace 1 and you see something different on the laptop, such as a browser, and something else on the external monitor, such as a terminal.</p>
<p>When you switch workspaces, the changes are reflected on one screen, while the other screen remains unchanged.</p>
<p>De facto, it works like this: you have five workspaces on one screen and one on the other.</p>
<h2>Problem with installing packages e.g. firefox</h2>
<p>I once ran into a problem where I couldn&#39;t install a package from the Omarcha repositories. Fortunately, I could do it using the AUR, so it wasn&#39;t a big deal.</p>
<p><img src="x/images/omarchy/Pasted%20image%2020251006071705.png" alt="Omarchy"></p>
<h2>For whom Omarchy</h2>
<p>As a car enthusiast, I can say that Omarchy is comparable to an Alfa Romeo. When everything works, you&#39;re thrilled, but when something starts breaking, you start to get annoyed. Fortunately, I didn&#39;t experience any major problems at first, so my experience has been very positive. I&#39;d love to write a post next year about &quot;Omarchy after a year of using it.&quot; In that time, I have a lot to catch up on and learn, especially about Arch Linux and Hyprl.</p>
<h2>Summary</h2>
<p>So, yes, I plan to stick with this distro! It&#39;s the perfect distro for web developers and programmers. In my opinion, it&#39;s for people who already have Linux experience. If you&#39;re just starting out and migrating from Windows or Mac, I suggest starting with Omakub. It&#39;s a different distro from DHH, built on Ubuntu. It might be easier to get to grips with when starting out in the Linux world.</p>
<p>The biggest advantage of Omarchy is its appearance and functionality. It provides significant benefits in terms of work ergonomics. It provides default apps and setup for development. I would rate it 10/10, but due to an annoying issue with monitors, I need to be more strict and give it, let&#39;s say, 9!</p>
<hr>
<p><em>Published: 10/17/2025</em> #blog #linux #omarchy</p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Setting up Nextcloud on Raspberry Pi 5 using Docker]]></title>
            <description><![CDATA[My goal was simple – to move photos from Google Photos and Apple Photos &quot;to my own server.&quot; I decided to try NextCloud and installed it using NextCloudPI, a custom-built distribution for Raspberry Pi.]]></description>
            <link>https://frodigo.com/Blog/2025/Setting+up+Nextcloud+on+Raspberry+Pi+5+using+Docker</link>
            <guid isPermaLink="false">https://frodigo.com/Blog/2025/Setting+up+Nextcloud+on+Raspberry+Pi+5+using+Docker</guid>
            <pubDate>Wed, 24 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>My goal was simple – to move photos from Google Photos and Apple Photos &quot;to my own server.&quot; I decided to try NextCloud and installed it using NextCloudPI, a custom-built distribution for Raspberry Pi.</p>
<p>I purchased a brand new Raspberry Pi 5, a cooler, and an external USB drive. I also needed to run an Ethernet cable to my <del>office</del> garage, which I should have done a long time ago anyway.</p>
<h2>NextCloudPi. It should work, but it shouldn&#39;t work for me</h2>
<p>My initial attempts to launch NextCloud were unsuccessful. I couldn&#39;t even get past the first launch. Here are the errors I encountered:</p>
<pre><code class="language-bash">[FAILED] failed to start avahi-deamon.service - Avahi mDNS/DNS-SD stack
run `systemctl status avahi-daemon.service` for details

[FAILED] failed to start systemd-logind.service - User login management

[failed] failed to start bluetooth.service -

[failed] failed to start udisk2.service - disk manager

[failed] failed to start dbus.service - D-Bus System message bus
</code></pre>
<p>I tried to fix them, but I couldn&#39;t even log in because the system never finished the first boot. It was frustrating. At least as frustrating as Dembele winning the Ballon d&#39;Or (okay, a little less so).</p>
<p>Imagine that even a restart didn&#39;t help. Nor did turning it off and on. Interestingly, I downloaded an older version of NextCloudPi, but I still had no success. I tried two versions: v1.55.3 and v1.55.2.</p>
<p>It&#39;s open-source software, developed by the community. No one said it would work, and I don&#39;t blame anyone for it not working. Perhaps there was something wrong with my configuration. I didn&#39;t pursue the issue further; I just tried a different approach.</p>
<h2>NextCloud on Docker. It must work</h2>
<p>I did a small research and I have found this guide:  <a href="https://help.nextcloud.com/t/guide-setting-up-nextcloud-on-raspberry-pi-ssl-advanced-app-support-high-performance-postgresql-and-cloudflare-zero-trust-integration/185757]">https://help.nextcloud.com/t/guide-setting-up-nextcloud-on-raspberry-pi-ssl-advanced-app-support-high-performance-postgresql-and-cloudflare-zero-trust-integration/185757]</a>. It was good as a starting point.</p>
<p>But it couldn&#39;t just work, right?</p>
<h3>&quot;Data Directory Invalid&quot; Error</h3>
<p>I initially ran NextCloud on an SD card, but ultimately wanted to store the data on an external SSD. I achieved this by setting the <code>datadirectory</code> as a Docker volume, mounted on an external drive.</p>
<pre><code class="language-bash">/media/frodigo/nextcloud-data:/var/www/html/data:z
</code></pre>
<p>After restarting NextCloud I saw this error:</p>
<pre><code class="language-bash">Data directory is invalid.
Make sure there is a file called &quot;.ncdata&quot; in the root of the data directory.
The data directory is not writable.
</code></pre>
<p>I did a research and I have realized that Nextcloud requires a specific marker file <code>.ncdata</code> in the data directory root to validate the directory structure.</p>
<p>I checked if the file exist, and It didn&#39;t exist so I created a new one:</p>
<pre><code class="language-bash"># Check if the file exists
ls -la /media/frodigo/nextcloud-data/.ncdata

# Create if missing
echo &quot;# Nextcloud data directory&quot; &gt; /media/frodigo/nextcloud-data/.ncdata
</code></pre>
<p>However, that didn&#39;t completely resolve the issue. I moved forward a bit, but then saw another error saying that the data directory is not writable.</p>
<h3>User ID Mismatches</h3>
<p>Initially, I set permissions on the data directory so that the <code>www-data</code> user could access it. To my surprise, it turned out that the <code>www-data</code> user ID on the host was different from the <code>www-data</code> user ID in the container, which is why I was experiencing problems.</p>
<ul>
<li>Host system <code>www-data</code> user: UID 33</li>
<li>Container <code>www-data</code> user: UID 82</li>
</ul>
<p>The solution for this problem was to align file ownership with the container&#39;s expectations:</p>
<pre><code class="language-bash"># Check what UID www-data has inside the container
docker exec -it nextcloud id www-data

# Set ownership to match container UID
sudo chown -R 82:82 /path/to/nextcloud-data/
sudo chmod -R 755 /path/to/nextcloud-data/
</code></pre>
<h4>Troubleshooting workflow</h4>
<p>When facing data directory issues:</p>
<ol>
<li><strong>Verify the external drive is mounted</strong>: <code>df -h</code></li>
<li><strong>Check Docker volume mappings</strong>: Review your <code>docker-compose.yml</code></li>
<li><strong>Inspect file permissions</strong>: <code>ls -la /path/to/data/</code></li>
<li><strong>Check container user IDs</strong>: <code>docker exec -it container_name id www-data</code></li>
<li><strong>Verify .ncdata file exists and is readable</strong></li>
<li><strong>Restart containers after making changes</strong>: <code>docker-compose down &amp;&amp; docker-compose up -d</code></li>
<li><strong>Debug container user context</strong>: <code>docker-compose exec -u www-data nextcloud touch /var/www/html/data/test.txt</code></li>
</ol>
<p>Note: Docker introduces multiple abstraction layers that can mask the root cause of issues. The key to successful troubleshooting is understanding these layers and testing each one systematically. Always verify your assumptions at each level:</p>
<ul>
<li>network connectivity</li>
<li>volume mounting</li>
<li>file permissions,</li>
<li>application configuration</li>
</ul>
<h2>It works. Somehow</h2>
<p>After overcoming these setbacks, my NextCloud on Pi started working, but it seems this is more the beginning of an adventure than the end. The rest of my adventures might come in the near future, if I don&#39;t give up. We&#39;ll see.</p>
<hr>
<p><em>Published: 24/09/2025</em> #blog #nectcloud #raspberrypi #diy #cloud #docker</p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Why I build my own RSS feed for Obsidian Publish website]]></title>
            <description><![CDATA[Obsidian and Obsidian Publish are great tools and they allow you to write and publish content like a nitro. I couldn&#39;t imagine what can go wrong when using Obsidian publish. Then after a few weeks I realized what is the weak point that i haven&#39;t seen before: <strong>RSS feed</strong>.]]></description>
            <link>https://frodigo.com/Blog/2025/Why+I+built+my+own+RSS+feed+generator+for+the+Obsidian+Publish+website</link>
            <guid isPermaLink="false">https://frodigo.com/Blog/2025/Why+I+built+my+own+RSS+feed+generator+for+the+Obsidian+Publish+website</guid>
            <pubDate>Thu, 17 Apr 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Obsidian and Obsidian Publish are great tools and they allow you to write and publish content like a nitro. I couldn&#39;t imagine what can go wrong when using Obsidian publish. Then after a few weeks I realized what is the weak point that i haven&#39;t seen before: <strong>RSS feed</strong>.</p>
<p>Autogenerated RSS feed on Obsidian publish is really simple. I realized it when I talked with my colleague and he asked me about RSS feed. I shared a link with him. He added it to his RSS reader. I saw it. It looked like something hastily written. Just titles, nothing. I felt a bit ashamed.</p>
<h1>TL;DR</h1>
<ul>
<li>Obsidian Publish&#39;s default RSS feed only includes titles and links - no content or descriptions</li>
<li>After sharing my RSS feed with a colleague, I felt motivated to create a better one</li>
<li>Built a custom RSS generator that converts Markdown files to feeds with full content</li>
<li>Implemented special handling for Obsidian&#39;s wiki-style links to maintain proper formatting</li>
<li>Deployed the feeds to Cloudflare Pages with a custom subdomain</li>
<li>Created two versions: one with all content and one with just recent posts</li>
<li>Future improvements include code refactoring, category-specific feeds, and simpler hosting via GitHub</li>
</ul>
<hr>
<p>I investigated why quality of my feed was so bad. It was because in feed generated by Obsidian, there are only two fields for each item: title of an article and link to it.</p>
<pre><code class="language-xml">&lt;item&gt;
	&lt;title&gt;Historical cryptography algorithms&lt;/title&gt;

	&lt;link&gt;
https://frodigo.com/Blog/2025/Historical+cryptography+algorithms
	&lt;/link&gt;
&lt;/item&gt;
</code></pre>
<p>So it is not very usable, because someone who uses RSS feeds can only see titles and use link to go to the website.</p>
<p>But what I like the most when I use RSS feeds is to have possibility to read content without need to go outside.</p>
<p>So for me proper RSS feed item looks like this:</p>
<pre><code class="language-xml">&lt;item&gt;
	&lt;title&gt;
		&lt;![CDATA[ Historical cryptography algorithms ]]&gt;
	&lt;/title&gt;

	&lt;description&gt;
		# short description like first paragraph
	&lt;/description&gt;

	&lt;link&gt;
https://frodigo.com/Blog/2025/Historical+cryptography+algorithms
	&lt;/link&gt;

	&lt;guid isPermaLink=&quot;false&quot;&gt;
	https://frodigo.com/Blog/2025/Historical+cryptography+algorithms
	&lt;/guid&gt;

	&lt;pubDate&gt;Fri, 28 Mar 2025 00:00:00 GMT&lt;/pubDate&gt;

	&lt;content:encoded&gt;
		# full content here so I can read it in my RSS reader
	&lt;/content:encoded&gt;

&lt;/item&gt;
</code></pre>
<p>In this example you can see that there is description field that shows something like excerpt. Also there is a content field which contains a full content of an article.</p>
<p>I tried to find how to implement something like this using Obsidian publish, but I didn&#39;t find a way.</p>
<p>So I started to thinking how to implement it by myself.</p>
<p>And I came up with this idea:</p>
<ul>
<li>create RSS feed from my markdown files</li>
<li>deploy it somewhere</li>
<li>add link to it on my website</li>
</ul>
<hr>
<h2>Creating RSS feed</h2>
<p>In my astro blog I had a script in JS that generates RSS feed so I have used it as starting point</p>
<p>I needed to adjust it a little bit. I needed to implement support for links in Obsidian Wiki format like <code>[[link]]</code> or even <code>[[this is link|this is a text]]</code>.</p>
<p>Also I wanted to have an option to specify which content should be in feed and which not. On my website I have a few pages that can be considered as blog article (like this one). Besides, I have a lot of content that is just my Notes, and I don&#39;t want to spam people with anything that comes to mind.</p>
<p>I didn&#39;t have to much time so I used Cursor to adjust this script to my needs.</p>
<p>I was disappointment about results. Cursor was not able to implement link formatting in a acceptable way. Cursor generated hundreds of lines of code for simple feature.</p>
<p>In addition, when I asked it about writing tests, cursor started do things like a drunk programmer.</p>
<p>So I landed with an overcomplicated solution. But it works.</p>
<p>How it works:</p>
<ul>
<li><strong>Markdown in → RSS out</strong>: scans my project for dated <code>.md</code> notes and turns them into two RSS files</li>
<li><strong>Obsidian link handling</strong>: understands <code>[[Wiki Links]]</code>, cleans up internal URLs, and keeps external links unchanged.</li>
<li><strong>Auto metadata</strong>: pulls title, date, description, categories, and optional hero image from each file’s front‑matter.</li>
<li><strong>Feed hygiene</strong>: sorts by date, skips undated posts, and embeds full HTML content in <code>&lt;content:encoded&gt;</code></li>
<li><strong>Post‑build sanity check</strong>: extracts every internal link from the feed and writes them to <code>links-to-test.json</code> for automated link‑testing.</li>
</ul>
<p>This is the full implementation: <a href="https://gist.github.com/Frodigo/bbcf18db7e431cfcbb31f6fb13dd9cd6">https://gist.github.com/Frodigo/bbcf18db7e431cfcbb31f6fb13dd9cd6</a></p>
<p>Here is the most interesting part:</p>
<pre><code class="language-javascript">// --- Link‑handling magic ---
function configureMarkedRenderer() {
  const renderer = new marked.Renderer();

  renderer.link = (href, title, text) =&gt; {
    // Extract URL from href if it&#39;s an object
    const hrefStr =
      typeof href === &quot;object&quot; &amp;&amp; href !== null
        ? href.href || href.url || &quot;#&quot;
        : String(href || &quot;&quot;);

    // Extract text content if it&#39;s an object
    const textStr =
      typeof text === &quot;object&quot; &amp;&amp; text !== null
        ? text.text || text.title || hrefStr
        : String(text || hrefStr);

    // Handle wiki links
    if (hrefStr.startsWith(&quot;[[&quot;)) {
      const linkText = hrefStr.slice(2, -2);
      const url = generateUrl(null, linkText);
      return `&lt;a href=&quot;${url}&quot;&gt;${textStr}&lt;/a&gt;`;
    }

    // For regular links, extract the last part of the URL for the text and decode it
    if (hrefStr.startsWith(&quot;https://frodigo.com/&quot;)) {
      const lastPart = hrefStr.split(&quot;/&quot;).pop();
      const decodedText = decodeURIComponent(lastPart.replace(/\+/g, &quot; &quot;));
      return `&lt;a href=&quot;${hrefStr}&quot;&gt;${decodedText}&lt;/a&gt;`;
    }

    // For regular links, ensure proper formatting
    return `&lt;a href=&quot;${hrefStr}&quot;&gt;${textStr}&lt;/a&gt;`;
  };

  marked.setOptions({
    renderer,
    mangle: false,
    headerIds: false,
  });
}

function processWikiLinks(html, filePath, config) {
  return html.replace(/\[\[(.*?)\]\]/g, (_, link) =&gt; {
    const [target, label = target] = link.split(&quot;|&quot;);
    const url = `${config.site.site_url}/${generateUrl(null, target)}`;
    return `&lt;a href=&quot;${url}&quot;&gt;${label}&lt;/a&gt;`;
  });
}
</code></pre>
<p>(funny thing: when I added this snippet to codebase, I needed immediately fix the generator because it generated links from the code snippet...)</p>
<p>These two functions are responsible for handling and parsing links.</p>
<p><code>processWikiLinks()</code> runs over the freshly rendered HTML and turns any remaining <code>[[Wiki Link|Label]]</code> syntax into fully qualified site URL.</p>
<p>I didn&#39;t trust to this Cursor generated code and I needed to created a e2e tests that checks that all links in generated feed are ok.</p>
<p>I set up Playwright for that and wrote simple test: <a href="https://gist.github.com/Frodigo/e1577910f92c0619b21b7d1c9de4c5a1">https://gist.github.com/Frodigo/e1577910f92c0619b21b7d1c9de4c5a1</a></p>
<hr>
<h2>Deploying RSS feed</h2>
<p>I tried to upload generated xml files to Obsidian Publish &quot;server&quot;, but it is not possible</p>
<p>So I decided to create a simple static website (used CLaudflare pages for that) and I deployed generated feeds there.</p>
<p>Also I created a Github action that automatically generate feeds and deploy them to Cloudflare: <a href="https://gist.github.com/Frodigo/a129e1914fea25156dfd2907d9a65940">https://gist.github.com/Frodigo/a129e1914fea25156dfd2907d9a65940</a></p>
<p>In addition I set up subdomains and it the end I have two RSS feeds available:</p>
<ul>
<li>All items: <a href="https://rss.frodigo.com/feed.xml">https://rss.frodigo.com/feed.xml</a></li>
<li>Recent 10 items: <a href="https://rss.frodigo.com/feed.recent.xml">https://rss.frodigo.com/feed.recent.xml</a></li>
</ul>
<h2>Showing feeds on my Obsidian page</h2>
<p>This was an easy one. I have just written a section on my homepage and added links there.</p>
<hr>
<h2>Wrap up</h2>
<p>I needed a few hours to generate this feed and deploy it to the server and now I have my RSS feed.</p>
<p>I see some potential improvement that I can make:</p>
<ol>
<li>Before everything I have to refactor the code, because I feel it can be 5 times more simple when human writes it.</li>
<li>I want to have separate feeds for categories</li>
<li>I realized that instead of deploying generated feed to Cloudflare I can just generate it and keep in version control. My repository is public so I can just use a raw version of file from Github and it should work</li>
</ol>
<p>All of these items I will do before I die. If so, I will write about them in the separate article.</p>
<p>I am wondering how to do this changes in a way that someone else can have such a nice RSS feed on their Obsidian page. Maybe Obsidian plugin? If you think is interesting, let me know!</p>
<hr>
<p><em>Published 17/04/2025</em> #blog #Obsidian #ObsidianPublish #WebDevelopment #ContentSyndication #JavaScript #StaticSite #BloggingTools #WebAutomation #Markdown #GitHubActions #CloudflareSites #KnowledgeManagement #DigitalGarden #RSSFeeds #ContentCreation #WebsiteCustomization #AIAssistants #ProgrammingChallenge #PersonalProject #FrontendDevelopment #NodeJS #Git #CI/CDtools #Cloudflare #Tutorial #CaseStudy #ProjectSetup #Intermediate #APISecurity #AutomatedTesting</p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[What well-known life laws I confirmed when I did a kitchen renovation]]></title>
            <description><![CDATA[It was a cold winter evening, somewhere in the countryside. Spouses were sitting on a comfortable green sofa and tasting various snacks (unfortunately, unhealthy).]]></description>
            <link>https://frodigo.com/Blog/2025/What+well-known+life+laws+I+confirmed+when+I+did+a+kitchen+renovation</link>
            <guid isPermaLink="false">https://frodigo.com/Blog/2025/What+well-known+life+laws+I+confirmed+when+I+did+a+kitchen+renovation</guid>
            <pubDate>Sun, 13 Apr 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>It was a cold winter evening, somewhere in the countryside. Spouses were sitting on a comfortable green sofa and tasting various snacks (unfortunately, unhealthy).</p>
<p>— I can’t wait for our new kitchen, wife said. She wanted to have a new kitchen for months, because she was frustrated that the furniture was falling apart and was impractical.</p>
<p>When she was cooking and something wasn’t working out for her, she would approach her husband and say — When are we finally going to do this renovation?</p>
<p>— I will make this soon, I promise, he answered.</p>
<p>— You, why don’t we hire professionals for this? Wife asked with some kind of irritation in her voice.</p>
<p>— I am a professional, you will see! He said firmly, and a rather obvious spark was visible in his eyes!</p>
<p>This is how I, the programmer who likes DIY, got myself into a kitchen renovation. Something that seemed like it would be fun. But what was it really like?</p>
<p>Well, I confirmed all the well-known life laws that I am aware of during this renovation.</p>
<p>The fact that I survived and I write it is the best thing that could happen. Besides, another important thing — I still have a wife!</p>
<h2>Murphy’s Law</h2>
<blockquote>
<p>“If anything can go wrong, it will.”</p>
</blockquote>
<p>The timeline for this renovation was short:</p>
<ol>
<li>1 day to dismantle old furniture</li>
<li>2 days to remove old tiles and prepare the walls for new ones</li>
<li>1 day for making changes in electricity (adding new electrical sockets and additional wiring for new lighting)</li>
<li>3 days to install new tiles and paint the walls</li>
<li>After 7 days, a team should come to assemble the new furniture</li>
</ol>
<p>For each point, I could write a separate story, but I’ll spare myself that today and move on to the climax.</p>
<p>After seven days of hard work, after dealt with various unexpected things.</p>
<p>When I had about 10 tiles left to lay, I opened the last package of tiles.</p>
<p>I could already imagine myself laying the last tiles, then reaching for the grouting and the job would be done. I could already feel the pride when my wife would come and say: “You’re a hero, my husband!”</p>
<p>I opened the last box.</p>
<p>And I thought I was going to go crazy. It turned out that the last package of tiles was different tiles I ordered. I didn’t check it beforehand.</p>
<p>I turned red in the face and started walking around, cursing under my breath. My wife looked at me with sympathy.</p>
<p>I thought I would go out of my mind and stand next to me. Oh, good that the tile seller who made a mistake wasn’t there because <a href="https://www.instagram.com/reel/DINr85_TeCK/?igsh=MWgxdnpiN3ZubW5xbA%3D%3D">https://www.instagram.com/reel/DINr85_TeCK/?igsh=MWgxdnpiN3ZubW5xbA%3D%3D</a>. How could you have made such a mistake?</p>
<p>I couldn’t finish, and the next day they were coming to assemble the furniture.</p>
<p>I filed a complaint and ordered new tiles, but I had to wait two weeks for them.</p>
<blockquote>
<p>“If anything can go wrong, it will.”</p>
</blockquote>
<h2>Yhprum’s Law</h2>
<p>The opposite of Murphy’s Law. It states:</p>
<blockquote>
<p>“Everything that can work, will work.”</p>
</blockquote>
<p>I have called a professional from the company responsible for new furniture and asked if it’s possible to start with furniture when the tiles are not done yet.</p>
<p>— Of course, he said. We can do our job and mount new furniture. When we&#39;re done, you can continue working with tiles.</p>
<p>It seemed strange to me, but if an expert says so, it must be so, right?</p>
<blockquote>
<p>“Everything that can work, will work.”</p>
</blockquote>
<p>Guess what? When they finished the furniture and I started again with tiles, I had a serious problem finishing them.</p>
<p>I had to be careful not to get anything dirty or mess it up. Murphy&#39;s law — again!</p>
<h2>Parkinson’s Law</h2>
<p><em>This law suggests that work expands to fill the time available.</em></p>
<p>And here is the most important law.</p>
<p>Because you know, it’s been a few weeks since this renovation.</p>
<p>The kitchen is supposedly finished, but there’s some silicone missing here. In another place, the socket would have to be fixed. One lamp is still not replaced.</p>
<p>But Parkinson’s law comes into play. Because as long as I live, I have time to finish this job ;-)</p>
<h2>Conclusion</h2>
<p>As I stand in my <em>almost</em> finished kitchen. I laugh thinking about how confidently I said, ‘I am a professional, you will see!’</p>
<p>The funniest part of all of these laws is that they fit into every situation. Perhaps the only law that truly matters is the one you choose to believe in.</p>
<p>Actually, I believe that next time, when I do a kitchen renovation, I will complete it 100%.</p>
<p>And that I will do it together. With the same wife.</p>
<h2>Sources</h2>
<ul>
<li><a href="https://en.wikipedia.org/wiki/Murphy%27s_law">https://en.wikipedia.org/wiki/Murphy%27s_law</a></li>
<li><a href="https://en.wikipedia.org/wiki/Yhprum%27s_law">https://en.wikipedia.org/wiki/Yhprum%27s_law</a></li>
<li><a href="https://en.wikipedia.org/wiki/Parkinson%27s_law">https://en.wikipedia.org/wiki/Parkinson%27s_law</a></li>
</ul>
<hr>
<p><em>Published 13/04/2025</em> #blog #diy</p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Why teaching kids how to program is not easy]]></title>
            <description><![CDATA[It was a cold winter day. The temperature in my place was around -5 degrees Celsius (outside, of course).]]></description>
            <link>https://frodigo.com/Blog/2025/Why+teaching+kids+how+to+program+is+not+easy</link>
            <guid isPermaLink="false">https://frodigo.com/Blog/2025/Why+teaching+kids+how+to+program+is+not+easy</guid>
            <pubDate>Mon, 07 Apr 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>It was a cold winter day. The temperature in my place was around -5 degrees Celsius (outside, of course).</p>
<p>I was sitting at my computer, trying to solve a deployment problem with my application, when my son came into my home office. He sat off to the side and watched quietly for a while.</p>
<p>“What’s up, son?” I asked, turning my attention to him.</p>
<p>“Dad, what exactly are you doing here?”</p>
<p>I began explaining to him that I was programming. We talked for a bit, and in the end, he said, “Dad, I want to be a programmer like you!”</p>
<p>At that moment, I felt a spark of satisfaction and pride run through my entire body. I’m sure there was a <strong>twinkle in my eye.</strong></p>
<h2>TL;DR</h2>
<ul>
<li>Teaching kids to code isn’t as easy as sharing your programmer passion</li>
<li>Start with visual block-based coding (like Scratch) instead of text-based languages</li>
<li>Use learning platforms like code.org rather than creating your own projects</li>
<li>Create consistent session structures and rituals for better learning</li>
<li>Visualize abstract concepts and break complex processes into smaller steps</li>
<li>Be patient — learning happens about 150 times slower than you might expect</li>
<li>Prioritize fun and enjoyment over rapid progress</li>
</ul>
<h2>First computer and playing games</h2>
<p>It’s amazing how children try to imitate their parents, isn’t it?</p>
<p>I said to him, “It’s time, my son. It’s time for you to have your first <strong>computer</strong>.”</p>
<p>Up until then, I’d tried to keep him away from the cyber world, from phones and computers. But if he wanted to be like Dad, well, there was no point in delaying anymore.</p>
<p>And so my son got a <strong>Raspberry Pi 400</strong>, which was supposed to be the beginning of his programming journey.</p>
<p>But of course, we started with playing games. (I bought PiMiga — an <strong>Amiga</strong> emulator for Raspberry Pi — and we played quite a lot, but that’s a topic for another story, I think.)</p>
<h2>First program</h2>
<p>After some time, we started programming. On a Raspberry Pi, you can easily begin writing <strong>Python</strong> code. Our first program was about counting how much time we spent playing on the computer.</p>
<p>My son really likes <strong>Super Mario</strong> and math, so we created simple programs on these subjects. I tried to make it interesting for him.</p>
<p>What’s funny is that this was also my first time coding in Python. For some reason, I had been avoiding this language before, but since then, I’ve actually grown very close to Python.</p>
<h2>Things become boring</h2>
<p>After the initial excitement, problems appeared.</p>
<p>“Can we play this? Can we play that? But why do I have to turn it off now?” These were questions from a child who had already forgotten that he was supposed to become a programmer.</p>
<p>Ah, these games — had they ruined everything? I tried to <strong>educate</strong> my son about how long he could use a computer and how to use the internet safely. Despite this, he was losing interest.</p>
<p>Unfortunately, there was a competition: <strong>programming vs. gaming</strong>, and programming had no chance in this fight.</p>
<p>And that’s how I came to the conclusion:</p>
<h2>Teaching kids programming is demanding</h2>
<p>I realized how naive I’d been to try teaching my child programming without any preparation.</p>
<p>We were struggling with:</p>
<ol>
<li><p><strong>Abstract thinking</strong> — It’s hard for children to connect abstract concepts like loops, variables, and functions with real life.</p>
</li>
<li><p><strong>Motivation</strong> — My son wanted to see results immediately, but coding often requires patience before seeing outcomes.</p>
</li>
<li><p><strong>Excessive Screen Time</strong> — Five minutes of actual programming required more than half an hour in front of the screen because I needed to explain concepts first, provide encouragement, demonstrate examples, and of course, allow time for games.</p>
</li>
<li><p><strong>Teacher competency</strong> — I wish I were a good programming teacher, but I’m not. Programming and teaching programming are two different skills. Additionally, teaching adults how to code is quite different from teaching kids. There’s simply no way to be an effective teacher for children without proper preparation.</p>
</li>
</ol>
<p><img src="https://miro.medium.com/v2/resize:fit:1400/1*gL7Cx6vivOhCf7IPnBZ7oA.png" alt="teaching kids"></p>
<p>Teaching kids programming is demanding</p>
<h2>The second chance</h2>
<p>I can sum up this first period of teaching as a difficult start. But it’s not that bad — I showed something to my child, drew conclusions, and we can continue.</p>
<p>This is what I learned:</p>
<h3>1. Block-based coding is better for starting than text-coding</h3>
<p>Instead of starting with Python, I could have used Scratch and similar interactive tools that teach programming in a more visual way.</p>
<p>Block-based environments allow children to see programming concepts represented as tangible elements they can manipulate, making abstract ideas more concrete and approachable.</p>
<h3>2. Use established learning platforms instead of creating your own projects</h3>
<p>I initially thought I would easily come up with interesting projects and coding ideas. However, it wasn’t that simple. Now I use platforms like <a href="https://code.org/">https://code.org/</a> and <a href="https://www.gov.pl/web/koduj">https://www.gov.pl/web/koduj</a> (a Polish resource) to guide our learning, and it’s much more effective for both of us.</p>
<h3>3. Build rituals and repeatable session structures</h3>
<p>I’ve noticed better results when my son knows exactly what to expect from each lesson. Having a consistent structure for what we’ll do during the session and afterward creates a sense of familiarity and security.</p>
<p>Additionally, I now spend more time reviewing what we learned previously, discussing what we’re working on currently, and previewing what comes next. These regular summaries and repetitions have proven remarkably effective.</p>
<h3>4. Visualise concepts and simplify complex processes</h3>
<p>I try to relate abstract programming concepts to familiar, concrete objects or experiences. Analogies help connect new information to my son’s existing knowledge base, making difficult concepts more accessible.</p>
<p>When something is challenging to explain, I break it down into smaller pieces and tackle them one by one. This step-by-step approach prevents overwhelm and builds understanding progressively.</p>
<h3>5. Don’t force anything</h3>
<p>Learning works best when it’s fun and enjoyable. The hardest thing for me to accept was that the pace of learning is about 150 times slower than what I would prefer as a teacher. However, I’ve learned that adapting to my son’s pace and finding joy in the process leads to more sustainable progress.</p>
<p><img src="https://miro.medium.com/v2/resize:fit:1400/1*KSN2p_yGksAmBqS6jWrFDA.png" alt="don't force"></p>
<p>Lessons Learned</p>
<h2>Good luck</h2>
<p>Teaching programming to kids requires luck, considerable strength, and abundant patience. I wish you success on this rewarding journey!</p>
<p>And may your child become an even better programmer than you!</p>
<hr>
<p><em>Published 07/04/2025</em> #blog #ProgrammingFundamentals  #Python #BestPractices  #ConceptExplanation  #CaseStudy  #Beginner</p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How I failed at writing a blog many times and what I would do differently if I could turn back time]]></title>
            <description><![CDATA[I’ve been trying to write a blog for many years. I published my first post back in 2017. By now, I wish I could say I’m a good writer with hundreds of posts behind me.]]></description>
            <link>https://frodigo.com/Blog/2025/How+I+failed+at+writing+a+blog+many+times+and+what+I+would+do+differently+if+I+could+turn+back+time</link>
            <guid isPermaLink="false">https://frodigo.com/Blog/2025/How+I+failed+at+writing+a+blog+many+times+and+what+I+would+do+differently+if+I+could+turn+back+time</guid>
            <pubDate>Sat, 05 Apr 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>I’ve been trying to write a blog for many years. I published my first post back in 2017. By now, I wish I could say I’m a good writer with hundreds of posts behind me.</p>
<p>But the reality is different. In this article, I’ll share the mistake I kept repeating — and what I wish I had done differently if I could turn back time.</p>
<p><img src="https://miro.medium.com/v2/resize:fit:1400/1*ukwdWNr3j8MzNKDjZAWJtw.png" alt="my first blog"></p>
<p>My first blog. Source: <a href="https://web.archive.org/web/20170710170315/http://marcin-kwiatkowski.com/">https://web.archive.org/web/20170710170315/http://marcin-kwiatkowski.com/</a></p>
<h2>TL;DR</h2>
<p>Programmers who want to be writers should focus on writing, not on endlessly rebuilding their blog platforms.</p>
<p>If I could turn back time, I’d stop chasing the perfect blog setup and just write. Reinventing the wheel was a great excuse to avoid the hard part: writing.</p>
<h2>My first steps into the trap</h2>
<p>My first blog was a WordPress site written in Polish — a solid choice to get started. The quality of my content wasn’t great, but I enjoyed it. I felt like I was making progress, and each new post was a little better than the last.</p>
<h2>Chasing performance, losing Purpose</h2>
<p>A few years later, I worked on a project using React and Next.js — and I was impressed. I thought, <em>why not build my blog with it and fully customize the experience?</em></p>
<p>What could go wrong?</p>
<p>I spent weeks crafting my shiny new blog platform. Then more weeks importing posts from WordPress. I even chose Storyblok as a headless CMS. It was a big re-platforming effort, and during that time, I wasn’t writing anything new.</p>
<p>I believed that a Next.js blog would be blazing fast and score perfectly in PageSpeed Insights. And it did.</p>
<p>But now I know: the most important part of a blog is the content — not whether it gets a score of 100 or 45.</p>
<p>Honestly, I never even finished implementing all the features I had planned. After a few months, I realized it wasn’t as good as I’d hoped.</p>
<p>Writing on WordPress was easier.</p>
<p><img src="https://miro.medium.com/v2/resize:fit:1400/1*TdE_i5RFSX9Yx1GQ-WwpOA.png" alt="blog on next js"></p>
<p>Blog on NextJS (unfortunatelly CSS doesn’t work in the archive so it looks ugly. Source: <a href="https://web.archive.org/web/20210602071506/https://marcin-kwiatkowski.com/">https://web.archive.org/web/20210602071506/https://marcin-kwiatkowski.com/</a></p>
<h2>Wordpress again</h2>
<p>After failing with my custom setup, I decided to go back to WordPress — which meant more weeks spent on yet another migration.</p>
<p>This time, I also set up a multilingual platform. I thought I could write in both Polish and English at the same time.</p>
<p>It kind of worked. But I quickly realized it wasn’t as easy as I had imagined. Instead of focusing on writing good content, I was spending my energy on translating.</p>
<p>(Later, I started using AI for that — but that’s another story.)</p>
<p>I should have resisted the hype and shiny new tech — but I didn’t.</p>
<p>Every new tool fascinated me, and I kept running away from what truly mattered: improving my writing and actually writing.</p>
<p>Instead, I jumped from one technology to another like a man possessed.</p>
<p><img src="https://miro.medium.com/v2/resize:fit:1400/1*JAgmfqvrIcMPSvoUrl5Ymw.png" alt="wordpress blog"></p>
<p>New Wordpress blog (also CSS is broken in archive). Source: <a href="https://web.archive.org/web/20230606020832/https://marcinkwiatkowski.com/">https://web.archive.org/web/20230606020832/https://marcinkwiatkowski.com/</a></p>
<h2>Go away from WordPress again and migrate to Astro</h2>
<p>Then I met astro.build and I was fascinated. I thought that a static website with Markdown as an engine for content is a good idea (and of course it is).</p>
<p>I migrated to Astro. I spent a lot of time because this time I thought that I needed to use this re-platforming opportunity to create a beautiful blog. It was kinda beautiful.</p>
<p><img src="https://miro.medium.com/v2/resize:fit:1400/1*eYJjtMNFMbei6xtnnSnAlQ.png" alt="astrojs blog"></p>
<p>Blog migrated to astro. Source: <a href="https://web.archive.org/web/20241005174031/http://marcinkwiatkowski.com/">https://web.archive.org/web/20241005174031/http://marcinkwiatkowski.com/</a></p>
<p>I was really satisfied with this implementation. I wrote more about this here: <a href="https://medium.com/gitconnected/how-and-why-i-moved-my-blog-from-wordpress-to-astro-and-markdown-3549672d5a86">https://medium.com/gitconnected/how-and-why-i-moved-my-blog-from-wordpress-to-astro-and-markdown-3549672d5a86</a></p>
<p>Writing in Markdown is perfect. I also started using Obsidian. I could focus more on writing. Anyway, I still used a lot of my time on improving the blog platform, refactoring, adding new features, optimizing, etc.</p>
<p><img src="https://miro.medium.com/v2/resize:fit:1400/1*jFzqT5jvztwT4ilcZUlbqg.png" alt="my blog platform journey"></p>
<h2>The AHA moment</h2>
<p>In February, after a 10-year break from engineering studies, I started my master’s degree.</p>
<p>During one of the first lectures, something the lecturer said made something click in my head.</p>
<p>It was a Saturday, already late — around 6 p.m. We were sitting in a small classroom, everyone visibly tired. The kind of moment where you’d expect nothing meaningful to happen.</p>
<p>And yet, that’s exactly when it did.</p>
<p>The lecturer was an older professor with a sharp sense of humor. His shirt peeked out from under a worn sweater, and his piercing gaze made you pay attention.</p>
<p>There was something about him — calm, thoughtful, quietly commanding.</p>
<p>What truly stayed with me was something he said during that lecture. He spoke slowly and clearly about what awaits every student: the master’s thesis.</p>
<p>He said something like this (I’m paraphrasing):</p>
<blockquote>
<p><em>If your master’s thesis is truly good, you can write it by hand with a pen — and your supervisor will copy it himself, because he’ll know it’s worth the effort.</em>
<em>But if your work is weak, even if it looks beautiful and polished, no one will care.</em></p>
</blockquote>
<p>This is a very interesting point of view. It may be obvious, but it made me reflect on how I approach writing.</p>
<p>I thought, do I really want to be a writer, or am I just pretending?</p>
<h2>Facing the truth about my writing</h2>
<p>It was hard to face the truth: I hadn’t been focusing on the right thing.</p>
<p>At one point, I even asked myself — <em>maybe writing isn’t for me? Maybe I’m just pretending?</em></p>
<p>Still, I gave myself another chance.</p>
<p>So… what did I do?</p>
<p>Replatformed again. 😅</p>
<p>This time, I ditched the beautiful Astro website and went back to basics. I stuck with Markdown and write in Obsidian.</p>
<p>Now, Obsidian is my writing engine. My website is plain — some might even call it ugly. But I don’t care.</p>
<p>I’m finally focusing on what matters: <strong>writing</strong>.</p>
<p><img src="https://miro.medium.com/v2/resize:fit:1400/1*Hfi8Lapum4YdQoFAzAiU2A.png" alt="obsidian blog"></p>
<p>My new ugly website. Source: <a href="https://frodigo.com">https://frodigo.com</a></p>
<p>So now I’m writing — and I’ve made a deal with myself: no replatforming for the next year.</p>
<p>Just writing.</p>
<p>Can I stick to it?</p>
<p>We’ll see.</p>
<hr>
<p><em>Published: 04/05/2025</em> #blog #writing #BloggingTools #SoftwareEngineering #Productivity #PersonalGrowth</p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[I migrated website from Astro.build to Obsidian publish]]></title>
            <description><![CDATA[Programmers who want to be writers should focus on writing, not on endlessly rebuilding their blog platforms.]]></description>
            <link>https://frodigo.com/Blog/2025/I+migrated+website+from+Astro.build+to+Obsidian+publish</link>
            <guid isPermaLink="false">https://frodigo.com/Blog/2025/I+migrated+website+from+Astro.build+to+Obsidian+publish</guid>
            <pubDate>Sat, 05 Apr 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h2>TL;DR</h2>
<p>Programmers who want to be writers should focus on writing, not on endlessly rebuilding their blog platforms.</p>
<p>If I could turn back time, I’d stop chasing the perfect blog setup and just write. Reinventing the wheel was a great excuse to avoid the hard part: writing.</p>
<p>Read more in my Medium story: <a href="https://medium.com/@frodigo/how-i-failed-at-writing-a-blog-many-times-d1e5040763a7">https://medium.com/@frodigo/how-i-failed-at-writing-a-blog-many-times-d1e5040763a7</a></p>
<hr>
<p><em>Published 05/04/2025</em> #blog #obsidian #astro</p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Historical cryptography algorithms]]></title>
            <description><![CDATA[My first task on the cybersecuirty learning roadmap course at studies was about experimenting with historical cryptography algorithms.]]></description>
            <link>https://frodigo.com/Blog/2025/Historical+cryptography+algorithms</link>
            <guid isPermaLink="false">https://frodigo.com/Blog/2025/Historical+cryptography+algorithms</guid>
            <pubDate>Fri, 28 Mar 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>My first task on the cybersecuirty learning roadmap course at studies was about experimenting with historical cryptography algorithms.</p>
<p>I was sitting in the computer room, it was quite stuffy, and around me were 30 other students. It was the third class of the day. The lecturer came in, who looked like a rather strict gentleman. He explained to us briefly what the course would look like and what the rules were for passing.</p>
<p>The atmosphere began to thicken, and the gentleman promptly moved on to the task. He told us to install <code>Cryptool1</code> and do a task related to historical cryptographic algorithms.</p>
<p>Cryptool is a free tool for learning cryptography. Okay, let&#39;s install it, but wait, wait.</p>
<p>This tool only works on Windows. :facepalm</p>
<p>I did a quick research and found that I can use run Windows program on Mac using Wine. Let&#39;s try I thought:</p>
<p><code>brew install --cask wine-stable</code></p>
<p>I was pressed for time, because after the exercises we had to write a report.</p>
<p>Unfortunately, internet connection was so poor in that computer room and I was not able to install this tool in 1 hour.</p>
<p>In meantime I used Perplexity to find information about historical crypto algorithms.</p>
<p>I spent the rest of the class reading...</p>
<h2>Few days later</h2>
<p>I did back to the topic and again installed the <code>Wine</code>, but this time at my home and internet was quite better.</p>
<p>After that I needed to install <code>Rosetta</code> - a tool that allows to run apps dedicated for Intel Chips on Apple chips. Oh really?</p>
<p>To run <code>Cryptool</code> on my mac I needed to install wine (to run Windows app), but to run <code>Wine</code> i needed to install <code>Rosetta</code></p>
<pre><code class="language-mermaid">flowchart LR
    A[Mac with Apple Silicon] --&gt; B[Rosetta 2]
    B --&gt; C[Wine]
    C --&gt; D[CrypTool]
</code></pre>
<p>I ran Cryptool installer:</p>
<p><code>wine SetupCrypTool_1_4_42_pl.exe</code></p>
<p>Then I was able to run it (I installed the polish version)</p>
<p><img src="Notes%20&%20Learning/Engineering/Cybersecurity/images/cryptool-installed.png" alt="Cryptool1 installed on Mac"></p>
<h2>Algorithms</h2>
<p>Ok, so at this moment I can experiment with Cryptool and learn about crypto a bit.</p>
<p>In the scope of task I had: <em>Cesar</em>, <em>Vigenere</em>, <em>Hill</em> and <em>XOR</em>. Let&#39;s take a closer look at them</p>
<h3>Caesar&#39;s cipher</h3>
<p>It&#39;s one of the oldest and simplest  <a href="Encryption.md">Encryption.md</a> algorithm invented (or at least associated with) Julius Caesar.</p>
<p>Cesar used in in around 58 BC. How it worked? It scrambled a message by shifting its letters.
Technically speaking is a <a href="Monoalphabetic%20rotation%20ciphers.md">Monoalphabetic%20rotation%20ciphers.md</a> it shifts letters by 3, but there is also a variant called <code>ROT-13</code> that shifts letters by 13.</p>
<p>I implemented the Caesar cipher in C# for learning purposes.</p>
<p>Here you can find:</p>
<ul>
<li><a href="https://github.com/Frodigo/garage/tree/main/sandbox/cybersecurity/cryptography/HistoricalCiphersDemo">https://github.com/Frodigo/garage/tree/main/sandbox/cybersecurity/cryptography/HistoricalCiphersDemo</a></li>
<li><a href="https://github.com/Frodigo/garage/blob/main/sandbox/cybersecurity/cryptography/HistoricalCiphersDemo/Ciphers/Caesar/CaesarCipher.cs">https://github.com/Frodigo/garage/blob/main/sandbox/cybersecurity/cryptography/HistoricalCiphersDemo/Ciphers/Caesar/CaesarCipher.cs</a></li>
<li><a href="https://github.com/Frodigo/garage/blob/main/sandbox/cybersecurity/cryptography/HistoricalCiphersDemo/Tests/CaesarCipherTests.cs">https://github.com/Frodigo/garage/blob/main/sandbox/cybersecurity/cryptography/HistoricalCiphersDemo/Tests/CaesarCipherTests.cs</a></li>
</ul>
<p>Before you go to the next cipher, check how the caesar works on diagrams below:</p>
<pre><code class="language-mermaid">flowchart LR
    A[Plain Text] --&gt; B[Apply Shift]
    B --&gt; C[Encrypted Text]
    D[Key: Shift Value] --&gt; B
</code></pre>
<pre><code class="language-mermaid">flowchart TD
    subgraph &quot;Example&quot;
    E[Plain: HELLO] --&gt; F[Shift by 3]
    F --&gt; G[Cipher: KHOOR]
    end
</code></pre>
<pre><code class="language-mermaid">flowchart TD
    subgraph &quot;Alphabet Shift&quot;
    H[&quot;A B C D E F G H I J K L M N O P Q R S T U V W X Y Z&quot;] --&gt; I[&quot;Shift by 3&quot;]
    I --&gt; J[&quot;D E F G H I J K L M N O P Q R S T U V W X Y Z A B C&quot;]
    end
</code></pre>
<h3>Vigenère cipher</h3>
<p>It&#39;s a <a href="polyalphabetic%20substitution%20cipher.md">polyalphabetic%20substitution%20cipher.md</a> which uses a keyword to encrypt and decrypt messages.</p>
<p><strong>How it works?</strong></p>
<ol>
<li>Each letter of the key specifies a different shift</li>
<li>Same plaintext letter can encrypt to different <a href="Ciphertext.md">Ciphertext.md</a> letters</li>
<li>Algorithm takes two inputs: message(plaintext) and key(password).</li>
<li>Algorithm prepare key by repeating it to the message length.</li>
<li>Then it intitialize empty result string.</li>
<li>Next it iterates on each character in plaintext:</li>
<li><ol>
<li>if a char is a letter, gets corresponding char of key</li>
</ol>
</li>
<li><ol start="2">
<li>checks is plaintext cha uppercase and calculate Vigenère shift:<ol>
<li>for uppercase letter: <code>(P + K) % 26 +65</code></li>
<li>otherwise: <code>(P + K) % 26 + 97</code></li>
</ol>
</li>
</ol>
</li>
<li><ol start="3">
<li>if char is not a letter, it&#39;s added to the results unchanged</li>
</ol>
</li>
<li>When iteration is done, returns ciphertext.</li>
</ol>
<p>You can find my implementation of this cipher here:</p>
<ul>
<li><a href="https://github.com/Frodigo/garage/blob/main/sandbox/cybersecurity/cryptography/HistoricalCiphersDemo/Ciphers/Vigen%C3%A8re/Vigen%C3%A8reCipher.cs">https://github.com/Frodigo/garage/blob/main/sandbox/cybersecurity/cryptography/HistoricalCiphersDemo/Ciphers/Vigen%C3%A8re/Vigen%C3%A8reCipher.cs</a></li>
<li><a href="https://github.com/Frodigo/garage/blob/main/sandbox/cybersecurity/cryptography/HistoricalCiphersDemo/Tests/Vigen%C3%A8reCipherTests.cs">https://github.com/Frodigo/garage/blob/main/sandbox/cybersecurity/cryptography/HistoricalCiphersDemo/Tests/Vigen%C3%A8reCipherTests.cs</a></li>
</ul>
<p>Take a look on diagram:</p>
<pre><code class="language-mermaid">---
id: 050da2d7-1974-4ec0-89f9-f9d5ae2c3d33
---
flowchart TD
    Start([Start]) --&gt; InputText[/Input Plaintext/]
    InputText --&gt; InputKey[/Input Keyword/]
    InputKey --&gt; PrepKey[Prepare Key: Repeat to match plaintext length]
    PrepKey --&gt; InitResult[Initialize Empty Result String]

    InitResult --&gt; LoopStart{For each character in plaintext}

    LoopStart --&gt;|Yes| CheckChar{Is character a letter?}
    LoopStart --&gt;|No| EndProcess([End])

    CheckChar --&gt;|Yes| GetChars[Get plaintext char and corresponding key char]
    CheckChar --&gt;|No| AddUnchanged[Add character to result unchanged]
    AddUnchanged --&gt; NextChar[Next character]
    NextChar --&gt; LoopStart

    GetChars --&gt; DetermineCase{Is plaintext char uppercase?}

    DetermineCase --&gt;|Yes| ShiftUpper[&quot;Calculate Vigenere shift:
    P = plaintext char code - 65
    K = key char code - 65
    C = (P + K) % 26 + 65&quot;]

    DetermineCase --&gt;|No| ShiftLower[&quot;Calculate Vigenere shift:
    P = plaintext char code - 97
    K = key char code - 97
    C = (P + K) % 26 + 97&quot;]

    ShiftUpper --&gt; ConvertBack[Convert code back to character]
    ShiftLower --&gt; ConvertBack

    ConvertBack --&gt; AddToResult[Add shifted character to result]
    AddToResult --&gt; NextChar

    EndProcess --&gt; ReturnResult[/Return Encrypted Text/]
    ReturnResult --&gt; End([End])


    class Start,End,EndProcess terminator
    class InputText,InputKey,ReturnResult io
    class PrepKey,InitResult,GetChars,ShiftUpper,ShiftLower,ConvertBack,AddToResult,AddUnchanged,NextChar process
    class LoopStart,CheckChar,DetermineCase decision
</code></pre>
<h3>Hill cipher</h3>
<p>It&#39;s a polygraphic substitution cipher.</p>
<p>It&#39;s a block cipher.</p>
<p>It uses linear algebra and matrix manipulation to encrypt and decrypt messages.</p>
<h4>How it works</h4>
<ol>
<li>Turn letters into numbers<ul>
<li><code>A = 0, B = 1, C = 2... Z = 25</code></li>
</ul>
</li>
<li>Break your message into small groups<ul>
<li>If using a <code>2×2</code> key, break into groups of 2 letters</li>
</ul>
</li>
<li>Use a special key (a math grid of numbers)<ul>
<li>The key is a square grid of numbers (like 2×2 or 3×3)</li>
</ul>
</li>
<li>Do some math magic with your numbers and the key<ul>
<li>Multiply your letter numbers by the key grid</li>
</ul>
</li>
<li>Turn the new numbers back into letters</li>
<li>Done</li>
</ol>
<h5>Example with &quot;HELP&quot;</h5>
<ol>
<li>Convert to numbers:<ul>
<li>H = 7, E = 4, L = 11, P = 15</li>
</ul>
</li>
<li>Split into groups: [7,4] and [11,15]</li>
<li>Use this key:
[3, 2]
[5, 7]</li>
<li>Math magic:<ul>
<li>For [7,4]:
[3 2] × [7] = [3×7 + 2×4] = [21+8] = [29] → [3] (after mod 26) (because there are 26 letters in alphabet)
[5 7] [4] [5×7 + 7×4] [35+28] [63] → [11] (after mod 26)</li>
<li>For [11,15]:
[3 2] × [11] = [3×11 + 2×15] = [33+30] = [63] → [11] (after mod 26)
[5 7] [15] [5×11 + 7×15] [55+105] [160] → [4] (after mod 26)</li>
</ul>
</li>
<li>Convert back to letters:<ul>
<li>3 = D, 11 = L, 11 = L, 4 = E</li>
<li>Secret message: &quot;DLLE&quot;</li>
</ul>
</li>
</ol>
<p>To decode it, your need the special inverse key to turn &quot;DLLE&quot; back into &quot;HELP&quot;!</p>
<p>For encryption we used this key (matrix):</p>
<p>[3 2]
[5 7]</p>
<p>To decrypt the message, we need to find the inverse of this matrix.</p>
<p>You can find a basic Hill Cipher implementation here:</p>
<ul>
<li><a href="https://github.com/Frodigo/garage/blob/main/sandbox/cybersecurity/cryptography/HistoricalCiphersDemo/Ciphers/Hill/HillCipher.cs">https://github.com/Frodigo/garage/blob/main/sandbox/cybersecurity/cryptography/HistoricalCiphersDemo/Ciphers/Hill/HillCipher.cs</a></li>
<li><a href="https://github.com/Frodigo/garage/blob/main/sandbox/cybersecurity/cryptography/HistoricalCiphersDemo/Tests/HillCipherTests.cs">https://github.com/Frodigo/garage/blob/main/sandbox/cybersecurity/cryptography/HistoricalCiphersDemo/Tests/HillCipherTests.cs</a></li>
</ul>
<p>As you can see implementation of this cipher is quite more complicated that implementation of previous ones (Caeasr, Vigenère).</p>
<p>Take a look at diagram that presents how <em>Hill cipher</em> works:</p>
<pre><code class="language-mermaid">flowchart TD
    subgraph &quot;Decryption Process&quot;
        CT2[Ciphertext] --&gt; CNV2[Convert Letters to Numbers]
        CNV2 --&gt; BLK2[Divide into n-letter Blocks]

        KM --&gt; INV[Calculate Inverse Key Matrix\nmodulo 26]

        BLK2 --&gt; MM2{Matrix Multiplication}
        INV --&gt; MM2
        MM2 --&gt; MOD2[Apply Modulo 26]
        MOD2 --&gt; CONV2[Convert Numbers Back to Letters]
        CONV2 --&gt; PT2[Plaintext Message]
    end

    class PT,PT2 plaintext
    class KM,INV key
    class CT,CT2 ciphertext
    class CNV,BLK,MM,MOD,CONV,CNV2,BLK2,MM2,MOD2,CONV2 process
    class EX example
</code></pre>
<p>Uh, a lot of theory and it&#39;s not easy, is it? Let&#39;s try to see how it work in practice and do some tasks in Cryptool.</p>
<h2>Tasks &amp; Experiments</h2>
<h3>1. Compare plaintext entropy values ​​for Polish, English and one other selected language</h3>
<p>I used these texts:</p>
<p><strong>Polish:</strong></p>
<pre><code class="language-shell">Dzień dobry. Witamy w świecie kryptografii. Bezpieczeństwo informacji jest niezwykle ważne w dzisiejszych czasach. Każdy użytkownik powinien dbać o ochronę swoich danych osobowych. Zastosowanie zaawansowanych algorytmów szyfrujących pozwala na skuteczne zabezpieczenie komunikacji. Język polski zawiera wiele znaków diakrytycznych, takich jak ą, ę, ć, ł, ń, ó, ś, ź, ż, co zwiększa liczbę możliwych kombinacji i wpływa na wartość entropii. Analiza częstotliwości występowania liter w języku polskim może dostarczyć interesujących wyników dla badaczy kryptografii. Polska literatura obfituje w bogate słownictwo i złożone konstrukcje gramatyczne.
</code></pre>
<p><strong>Results:</strong></p>
<ul>
<li>text has <code>23</code> different characters comparing to <code>26</code> characters in alphabet</li>
<li>entropy: <code>4.26</code></li>
<li>max possible entropy: <code>4.70</code></li>
</ul>
<p><strong>English:</strong></p>
<pre><code class="language-shell">Good morning. Welcome to the world of cryptography. Information security is extremely important in today&#39;s world. Every user should take care to protect their personal data. The use of advanced encryption algorithms allows for effective security of communication. The English language relies primarily on 26 letters without diacritical marks, which affects its entropy value. Frequency analysis of letters in English can provide interesting results for cryptography researchers. English literature is rich in vocabulary and has its own unique grammatical structures that differ from other languages.
</code></pre>
<p><strong>Results:</strong></p>
<ul>
<li>text has <code>24</code> different characters comparing to <code>26</code> characters in alphabet</li>
<li>entropy: <code>4.16</code></li>
<li>max possible entropy: <code>4.70</code></li>
</ul>
<p><strong>Italian:</strong></p>
<pre><code class="language-shell">Buongiorno. Benvenuti nel mondo della crittografia. La sicurezza delle informazioni è estremamente importante nel mondo di oggi. Ogni utente dovrebbe prestare attenzione alla protezione dei propri dati personali. L&#39;uso di algoritmi di crittografia avanzati consente un&#39;efficace sicurezza della comunicazione. La lingua italiana utilizza l&#39;alfabeto latino con alcune lettere accentate come à, è, ì, ò, ù, che influiscono sul valore dell&#39;entropia. L&#39;analisi della frequenza delle lettere in italiano può fornire risultati interessanti per i ricercatori di crittografia. La letteratura italiana è ricca di vocabolario e ha strutture grammaticali uniche.
</code></pre>
<p><strong>Results:</strong></p>
<ul>
<li>text has <code>21</code> different characters comparing to <code>26</code> characters in alphabet</li>
<li>entropy: <code>3.92</code></li>
<li>max possible entropy: <code>4.70</code></li>
</ul>
<h4>My interetations of results</h4>
<p>Because entropy is a a measure of unpredictability and polish had has the best score it is more unpredicable than english and italian.</p>
<p>Based on that, polish is a good language for cryptography use cases.</p>
<h3>2. Compare the entropy values ​​of pllaintext and cryptogram depending on the algorithm (Caesar, Vigenere, Hill)</h3>
<p>I used my polish plaintext and encrypted it using these trhee algorithms.</p>
<p><strong>Results:</strong></p>
<table>
<thead>
<tr>
<th>Algorithm</th>
<th>Entropy</th>
<th>% Max Possible Entropy</th>
<th>Number of unique characters</th>
</tr>
</thead>
<tbody><tr>
<td>Plaintext</td>
<td>4.26</td>
<td>90.6%</td>
<td>23</td>
</tr>
<tr>
<td>Caesar</td>
<td>4.26</td>
<td>90.6%</td>
<td>23</td>
</tr>
<tr>
<td>Vigenere</td>
<td>4.62</td>
<td>98.3%</td>
<td>26</td>
</tr>
<tr>
<td>Hill</td>
<td>4.61</td>
<td>98%</td>
<td>26</td>
</tr>
</tbody></table>
<p>Caesar cipher does not increase entropy because it&#39;s just shifting letters, but Vigenere and Hill increased it a lot.</p>
<p>Vigenere disrupts the typical frequency of letter occurrence.</p>
<p>Hill is a block substitution cipher that significantly changes the statistical properties of the text.</p>
<h3>3. Compare plaintext histograms for Polish, English, and one other selected language</h3>
<p><strong>Polish:</strong></p>
<pre><code class="language-shell">Język polski należy do grupy języków zachodniosłowiańskich. Charakteryzuje się bogatym słownictwem oraz złożoną gramatyką. Zawiera wiele znaków diakrytycznych, takich jak ą, ę, ć, ł, ń, ó, ś, ź czy ż. W polszczyźnie występuje siedem przypadków oraz trzy rodzaje gramatyczne. Teksty pisane po polsku mają specyficzny rozkład częstotliwości liter, gdzie samogłoski takie jak a, e, i, o stanowią znaczący procent. Spółgłoski takie jak t, n, r, s, w również pojawiają się często. Rzadziej występują litery q, v czy x, które znajdują się głównie w wyrazach zapożyczonych. Język polski wyróżnia się na tle innych języków europejskich nagromadzeniem spółgłosek, co widać w słowach takich jak &quot;chrząszcz&quot; czy &quot;źdźbło&quot;. Niezwykle ważne w polszczyźnie są miękkości i twardości głosek, które wpływają na znaczenie wyrazów. Polski zasób słownictwa jest stale wzbogacany przez nowe wyrazy, często adaptowane z innych języków. Struktura zdań bywa rozbudowana, z wieloma formami czasowników oraz użyciem imiesłowów. Typowy tekst w języku polskim zawiera wszystkie te charakterystyczne elementy, które tworzą niepowtarzalny profil statystyczny naszego języka.
</code></pre>
<p><img src="Notes%20&%20Learning/Engineering/Cybersecurity/images/histogram-plain-pl.png" alt="polish plain text histogram"></p>
<p>I can see that the vowels A, E, I, O dominate.</p>
<p><strong>English:</strong></p>
<pre><code class="language-shell">The English language belongs to the West Germanic branch of the Indo-European language family. It is widely spoken across the globe, serving as a primary or secondary language for millions of people. English features twenty-six letters without diacritical marks, unlike many European languages. Its alphabet consists of five vowels and twenty-one consonants, though the letter &#39;y&#39; sometimes functions as a vowel. Frequent letters include e, t, a, o, and i, while less common ones are z, q, and x. English vocabulary incorporates words from numerous languages, including Latin, French, Greek, and German, making it lexically rich and diverse. Grammatically, English has simplified many of its inflectional forms compared to Old English, relying more on word order and auxiliary verbs to convey meaning. Sentence structure typically follows subject-verb-object pattern, though variations exist for questions and certain expressions. This representative text contains all the characteristic elements that create the unique statistical profile of the English language.
</code></pre>
<p><img src="Notes%20&%20Learning/Engineering/Cybersecurity/images/hidtogram-plain-eng.png" alt="english plain text histogram"></p>
<p>I can see that the letter &quot;E&quot; is the most frequent use comparing to &quot;A&quot; in polsih.</p>
<p><strong>Italian:</strong></p>
<pre><code class="language-shell">La lingua italiana appartiene al gruppo delle lingue romanze, derivata direttamente dal latino. È caratterizzata da una ricca sonorità e da un&#39;ampia gamma di vocali. L&#39;italiano utilizza ventiuno lettere dell&#39;alfabeto latino, con l&#39;aggiunta di cinque lettere straniere usate solo in parole di origine estera. Le vocali a, e, i, o, u sono molto frequenti e rappresentano una percentuale significativa del testo scritto. Tra le consonanti, le più comuni sono l, r, n, t e s. L&#39;italiano si distingue per la presenza di consonanti doppie che modificano la pronuncia e il significato delle parole. Gli accenti sono importanti e vengono usati principalmente sulle vocali finali, come à, è, ì, ò, ù. La struttura grammaticale include tre generi (maschile, femminile e neutro, quest&#39;ultimo limitato a pochi casi) e un sistema verbale complesso con numerosi tempi e modi. Il vocabolario italiano è stato arricchito nel corso dei secoli da influenze greche, arabe, spagnole e francesi. Questo testo rappresentativo contiene tutti gli elementi caratteristici che creano il profilo statistico unico della lingua italiana.
</code></pre>
<p><img src="Notes%20&%20Learning/Engineering/Cybersecurity/images/histogram-plain-ita.png" alt="italian plain text histogram"></p>
<hr>
<h3>4. Compare plaintext and cryptogram histograms depending on the algorithm (Caesar, Vigenere, Hill)</h3>
<p>I used polish text and here are histograms:</p>
<p><img src="Notes%20&%20Learning/Engineering/Cybersecurity/images/histogram-encrypt-cesar.png" alt="histogram caesar"></p>
<p><img src="Notes%20&%20Learning/Engineering/Cybersecurity/images/histogram-encrypt-vine.png" alt="histogram vigenere">
<img src="Notes%20&%20Learning/Engineering/Cybersecurity/images/histogram-encrypt-hill.png" alt="histogram hill"></p>
<p>The Caesar cipher preserves this distinctive pattern but shifts it along the alphabet, maintaining the same frequency peaks but at different letter positions.</p>
<p>More sophisticated encryption methods show progressively flatter distributions.</p>
<p>The Hill cipher displays a much more balanced frequency distribution than Caesar, though some letters still reach around 6% frequency.</p>
<p>The Vigenère cipher presents a histogram with frequencies mostly ranging between 2-6%, making it more resistant to frequency analysis than Caesar but still less secure than Hill, which demonstrates the most uniform distribution of the three encryption methods.</p>
<h2>That&#39;s enought for today</h2>
<p>Oh, I am tired, but I learned a lot:</p>
<ol>
<li>Humanity knew encryption algorithms even before our era.</li>
<li>The Caesar cipher is the simplest. It shift letters by a fixed amount. It&#39;s also the easiest to break</li>
<li>The Vigenère cipher uses a keyword to create multiple shifting alphabets, making it more secure than Caesar</li>
<li>The Hill cipher is the most complex, using matrices and linear algebra to encrypt blocks of text</li>
<li>Different languages have different entropy values, with Polish showing higher entropy than English and Italian in my tests</li>
<li>More advanced ciphers like Vigenère and Hill significantly increase the entropy of the plaintext compared to Caesar</li>
<li>Cryptool is a nice tool that allow for experimentation with these algorithms, though setup can be challenging on Mac (in the end I needed to use Windows because Histograms didn&#39;t work on my crazy wine-rosetta setup)</li>
<li>Implementing these algorithms in code helped me understanding of how they work</li>
<li>These historical algorithms are not secure to use in modern world. Anyway it was worth to studying about them</li>
<li>At first I thought this topic would be uninteresting for me, but when I got into it quite deeply I had a good time. (The coolest part was of course implementing ciphers in c#)</li>
</ol>
<h2>Sources</h2>
<p>During my studying I visited many places in the internet and also used Claude and Perplexity. Below I list all of places where I did read something:</p>
<ul>
<li><a href="https://www.cryptool.org/en">https://www.cryptool.org/en</a></li>
<li><a href="https://www.ibm.com/think/topics/cryptography">https://www.ibm.com/think/topics/cryptography</a></li>
<li><a href="https://en.wikipedia.org/wiki/Cipher">https://en.wikipedia.org/wiki/Cipher</a></li>
<li><a href="https://en.wikipedia.org/wiki/Encryption">https://en.wikipedia.org/wiki/Encryption</a></li>
<li><a href="https://en.wikipedia.org/wiki/Ciphertext">https://en.wikipedia.org/wiki/Ciphertext</a></li>
<li><a href="https://www.kaspersky.com/resource-center/definitions/what-is-cyber-security">https://www.kaspersky.com/resource-center/definitions/what-is-cyber-security</a></li>
<li><a href="https://crypto.stackexchange.com/questions/66127/what-is-an-accurate-definition-for-a-monoalphabetic-substitution-cipher-and-wh">https://crypto.stackexchange.com/questions/66127/what-is-an-accurate-definition-for-a-monoalphabetic-substitution-cipher-and-wh</a></li>
<li><a href="https://pages.mtu.edu/~shene/NSF-4/Tutorial/VIG/Vig-Base.html">https://pages.mtu.edu/~shene/NSF-4/Tutorial/VIG/Vig-Base.html</a></li>
<li><a href="https://www.alrasheedcol.edu.iq/modules/lect/lect/7051-Security%20lecture%2010(%20Hill%20).pdf">https://www.alrasheedcol.edu.iq/modules/lect/lect/7051-Security%20lecture%2010(%20Hill%20).pdf</a></li>
<li><a href="https://bluegoatcyber.com/blog/how-is-xor-used-in-encryption/">https://bluegoatcyber.com/blog/how-is-xor-used-in-encryption/</a></li>
</ul>
<p>Thanks for reading!</p>
<hr>
<p><em>Last updated: 22/03/2025</em> #Cybersecurity #ProgrammingFundamentals #CSharp #Shell/Bash #Git #Cryptool #Tutorial #DeepDive #ProjectSetup #Intermediate #HistoricalCryptography</p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Onboarding to coding after being a tech leader for 4 years]]></title>
            <description><![CDATA[I worked in the e-commerce industry for more than 10 years, starting from frontend development to leading teams. In the end, I have realized that instead of doing what I truly love- writing code that solves problems - I have spent my time on moderating meetings, filling sheets, and creating Jira tickets.]]></description>
            <link>https://frodigo.com/Blog/2025/Onboarding+to+coding+after+being+a+tech+leader+for+4+years</link>
            <guid isPermaLink="false">https://frodigo.com/Blog/2025/Onboarding+to+coding+after+being+a+tech+leader+for+4+years</guid>
            <pubDate>Thu, 27 Mar 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>I worked in the e-commerce industry for more than 10 years, starting from frontend development to leading teams. In the end, I have realized that instead of doing what I truly love- writing code that solves problems - I have spent my time on moderating meetings, filling sheets, and creating Jira tickets.</p>
<p>When my friend told me that Docplanner was looking for a <strong>software engineer</strong>, I started seriously considering returning to hands-on programming.</p>
<p>The position was interesting for me because I could:</p>
<ol>
<li>Return to <strong>hands-on development.</strong></li>
<li>Work on a fresh stack (.NET/C# instead of Node.js).</li>
<li>Solve problems in a completely new (for me) business domain — healthcare instead of e-commerce.</li>
</ol>
<p>I had nothing to lose. I made a new CV and sent it. The recruitment process was quite difficult for me, but I did it. I got a job offer and I accepted it.</p>
<p>But, to prepare better for the new challenges, I decided to take a <strong>month off</strong> from finishing my previous collaboration with Alokai to start a new one with Docplanner.</p>
<p><img src="https://miro.medium.com/v2/resize:fit:1400/1*vu5N_-8vVd5p0GM7aKujQg.png" alt="tech stack transition"></p>
<h2>Preparation</h2>
<p>I knew I needed to prepare. <strong>It was hard for me to code at all,</strong> even in well-known technologies that I worked with daily, like NodeJS/Vue.</p>
<p>It’s truly shocking how out of practice you can get after 3–4 years of being a tech/team leader.</p>
<p>So, I started to think — how to prepare best? For starters, I bought three books, but I actually didn’t read them — only one “Learn C# In One Day and Learn It Well”, but it was short.</p>
<p>Also, I tried a Udemy course but dropped it in the middle, as it was incredibly dull.</p>
<p>What worked like a charm? <strong>I just wrote some code in C#</strong>. That’s it. Also, I created a project in Claude that helped me understand C# features from a NodeJS perspective. Like this:</p>
<p>My message:</p>
<p><img src="https://miro.medium.com/v2/resize:fit:1400/1*Ed1vwqK-ipKGdsEiLZzZmQ.png" alt="chat"></p>
<p>AI assistant answer:</p>
<p><img src="https://miro.medium.com/v2/resize:fit:1400/1*Y7QBwYygNdGJk1WW--kCGA.png" alt="chat 2"></p>
<p>It also showed me implementation in JS:</p>
<p><img src="https://miro.medium.com/v2/resize:fit:1400/1*PW_CBTvUlMEFU4QbyARgRg.png" alt="chat 3"></p>
<p>Screenshots show a trivial example, but this assistant was helpful in learning. It helped me identify the gaps in my knowledge and adapt to the best practices. Or, at least, acceptable practices.</p>
<h2>Onboarding</h2>
<p>When I started at Docplanner, I was very warmly welcomed and received an impressive onboarding checklist. I had time to meet people and the team.</p>
<p>Then I started learning about the company, products, technology and history.</p>
<p>Actually, it was my first job in my whole career and <strong>nobody expected that I would write code from the first day</strong>. I had time for everything needed to onboard to the company. Also, I had a buddy who showed me a lot and answered all my questions.</p>
<h2>Observation</h2>
<p>After the first onboarding tasks, I felt I could start contributing. But, actually, I needed to hold my horses! I tried to understand the project, workflow and technology.</p>
<p>To do so, I have reviewed others&#39; code, pull requests. I did listen to daily standups and tried to understand what was going on.</p>
<p>One of the biggest topics for me was understanding the approach to “how to code” in .NET projects. As a NodeJS programmer, I was not used to patterns like clean architecture and CQRS. Adapting to these and other patterns/principles was a little bit tricky because it required a <strong>mental shift.</strong></p>
<h2>First contribution</h2>
<p>My first pull request was intentionally small. I added some improvements to the local Docker setup.</p>
<p>After this, I started working on my first feature. My approach was to create a PR, even it was 100% wrong, and get feedback from others and iterate.</p>
<p>It perfectly showed that <strong>in the engineering world, a pull request is the easiest form of communication.</strong></p>
<h2>Total fun</h2>
<p>After approximately one month, I started to feel quite confident and guess what? The most experienced engineer on the team began a three-week holiday. At the same time. We kicked off a new project. The timing couldn’t have been more challenging.</p>
<p>I found that I can use skills from my previous job as a tech leader.</p>
<ul>
<li>Breaking big problems into <strong>smaller ones</strong></li>
<li>Designing the tech aspects of a project</li>
<li>Finding the shortest way to <strong>prototype</strong>/MVP</li>
<li><strong>Explaining</strong> to others how the solution will work</li>
</ul>
<p>It helped me to start work on the new project as a backend engineer and to cooperate with a frontend developer. I felt we had good progress and what we do makes sense.</p>
<p><img src="https://miro.medium.com/v2/resize:fit:1400/1*n8HTiDAwRsEUgAM01O_-2A.png" alt="transferable skills"></p>
<h2>Expected-unexpected benefits of a new role</h2>
<p>On one hand benefits I describe below are expected, but on the other hand, when I realized them, I felt something like “<strong>AHA moment</strong>.” I told to myself, “WOW, it’s nice and it&#39;s something that I looked for. Why did I not change the job earlier?”</p>
<h3>1. Freedom from meetings</h3>
<p>Suddenly being an attendee rather than a facilitator was awesome. I hadn’t realized how much my mental energy moderating meetings had consumed.</p>
<h3>2. Focus on depth rather than breadth</h3>
<p>As a tech leader, I developed broad but shallow knowledge across many areas. When I return to an <strong>individual contributor</strong> role I could develop deep expertise in specific areas again — something I had missed.</p>
<h3>3. 100% technical independence</h3>
<p>I felt again that I have 100% influence on how to do something and how to solve the problem I am working on. I like this feeling when I know that solving the case is only <strong>a matter of time</strong> and <strong>going deep enough into the details.</strong></p>
<h2>Challanges</h2>
<p>The first one was about feeling like a junior. Despite my experience, sometimes it was hard to understand the code, the architecture on a code level, etc. Especially when s<strong>truggling with framework-specific issues</strong>.</p>
<p>I tried to prepare myself and learn some .NET and C# stuff, but the real learning starts when you have to write production-ready code under time pressure.</p>
<h2>What can I advise you on when you start a new job?</h2>
<p>(The order doesn’t matter; all tips are on the same level)</p>
<h3>- Build relationships</h3>
<p>Technology is important for engineers, but when you work in a team, <strong>People are more important</strong> (it’s not obvious for all of us, really).</p>
<p>I got advice that I should take my time and meet people. Actually, it was hard for me, but I spent time talking with people and typically these talks were very interesting!</p>
<p>Build relationships, this is how to onboard perfectly.</p>
<h3>- Prioritize code review</h3>
<p>I wanted to write some code. I thought I would be able to write code from the first day of work. I didn’t but I spent a lot of time <strong>reading others’ code</strong> and doing code review. I learned a lot.</p>
<p>Prioritize code review, this is how to onboard perfectly.</p>
<h3>- Start small</h3>
<p>I wanted to build big things and show how good I am. Nice idea, but a better idea is to start easy. <strong>Refactor</strong> some code. <strong>Improve</strong> dev tooling. <strong>Fix</strong> bugs. From time to time, do more complex things and grow organically.</p>
<p>Start small, this is how to onboard perfectly.</p>
<h3>- Be curious</h3>
<p>When I saw .Net code, I didn’t understand too much. When you look at something new, you can have the same feeling. You can like something and not like it. And you have two options: complain or be curious. <strong>Ask “why?” Go deeper. Ask “why” again and again.</strong></p>
<p>Be curious, this is how to onboard perfectly.</p>
<h3>- Be patient</h3>
<p>Patience is a useful virtue in both life and coding. People also says that Rush is a bad advisor. You can rush and scrap the production. You can be patient and be sure not to screw up. You can wait for the code review. You can tune in to someone and at least have time to meet. <strong>Patience does make sense.</strong></p>
<p>Be patient, this is how to onboard perfectly.</p>
<h3>- Be nice</h3>
<p>Last but not least (I hate this sentence. Actually, why do I use it here???) Be a nice person. <strong>Nobody wants to work with jerks</strong>. Be respectful, and think three times before you say something stupid. Listen more than talk.</p>
<p>Be nice, this is how to onboard perfectly.</p>
<h2>Conclusion</h2>
<p>Onboarding into a new job and role can be demanding. Each of us goes through it individually and has our own needs and challenges.</p>
<p>What is definitely important is our attitude. It is good to use this new beginning as an opportunity to meet new people, learn something different, and look at something familiar from a new perspective. <strong>I hope that as you go through your onboarding, you use it in this way.</strong></p>
<hr>
<p><em>Published: 03/27/2025</em> #blog #CareerTransitions #SoftwareEngineering #TechLeadership #ProfessionalDevelopment #OnboardingProcess</p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Caesar Cipher Implementation in C Sharp]]></title>
            <description><![CDATA[Let&#39;s start from theory]]></description>
            <link>https://frodigo.com/Blog/2025/Caesar+Cipher+Implementation+in+C+Sharp</link>
            <guid isPermaLink="false">https://frodigo.com/Blog/2025/Caesar+Cipher+Implementation+in+C+Sharp</guid>
            <pubDate>Wed, 26 Mar 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>In this tutorial I&#39;ll show you how to implement a Caesar cipher in C# using traditional TDD approach.
You can learn two things. How:</p>
<ol>
<li>To code in TDD</li>
<li>Caesar cipher works</li>
</ol>
<p>Let&#39;s start from theory</p>
<h2>Caesar cipher</h2>
<p>The Caesar cipher is one of the earliest and simplest encryption techniques. It was used by Julius Caesar for his private correspondence.</p>
<p>It works by shifting each letter in the plaintext by a fixed number of positions in the alphabet.</p>
<p>Key features:</p>
<ul>
<li>Encrypt text using a specified shift value</li>
<li>Decrypt text using the same shift value</li>
<li>Preserve case (uppercase/lowercase) during encryption/decryption</li>
<li>Handle wraparound at alphabet boundaries (Z → A)</li>
<li>Preserve non-alphabetic characters (spaces, numbers, punctuation)</li>
<li>Support for negative shift values</li>
</ul>
<h3>How Caesar cipher works</h3>
<p>I&#39;ll show you how encryption and decryption process works in this cipher.</p>
<h4>Encryption process</h4>
<ol>
<li>Take each letter in the plaintext message</li>
<li>Shift it forward in the alphabet by the specified amount (the key)</li>
<li>Wrap around from Z to A if necessary</li>
<li>Non-alphabetic characters remain unchanged</li>
</ol>
<h4>Decryption process</h4>
<ol>
<li>Take each letter in the encrypted message</li>
<li>Shift it backward in the alphabet by the same amount</li>
<li>Wrap around from A to Z if necessary</li>
<li>Non-alphabetic characters remain unchanged</li>
</ol>
<pre><code class="language-mermaid">flowchart TD
    A[Start] --&gt; B[Take each letter]
    B --&gt; C{Is it a letter?}
    C --&gt;|Yes| D[Determine case]
    C --&gt;|No| E[Keep unchanged]
    D --&gt;|Uppercase| F[Shift from &#39;A&#39;]
    D --&gt;|Lowercase| G[Shift from &#39;a&#39;]
    F --&gt; H[Apply shift]
    G --&gt; H
    H --&gt; I[Handle wraparound]
    I --&gt; J[Add to result]
    E --&gt; J
    J --&gt; K{More letters?}
    K --&gt;|Yes| B
    K --&gt;|No| L[End]
</code></pre>
<h2>Let&#39;s code</h2>
<p>It&#39;s quite simple, isn&#39;t it? Good for us, we can implement in quickly!</p>
<h2>Step 1: write initial tests</h2>
<p>Start by creating a test project and writing failing tests for the simplest behavior:</p>
<ol>
<li>Create the test project structure:</li>
</ol>
<pre><code class="language-bash">dotnet new xunit -o CesarCipherTests
dotnet add CesarCipherTests/CesarCipherTests.csproj reference CesarCipherLib/CesarCipherLib.csproj
</code></pre>
<ol>
<li>Write the first failing test:</li>
</ol>
<pre><code class="language-csharp">using Xunit;
using CesarCipherLib;

namespace CesarCipherTests;

public class CesarCipherTests
{
    [Fact]
    public void When_EncryptingLetterA_WithShiftOf1_ShouldReturnLetterB()
    {
        // Arrange
        var cipher = new CesarCipher();

        // Act
        var result = cipher.Encrypt(&quot;A&quot;, 1);

        // Assert
        Assert.Equal(&quot;B&quot;, result);
    }
}
</code></pre>
<p>Running this test will fail since the implementation doesn&#39;t exist yet.</p>
<h2>Step 2: Implement minimal code</h2>
<p>Now, implement just enough code to make the test pass:</p>
<ol>
<li>Create the library project and class:</li>
</ol>
<pre><code class="language-bash">dotnet new classlib -o CesarCipherLib
</code></pre>
<ol>
<li>Implement the minimal <code>CesarCipher</code> class:</li>
</ol>
<pre><code class="language-csharp">namespace CesarCipherLib;

public class CesarCipher
{
    public string Encrypt(string text, int shift)
    {
        if (string.IsNullOrEmpty(text))
            return string.Empty;

        if (text == &quot;A&quot; &amp;&amp; shift == 1)
            return &quot;B&quot;;

        return text;
    }
}
</code></pre>
<p>In the very first implementation we need to do something simple to see green tests.</p>
<p>Running the test now should pass.</p>
<h3>Step 3: Add more tests and expand implementation</h3>
<p>We can go further by adding more tests cases and improve our implementation.</p>
<ol>
<li>Add the next test for incremental functionality:</li>
</ol>
<pre><code class="language-csharp">[Fact]
public void When_EncryptingLetterB_WithShiftOf1_ShouldReturnLetterC()
{
    var cipher = new CesarCipher();
    var result = cipher.Encrypt(&quot;B&quot;, 1);
    Assert.Equal(&quot;C&quot;, result);
}
</code></pre>
<ol>
<li>Refine the implementation to handle this case:</li>
</ol>
<pre><code class="language-csharp">public string Encrypt(string text, int shift)
{
    if (string.IsNullOrEmpty(text))
        return string.Empty;

    var result = new System.Text.StringBuilder();

    foreach (char c in text)
    {
        if (char.IsUpper(c))
        {
            char shifted = (char)(((c - &#39;A&#39; + shift) % 26) + &#39;A&#39;);
            result.Append(shifted);
        }
        else
        {
            result.Append(c);
        }
    }

    return result.ToString();
}
</code></pre>
<p>Note that this implementation only shifts uppercase letters and leaves all other characters (lowercase letters, numbers, symbols, etc.) unchanged.</p>
<ol>
<li>Add a test for alphabet wraparound:</li>
</ol>
<pre><code class="language-csharp">[Fact]
public void When_EncryptingLetterZ_WithShiftOf1_ShouldReturnLetterA()
{
    var cipher = new CesarCipher();
    var result = cipher.Encrypt(&quot;Z&quot;, 1);
    Assert.Equal(&quot;A&quot;, result);
}
</code></pre>
<ol>
<li>Continue with tests for lowercase letters:</li>
</ol>
<pre><code class="language-csharp">[Fact]
public void When_EncryptingLowercaseA_WithShiftOf1_ShouldReturnLowercaseB()
{
    var cipher = new CesarCipher();
    var result = cipher.Encrypt(&quot;a&quot;, 1);
    Assert.Equal(&quot;b&quot;, result);
}
</code></pre>
<ol>
<li>Expand the implementation to handle both cases:</li>
</ol>
<pre><code class="language-csharp">public string Encrypt(string text, int shift)
{
    if (string.IsNullOrEmpty(text))
        return string.Empty;

    var result = new System.Text.StringBuilder();

    foreach (char inputChar in text)
    {
        if (char.IsLetter(inputChar))
        {
            bool isLower = char.IsLower(inputChar);
            char baseChar = isLower ? &#39;a&#39; : &#39;A&#39;;

            int normalizedChar = (inputChar - baseChar + shift) % 26;
            if (normalizedChar &lt; 0) normalizedChar += 26;

            result.Append((char)(baseChar + normalizedChar));
        }
        else
        {
            result.Append(inputChar);
        }
    }

    return result.ToString();
}
</code></pre>
<h3>Step 4: Implement decryption</h3>
<ol>
<li>Add a test for decryption functionality:</li>
</ol>
<pre><code class="language-csharp">[Fact]
public void When_DecryptingEncryptedText_ShouldReturnOriginalText()
{
    var cipher = new CesarCipher();
    var plainText = &quot;Hello, World!&quot;;
    var shift = 5;
    var encryptedText = cipher.Encrypt(plainText, shift);

    var decryptedText = cipher.Decrypt(encryptedText, shift);

    Assert.Equal(plainText, decryptedText);
}
</code></pre>
<ol>
<li>Implement the <code>Decrypt</code> method:</li>
</ol>
<pre><code class="language-csharp">public string Decrypt(string text, int shift)
{
    // Decryption is just encryption with the negative shift
    return Encrypt(text, -shift);
}
</code></pre>
<h3>Step 5: Handle edge cases</h3>
<ol>
<li>Add tests for edge cases:</li>
</ol>
<pre><code class="language-csharp">[Fact]
public void When_EncryptingLetterA_WithShiftOfMinus1_ShouldReturnLetterZ()
{
    var cipher = new CesarCipher();
    var result = cipher.Encrypt(&quot;A&quot;, -1);
    Assert.Equal(&quot;Z&quot;, result);
}

[Fact]
public void When_EncryptingEmptyString_ShouldReturnEmptyString()
{
    var cipher = new CesarCipher();
    var result = cipher.Encrypt(&quot;&quot;, 1);
    Assert.Equal(&quot;&quot;, result);
}

[Fact]
public void When_EncryptingWithShiftOf0_ShouldReturnOriginalText()
{
    var cipher = new CesarCipher();
    var text = &quot;HELLO&quot;;
    var result = cipher.Encrypt(text, 0);
    Assert.Equal(text, result);
}
</code></pre>
<ol>
<li>Make sure your implementation handles all these cases correctly.</li>
</ol>
<h3>Step 6: Refactor and finalize</h3>
<ol>
<li><p>Review your implementation for any potential improvements while keeping all tests passing.</p>
</li>
<li><p>Create a simple console application to demonstrate the functionality:</p>
</li>
</ol>
<pre><code class="language-csharp">using CesarCipherLib;

namespace CesarCipherDemo;

public class Program
{
    public static void Main(string[] args)
    {
        Console.WriteLine(&quot;=== Caesar Cipher Demo ===&quot;);

        var cipher = new CesarCipher();

        // Example usage
        string message = &quot;HELLO WORLD&quot;;
        int shift = 3;
        string encrypted = cipher.Encrypt(message, shift);

        Console.WriteLine($&quot;Original: {message}&quot;);
        Console.WriteLine($&quot;Encrypted: {encrypted}&quot;);
        Console.WriteLine($&quot;Decrypted: {cipher.Decrypt(encrypted, shift)}&quot;);
    }
}
</code></pre>
<h3>Final verification</h3>
<ol>
<li>Run the test suite to verify all functionality:</li>
</ol>
<pre><code class="language-bash">dotnet test CesarCipherTests
</code></pre>
<ol>
<li>Run the sample application to demonstrate the cipher in action:</li>
</ol>
<pre><code class="language-bash">dotnet run
</code></pre>
<p>That&#39;s all!</p>
<h2>Summary</h2>
<p>By following this TDD approach, we&#39;ve created a Caesar Cipher implementation that:</p>
<ul>
<li>Preserves letter case</li>
<li>Handles special characters</li>
<li>Supports negative shifts</li>
<li>Properly wraps around the alphabet</li>
<li>Is thoroughly tested against various edge cases</li>
</ul>
<h2>Links</h2>
<ul>
<li><a href="https://github.com/Frodigo/garage/blob/main/sandbox/cybersecurity/cryptography/HistoricalCiphersDemo/Ciphers/Caesar/CaesarCipher.cs">https://github.com/Frodigo/garage/blob/main/sandbox/cybersecurity/cryptography/HistoricalCiphersDemo/Ciphers/Caesar/CaesarCipher.cs</a></li>
<li><a href="https://github.com/Frodigo/garage/blob/main/sandbox/cybersecurity/cryptography/HistoricalCiphersDemo/Tests/CaesarCipherTests.cs">https://github.com/Frodigo/garage/blob/main/sandbox/cybersecurity/cryptography/HistoricalCiphersDemo/Tests/CaesarCipherTests.cs</a></li>
<li><a href="https://github.com/Frodigo/garage/tree/main/sandbox/cybersecurity/cryptography/HistoricalCiphersDemo">https://github.com/Frodigo/garage/tree/main/sandbox/cybersecurity/cryptography/HistoricalCiphersDemo</a></li>
</ul>
<hr>
<p><em>Published at: 26/03/2025</em> #blog #ProgrammingFundamentals #Cybersecurity #CSharp  #CSharp #xUnit #DotNet #Tutorial #ConceptExplanation #Intermediate #AutomatedTesting</p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Blog]]></title>
            <description><![CDATA[#blog]]></description>
            <link>https://frodigo.com/x/templates/Blog+template</link>
            <guid isPermaLink="false">https://frodigo.com/x/templates/Blog+template</guid>
            <pubDate>Sun, 09 Feb 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<hr>
<p>#blog</p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How and Why I Moved My Blog from WordPress to Astro and Markdown]]></title>
            <description><![CDATA[I love writing and have been publishing articles on my blog for several years while constantly working on improving my writing skills. At the same time, as an engineer, I tend to (perhaps unnecessarily) pay attention to the technical aspects of my blog. Initially, my site was a simple WordPress setup. Over time, I expanded it to include support for two languages and the Elementor plugin for easy visual editing of the blog’s layout.]]></description>
            <link>https://frodigo.com/Blog/2025/How+and+why+I+moved+my+blog+from+WordPress+to+Astro+and+Markdown</link>
            <guid isPermaLink="false">https://frodigo.com/Blog/2025/How+and+why+I+moved+my+blog+from+WordPress+to+Astro+and+Markdown</guid>
            <pubDate>Wed, 15 Jan 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>I love writing and have been publishing articles on my blog for several years while constantly working on improving my writing skills. At the same time, as an engineer, I tend to (perhaps unnecessarily) pay attention to the technical aspects of my blog. Initially, my site was a simple WordPress setup. Over time, I expanded it to include support for two languages and the Elementor plugin for easy visual editing of the blog’s layout.</p>
<p>Eventually, this setup became overly complex, and my enjoyment of blogging declined. I decided to abandon WordPress and migrate my blog to a static site built with the Astro framework. In today’s article, I’ll share the journey I took and explain why Astro is an excellent choice for blogging, especially for programmers and web developers. I’ll also show you how I quickly and automatically imported all my WordPress posts into static Markdown files.</p>
<h2>When WordPress Becomes a Burden</h2>
<p>WordPress is suitable for 99% o==f== people. It worked for me too, but as I added more plugins, it started running slower and slower.</p>
<p>I used Elementor for customizing my site, but honestly, as a web developer, it’s much easier for me to customize the site’s appearance by editing HTML and CSS rather than using a visual editor.</p>
<p>The growing number of plugins in WordPress negatively impacted the site’s speed for end-users, and I wanted it to be lightning-fast. Plugins also require regular updates. While updates are theoretically automatic, they don’t always work as expected. Once, I updated Elementor, and my site stopped working, costing me several hours of troubleshooting and support calls.</p>
<p>With a large number of posts, the WordPress admin panel became sluggish, and finding anything was a chore. The bilingual setup only made it more cumbersome.</p>
<p>Another factor is that WordPress requires a hosting plan with a database, which incurs costs. In contrast, Astro and Markdown enable a static site, which costs me next to nothing to host. Since I currently earn no income from blogging, eliminating hosting and plugin expenses is a significant advantage.</p>
<p>WordPress also required me to monitor uptime since it’s a dynamically rendered site (server-side rendering). Despite configuring a CDN, there was still occasional downtime.</p>
<p>What finally pushed me to migrate from WordPress to Markdown and Astro was my growing frustration with editing posts in WordPress. It might sound trivial, but the need to log into the panel (requiring an internet connection) started to annoy me. Around that time, I began using Obsidian for note-taking and loved its offline functionality and simple Markdown files. With Obsidian, I can quickly jot something down, which wasn’t the case with WordPress.</p>
<h2>The First Attempt at Migration</h2>
<p>While using Obsidian, I discovered an intriguing YouTuber named <strong>Lazar Nikolov</strong>, who demonstrated how to integrate Obsidian with Astro (links to his videos at the end). I had already heard about Astro as a compelling meta-framework for building content-focused websites and apps.</p>
<p>Within a few minutes, I had a proof of concept for a site on Astro with a sample page and my first “Hello World” post. The initial experience was fantastic, but my past experiences taught me to temper my enthusiasm and look for potential weaknesses in the setup.</p>
<p>I had many posts on my blog and worried that I’d need to migrate them manually. Copying the first post from WordPress to Markdown was a nightmare — it required reformatting everything and downloading images separately.</p>
<p>Markdown resonates with me as a programmer. But was that enough of a reason to endure this pain? No.</p>
<p>My blog had around 50 posts in two languages. Some were so outdated that I decided not to migrate them, but I still had about 75 posts to move.</p>
<p>I searched for a WordPress plugin to convert posts to Markdown but couldn’t find anything useful. The thought of manual migration made me want to abandon the process and stick with WordPress.</p>
<h2>A Ray of Hope</h2>
<p>I kept searching and discovered a tool that converts WordPress-exported XML content into Markdown:</p>
<p><a href="https://github.com/lonekorean/wordpress-export-to-markdown">https://github.com/lonekorean/wordpress-export-to-markdown</a></p>
<p>A huge thanks to the creators of this tool — it likely saved me dozens of hours of tedious work.</p>
<p>All I had to do was export my WordPress content using the native export feature and run <strong>npx wordpress-export-to-markdown</strong> from the command line. Magic ensued!</p>
<p>The tool generated Markdown files, complete with images, ready for use in my Astro project. I only needed to manually add programming language specifications for code blocks and slightly adjust each post’s properties to match the schema in Astro. Incredible!</p>
<h2>Building a New Tech Stack</h2>
<p>Come along — I’ll show you the technologies I used for the new implementation.</p>
<h3>Markdown</h3>
<p>As mentioned, I use Obsidian for notes and store all my knowledge there. Transitioning to Markdown simplifies content creation and development for my blog. Thus, the heart of my tech stack, ladies and gentlemen, is: <strong>Markdown</strong>.</p>
<p>While WordPress is great for blogging and Notion excels at note-taking, I value simplicity. That’s why I chose Markdown — it works perfectly for both note-taking and blogging.</p>
<h3>Rendering and Running the Application</h3>
<p>I needed a solution to render my Markdown files. While exploring different frameworks for building blogs, I stumbled upon <strong>Astro</strong>. Initially, I was skeptical — another framework for building websites? But after some analysis and testing, I can confidently say that it’s a fantastic tool, especially for developers running a blog. Here’s why:</p>
<h3>Speed and Performance</h3>
<p>Astro renders pages server-side and sends the minimal amount of JavaScript to the browser. This translates to lightning-fast page load times. My blog on Astro loads in an instant.</p>
<h3>Flexibility Without Compromise</h3>
<p>You can use your favorite tools — React, Vue, or Svelte. Personally, I use <strong>Vue</strong> for more interactive components, while the rest of the site is pure HTML. Thanks to Astro’s island architecture, JavaScript loads only where needed.</p>
<h3>Excellent Content Support</h3>
<p>Astro has built-in support for Markdown and MDX. I write posts in Markdown using Obsidian, and it all works out of the box. This allows me to focus on writing without worrying about technical barriers.</p>
<h3>SEO Benefits</h3>
<p>Static page generation and minimal JavaScript are a foundation for great SEO. My blog on Astro scores high in Lighthouse without any additional optimization efforts.</p>
<h3>Multilingual Support</h3>
<p>On WordPress, I used the paid WPML plugin for multilingual support. In Astro, I wrote a few lines of code to achieve the same functionality — for free. I implemented the structure so that the base domain serves content in English, while the <code>/pl</code> subdirectory handles Polish content. The folder structure is as follows:</p>
<pre><code class="language-bash">- posts/
 - English posts
 - pl/
 - Polish posts
</code></pre>
<p>These are my two simple functions for detecting the language and retrieving static translations:</p>
<pre><code class="language-typescript">export function getLangFromUrl(url: URL) {
 const [, lang] = url.pathname.split(&quot;/&quot;);
 if (lang in ui) return lang as keyof typeof ui;
 return defaultLang;
}

export function useTranslations(lang: keyof typeof ui) {
 return function t(key: keyof (typeof ui)[typeof defaultLang]) {
 return ui[lang][key] || ui[defaultLang];
 };
}
</code></pre>
<p>I store translations in a simple file:</p>
<pre><code class="language-json">&quot;header.openMenu&quot;: &quot;Open main menu&quot;, // English
&quot;header.openMenu&quot;: &quot;Otwórz menu główne&quot;, // Polish
</code></pre>
<p>These can be easily used in the project:</p>
<pre><code class="language-javascript">- -
const lang = getLangFromUrl(Astro.url);
const t = useTranslations(lang);
 - -
&lt;span&gt;{t(&quot;header.openMenu&quot;)}&lt;/span&gt;
</code></pre>
<p>This setup works flawlessly — for free. Additionally, I use Astro Islands, so some Vue components are included where needed, and this function integrates seamlessly with them.</p>
<p>On WordPress, WPML translations were not only paid but also offered a mediocre experience. With Astro, I can translate Markdown files entirely using any AI tool, making the process more efficient.</p>
<h3>Deployment</h3>
<p>In WordPress, deployment was automatic — changes appeared as soon as I saved them in the admin panel.</p>
<p>In my new implementation, the project is built upon pushing changes to the <code>main</code> branch. The built project is deployed as a static site on <strong>Vercel</strong>. The build process is fast, although I understand it may slow down as content grows — a reasonable trade-off.</p>
<h3>Testing</h3>
<p>For testing, I used <strong>Playwright</strong>. Since I don’t have much to test, I’ve only written tests for the newsletter subscription functionality so far. However, even this small step forward feels significant compared to WordPress, where the only testing was manual and on production.</p>
<h3>Diagrams</h3>
<p>In <strong>Obsidian</strong>, diagrams work out of the box with <strong>Mermaid.js</strong>. Integrating Mermaid with Astro was equally straightforward, enabling me to write diagram code that’s automatically rendered as SVGs. I even customized the appearance of these diagrams to match my blog’s theme with just a few lines of configuration and the <code>remark-mermaid</code> plugin:</p>
<pre><code class="language-json">markdown: {
  remarkPlugins: [
    [
      remarkMermaid,
      {
        mermaidConfig: {
          theme: &quot;dark&quot;,
          look: &quot;handDrawn&quot;,
          themeCSS: &quot;.flowchart { margin-right: 30px; }&quot;,
          themeVariables: {
            fontSize: &quot;18px&quot;,
            darkMode: true,
            nodeBorder: &quot;#A130E7&quot;,
            mainBkg: &quot;#0a1929&quot;,
            nodeTextColor: &quot;#fff&quot;,
          },
        },
      },
    ],
  ],
},
</code></pre>
<h3>Frontend Technologies</h3>
<h3>Alpine.js vs. Vue.js</h3>
<p>Initially, I used Alpine.js for client-side functionality like toggling navigation menus or validating forms. While it worked for simple tasks, I struggled with more complex requirements. Lacking prior experience with Alpine.js, learning it became a chore.</p>
<p>Eventually, I leveraged Astro Islands and switched to Vue.js, a framework I’m highly experienced with, for browser-side functionality.</p>
<h3>Tailwind CSS vs. Sass</h3>
<p>At first, I styled the app with Tailwind CSS. However, over time, I switched to Sass. Tailwind’s approach to CSS didn’t resonate with me — I prefer separating structure (HTML) from appearance (CSS). This way, I can add CSS classes in HTML and define their properties in the <code>&lt;style&gt;</code> section, making the code more readable for me.</p>
<h2>Lessons Learned from the Migration</h2>
<h3>1. Lightning-Fast Performance</h3>
<p>Static sites built with Astro are significantly faster and score higher in PageSpeed Insights. Without additional optimization, my site achieves an 80/100 score on mobile, compared to 30 on WordPress (even after optimizations).</p>
<h3>2. Markdown Simplicity</h3>
<p>Editing content in Markdown is far easier than in WordPress. Finding and modifying anything is a breeze, especially with tools like VS Code, which allow global search and replace. Even with the additional steps of committing, pushing changes, and waiting for the build, the process feels more efficient than WordPress.</p>
<h3>3. Cost Reduction</h3>
<p>I no longer pay for hosting, a database, uptime checks, or WordPress plugins. A static site with my level of traffic incurs almost no costs and ensures 100% uptime.</p>
<h3>4. Seamless Integration with Obsidian</h3>
<p>This new approach tightly integrates my blog with my knowledge and notes stored in Obsidian, making content creation easier and more enjoyable.</p>
<h3>5. Unexpected Benefits</h3>
<p>With everything in Git, I have a complete history of my work, which motivates me and provides a sense of accomplishment. Additionally, I can work offline and publish whenever I have an internet connection — a blessing for anyone familiar with the reliability of Polish trains.</p>
<h3>Challenges</h3>
<p>In WordPress, there’s a plugin for everything, whereas in Astro, you often need to implement solutions yourself. Fortunately, most things require a one-time setup.</p>
<p>Collaboration might also be easier on WordPress. If I wanted someone else to contribute content, it would be simpler to find someone familiar with WordPress, as no technical knowledge is required to write there.</p>
<h2>Conclusion</h2>
<p>In this article, I shared my journey of migrating my blog from WordPress to Astro, and how Markdown simplified my content creation process. I discussed the challenges I faced with WordPress, from slow performance and editing frustrations to hosting and plugin costs. Then, I detailed how I streamlined the migration process with the right tools.</p>
<p>Static sites built with Astro are fast, cost-effective, and provide greater control over content. Integration with Obsidian further enhances the workflow, making it a fantastic option for tech enthusiasts who value simplicity, speed, and flexibility.</p>
<p><em>If you enjoyed this post, follow me for more articles. I’d love to hear about your experiences and perspectives in the comments!</em></p>
<h2>Links</h2>
<ul>
<li><a href="https://www.youtube.com/watch?v=33Mk-KrWklU&t=610s">https://www.youtube.com/watch?v=33Mk-KrWklU&t=610s</a></li>
<li><a href="https://www.youtube.com/watch?v=dz3GOp4hN50&t=357s">https://www.youtube.com/watch?v=dz3GOp4hN50&t=357s</a></li>
<li><a href="https://github.com/lonekorean/wordpress-export-to-markdown](https://github.com/lonekorean/wordpress-export-to-markdown?tab=readme-ov-file%29">https://github.com/lonekorean/wordpress-export-to-markdown](https://github.com/lonekorean/wordpress-export-to-markdown?tab=readme-ov-file%29</a></li>
<li><a href="https://github.com/remcohaszing/remark-mermaidjs">https://github.com/remcohaszing/remark-mermaidjs</a></li>
<li><a href="https://obsidian.md/">https://obsidian.md/</a></li>
<li><a href="https://astro.build/">https://astro.build/</a></li>
<li><a href="https://playwright.dev/">https://playwright.dev/</a></li>
<li><a href="https://alpinejs.dev/">https://alpinejs.dev/</a></li>
<li><a href="https://marcinkwiatkowski.com/blog">https://marcinkwiatkowski.com/blog</a></li>
</ul>
<hr>
<p><em>Published: 15/01/2025</em>  #blog #astrojs #WebDevelopment #WordpressAlternatives #obsidian</p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How Saying ‘I Don’t Know’ Can Make You a Better Software Engineer]]></title>
            <description><![CDATA[For several years, I’ve been working remotely, and one of the most annoying things in my work are certain types of meetings. Imagine a situation where a Product Owner/Engineering Manager/anyone else who makes decisions in your project organizes a meeting. There’s no agenda; you can only guess from the meeting name, which usually looks like “something sync”. You can’t deduce anything from it.]]></description>
            <link>https://frodigo.com/Blog/2025/How+saying+‘I+don’t+know’+can+make+You+a+better+Software+Engineer</link>
            <guid isPermaLink="false">https://frodigo.com/Blog/2025/How+saying+‘I+don’t+know’+can+make+You+a+better+Software+Engineer</guid>
            <pubDate>Tue, 07 Jan 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>For several years, I’ve been working remotely, and one of the most annoying things in my work are certain types of meetings. Imagine a situation where a Product Owner/Engineering Manager/anyone else who makes decisions in your project organizes a meeting. There’s no agenda; you can only guess from the meeting name, which usually looks like “something sync”. You can’t deduce anything from it.</p>
<p>Of course, you could try to ask about the agenda beforehand, but today I want to focus on such a hopeless case. There’s a meeting, nobody except the organizer knows why this meeting is happening, and the entire development team is there. The meeting is scheduled for 30 minutes.</p>
<p>At the beginning, there’s small talk, of course. I’m not joking here — small talk is important — if there’s no small talk, it means the atmosphere in the team and at the meeting isn’t good.</p>
<h2>The 100-Point Question</h2>
<p>Then the organizer starts talking about something, usually gives an introduction, and then fires away with questions like:</p>
<ul>
<li>I came up with feature FOO, how much time do you need for this?</li>
<li>Functionality BAR is slow, what can you do to improve it?</li>
<li>Why might users not notice the value of this feature? How can we change that?</li>
<li>If we had to cut one thing from the roadmap, what would it be and why</li>
<li>We need to show this functionality in today’s demo, but it’s not in production yet. Can we quickly finish and show it?</li>
</ul>
<p>These are questions that aren’t easy to answer, even if you ask questions to clarify the problem.</p>
<h2>Answering Under Time Pressure</h2>
<p>The problem arises when the questioner expects an immediate answer. I’ve often seen people (or myself) trying to answer such questions under pressure in the shortest possible time.</p>
<p>You can answer intuitively or based on your experience. Then the <em>boss</em> is happy, and the train (project) keeps moving forward.</p>
<p>But what first comes to mind isn’t always the best. You can’t always solve a problem in fifteen minutes. Sometimes you need time to investigate the matter.</p>
<p>Have you ever said something under pressure and later regretted it? I have, and it’s a terrible feeling.</p>
<p>I’ve worked with many engineers, and many of them, especially the ambitious ones, don’t want to admit they don’t know something or don’t want to be blockers and slow things down. Sometimes they’re afraid to speak up for some reason, or in other cases, they don’t want to look bad in front of others.</p>
<h2>What If We Gave Ourselves More Time?</h2>
<p>Instead of saying whatever comes to mind, simply say “I don’t know”. You can explain why, but just “I don’t know” should be enough in an organization that isn’t broken.</p>
<p>Because you know what? It’s obvious that sometimes you need more time to answer a question. None of us knows everything, and projects are becoming more demanding, and we’re increasingly using new technologies, even ones that didn’t exist a few years ago.</p>
<p>Besides, meetings without agendas, ad hoc meetings are a nightmare. There isn’t even time to prepare because you don’t know what for. When we say “I don’t know”, the questioner might be unhappy, but if you’re a specialist and still don’t know, you need to stand firm until the questioner understands.</p>
<p>A wise <em>boss</em> will understand.</p>
<p>They’ll ask what’s needed to be able to answer the question. They’ll listen to your objections, ask about challenges, start a dialogue.</p>
<h2>You’re Not Alone</h2>
<p>Remember, even if you don’t know something, your teammate might know and can help you. You work as a team, and each of us should support others. Be a team.</p>
<p>If you’re afraid to say “I don’t know” because you think you’ll delay the project, I’ll tell you that’s total nonsense. Saying something thoughtlessly is worse than saying “I don’t know” and can lead to doing something stupid as a team.</p>
<p>And usually, when we do stupid things mindlessly, we waste more time than if we had said “I don’t know” and taken time for research.</p>
<h2>“I Don’t Know” Prevents Mindless Action</h2>
<p>When you say “I don’t know”, there’s a chance you’ll have more time to dive into the topic to investigate and find an answer. You’ll say “no” to mindless action, to later say “yes” to work that will bring real value to your project.</p>
<p>Space will appear for doing things that are needed and well-thought-out. Space for discussion will appear.</p>
<p>Collaboration will appear.</p>
<h2>A World of Not-Knowing is Better Than a World of Ignorance</h2>
<p>Someone who has a ready answer for everything is ignorant. I’m afraid of such people. I’ve seen how such “wise men” destroyed projects with thoughtless decisions. Do you want to be another wise man, or a conscious engineer who thinks about how to really do valuable things and is a long-term partner for business?</p>
<p>If so, then simply don’t be afraid to say “I don’t know” and give yourself time to think. Talk about your objections and doubts. Also try to propose some action, like needing a few days for research and coming back with an answer afterward.</p>
<h2>Conclusion</h2>
<p>Thanks to “I don’t know”, you’ll have more time to understand the problem and influence your project to be better in the long term.</p>
<p>Not knowing is human. Be curious and you’ll learn.</p>
<p>==Of course, sometimes it’s worth trusting your intuition and experience.
But remember that “I don’t know” is an answer you shouldn’t be ashamed of.==</p>
<p><em>If you liked this article, share it with a colleague and follow me to see more like it.</em></p>
<hr>
<p><em>Published: 07/01/2025</em>  #blog #SoftwareEngineering #Leadership #EngineeringMangement #TeamCollaboration #SelfImprovement</p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Monolith vs Microservices - Pros and Cons and How to Approach Transformation]]></title>
            <description><![CDATA[<em>Last updated: 17/12/2024</em>]]></description>
            <link>https://frodigo.com/Blog/Archive/Frontend/Software+architecture/Monolith+vs+Microservices+-+Pros+and+Cons+and+How+to+Approach+Transformation</link>
            <guid isPermaLink="false">https://frodigo.com/Blog/Archive/Frontend/Software+architecture/Monolith+vs+Microservices+-+Pros+and+Cons+and+How+to+Approach+Transformation</guid>
            <pubDate>Tue, 17 Dec 2024 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p><em>Last updated: 17/12/2024</em></p>
<h2>The Good Old Monolith</h2>
<pre><code class="language-mermaid">flowchart LR
    subgraph Monolith
        A[UI] --&gt; B[Single Application]
        B --&gt; C[Single Database]
    end
</code></pre>
<p>I&#39;ve worked with various system architectures for many years and always wonder why some solutions work well in certain organizations but not in others. A monolith, or traditional application architecture, has much in common with a classic car. Simple engine, minimal electronics, everything is well-known and easy to understand. When a gasoline engine in a classic car won&#39;t start, it usually comes down to two reasons:</p>
<ol>
<li>no fuel</li>
<li>no electricity/spark</li>
</ol>
<p>(note: I&#39;m greatly simplifying here, but this isn&#39;t the place for in-depth analysis of how engines work)</p>
<p>In a monolith, you have one database, one deployment, one team. It&#39;s like a car that any mechanic can diagnose and fix. Debugging is simple - everything is in one place. Database transactions work reliably. Deployment is like taking a walk - simple and predictable.</p>
<p>The problem is that this car, like a monolith, has its limitations. No parking sensors, cameras, conveniences or driving assistance systems. You can add all of this yourself, but it&#39;s not simple, and the more you change and add non-standard things, the harder the car becomes to maintain. And it&#39;s the same with monoliths. Making changes becomes increasingly difficult. Young programmers complain about outdated practices. Scaling requires more and more resources.</p>
<hr>
<h2>The Microservices Revolution</h2>
<pre><code class="language-mermaid">flowchart LR
    subgraph Microservices
        D[UI] --&gt; E[Service 1]
        D --&gt; F[Service 2]
        D --&gt; G[Service 3]
        E --&gt; H[(DB 1)]
        F --&gt; I[(DB 2)]
        G --&gt; J[(DB 3)]
    end
</code></pre>
<p>And here come microservices - like the latest cars where the engine and chassis are wrapped in various systems that can work wonders. Microservices offer a vision of a system composed of independent, small services. But this vision, though tempting, raises legitimate concerns.</p>
<hr>
<pre><code class="language-mermaid">flowchart TB
    subgraph &quot;Microservice Complexity&quot;
        A[Microservice] --&gt; B[Infrastructure]
        A --&gt; C[Monitoring]
        A --&gt; D[Communication]
        A --&gt; E[Deployment]
        A --&gt; F[Security]
        A --&gt; G[Data consistency]
    end
</code></pre>
<h3>1. Distribution Complexity</h3>
<p>It&#39;s like going from a stage where fixing a car required a ratchet, screwdriver and hammer to a point where you can&#39;t even begin diagnostics without a range of diagnostic tools and specialist knowledge. Each microservice needs its own infrastructure, monitoring, logging. The world becomes more complicated.</p>
<h3>2. Complex Communication</h3>
<p>In monoliths, everything was within reach. Now you need a reliable system for inter-service communication. It&#39;s like tons of cables in the newest cars. In microservices, you must decide whether to connect via REST API, gRPC, or perhaps opt for distributed communication (events and messages).</p>
<h3>3. Data Consistency Challenges</h3>
<p>It&#39;s like coordinating all driving systems in a modern car to help the driver rather than kill them. When each microservice has its own database, we must ensure information synchronization. A change in one place must be recorded in all necessary places.</p>
<hr>
<h2>The Cost of Transformation</h2>
<p>From my personal experience with architectural transformations, I know that each path carries specific costs and risks. While leading a team working on developing a large system that created a platform for e-commerce store owners, I learned that what looks great on paper can be much more complicated in reality.</p>
<h2>Approaches to migration</h2>
<h3>1. Complete System Rewrite</h3>
<pre><code class="language-mermaid">graph TB
    A[Legacy System] --&gt;|&quot;Path 1&quot;| B[Complete Rewrite]
    B --&gt; B1[Advantages]
    B --&gt; B2[Challenges]
    B --&gt; B3[Final: Pure Microservices]

    B1 --&gt; B1A[Latest Tech/Clean Arch/No Debt]
    B2 --&gt; B2A[High Risk/Long Time/Dual Cost]
</code></pre>
<p>This path reminds me of my colleague&#39;s story, who worked at a company where they wanted to rewrite their entire system from scratch. After several months of work and significant investments, the project was... frozen. Why? There&#39;s probably no clear answer, but let&#39;s look at the pros and cons of this approach:</p>
<p><strong>Advantages:</strong></p>
<ul>
<li>Ability to use the latest technologies</li>
<li>Clean code without technical debt</li>
<li>Architecture tailored to current needs</li>
</ul>
<p><strong>Disadvantages:</strong></p>
<ul>
<li>Huge business risk</li>
<li>Long period without new features</li>
<li>High costs of parallel development</li>
<li>Risk of losing business knowledge from the old system</li>
</ul>
<hr>
<h3>2. Monolith Modernization</h3>
<p>I&#39;ve heard of several successful cases of monolith modernization. The key was focusing on what really needed improvement, rather than blindly following trends.</p>
<pre><code class="language-mermaid">graph TD
    A[Legacy System] --&gt;|&quot;Path 2&quot;| C[Monolith Modernization]
    C --&gt; C1[Advantages]
    C --&gt; C2[Challenges]
    C --&gt; C3[Final: Enhanced Monolith]

    C1 --&gt; C1A[Lower Cost/Continuity/Knowledge]
    C2 --&gt; C2A[Limited Tech/Scale Issues/Legacy]
</code></pre>
<p><strong>Advantages:</strong></p>
<ul>
<li>Lower risk - system continues to operate</li>
<li>Lower initial costs</li>
<li>Possibility of gradual improvements</li>
<li>Retention of business knowledge</li>
</ul>
<p><strong>Disadvantages:</strong></p>
<ul>
<li>Technological limitations remain</li>
<li>Difficulties with fundamental changes</li>
<li>Scalability problems</li>
<li>Challenges in recruiting for older technologies</li>
</ul>
<hr>
<h3>3. Gradual Transformation</h3>
<p>This approach worked best in my project. We started by extracting several critical functionalities into separate services while maintaining the working monolith as the system&#39;s core.</p>
<pre><code class="language-mermaid">graph TD
    A[Legacy System] --&gt;|&quot;Path 3&quot;| D[Gradual Transformation]
    D --&gt; D1[Advantages]
    D --&gt; D2[Challenges]
    D --&gt; D3[Final: Hybrid System]

    D1 --&gt; D1A[Controlled Risk/Learning/Flexibility]
    D2 --&gt; D2A[Complex/Slower/Dual Maintenance]
</code></pre>
<p><strong>Advantages:</strong></p>
<ul>
<li>Controlled risk</li>
<li>Opportunity to learn from small projects</li>
<li>Business continuity maintained</li>
<li>Gradual building of team competencies</li>
</ul>
<p><strong>Disadvantages:</strong></p>
<ul>
<li>Complexity during transition period</li>
<li>Slower pace of changes</li>
<li>Complicated integration of old with new</li>
<li>Greater infrastructure requirements</li>
</ul>
<p>With this approach, a hybrid architecture works best in the transitional state, where an API Gateway is placed between the frontend and backend services. The API Gateway is responsible for authentication and routing requests to the appropriate services.</p>
<pre><code class="language-mermaid">flowchart TB
  subgraph &quot;Hybrid Architecture&quot;
    A[Frontend] --&gt; B[API Gateway]
    B --&gt; C[Legacy Monolith]
    B --&gt; D[New Microservice 1]
    B --&gt; E[New Microservice 2]
  end
</code></pre>
<hr>
<h2>Practical Insights</h2>
<p>After months of working with microservices, I&#39;ve reached several important conclusions:</p>
<ol>
<li><p><strong>There are no universal solutions</strong> - architecture must fit the specific organization, its culture, and needs.</p>
</li>
<li><p><strong>People are more important than technology</strong> - the best architecture won&#39;t help if the team isn&#39;t ready for it.</p>
</li>
<li><p><strong>Evolution is better than revolution</strong> - gradual changes often bring better results than radical transformations.</p>
</li>
<li><p><strong>Business must come first</strong> - architecture should support business goals, not be a goal in itself.</p>
</li>
<li><p>You need time to learn – creating your first microservice doesn’t mean you already &quot;know microservices.&quot; This is only the beginning of your learning journey; everything is still ahead of you.</p>
</li>
</ol>
<hr>
<h2>Summary</h2>
<p>The choice between monolith and microservices, as well as the transformation method, are decisions that will have a long-term impact on any organization. The key is finding balance between technological ambitions and business realities.</p>
<p>Remember that neither monoliths nor microservices are inherently bad - everything depends on context. Sometimes the best solution might be a hybrid of both approaches, where part of the system remains a monolith while critical components operate as microservices.</p>
<hr>
<p>#SoftwareArchitecture #BackendDevelopment #SystemDesign #ConceptExplanation #ArchitectureReview #CaseStudy #Intermediate #Microservices #DataConsistency #SystemIntegration #ArchitecturalTransformation #HybridArchitecture</p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to import Magento GraphQL schema to Postman]]></title>
            <description><![CDATA[<em>Last updated at 01/06/2023</em>]]></description>
            <link>https://frodigo.com/Blog/Archive/Frontend/Magento/How+to+import+Magento+GraphQL+schema+to+Postman</link>
            <guid isPermaLink="false">https://frodigo.com/Blog/Archive/Frontend/Magento/How+to+import+Magento+GraphQL+schema+to+Postman</guid>
            <pubDate>Thu, 01 Jun 2023 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p><em>Last updated at 01/06/2023</em></p>
<p>GraphQL is a query language that allows you to get what you want from the backend, so basically, you need to describe the fields you wish to, write a query, and you are good to go. I have been working a lot with Magento GraphQL API, and I have used the Chrome extension to send requests. The number of queries I have saved has been increasing daily, and I have started looking for something that allows me to organize all my queries and mutations.</p>
<p>Then I remembered the <a href="https://www.postman.com/downloads/">https://www.postman.com/downloads/</a>, which has many beautiful features, and one tiny of them is that you can create your API collection and group all requests you want in folders.</p>
<h2>How to export Magento GraphQL schema</h2>
<p>The first question I asked myself was how can I export Magento GraphQL schema to the file. After some research, I found a perfect and super easy command-line tool.</p>
<h3>The Rover CLI</h3>
<p>The Rover CLI created by Apollo allowed me to export Magento Schema. Installation is super easy and took a few seconds (I installed it on Mac, it should be easy on Linux as well).</p>
<p>Use this command to install the Rover CLI:</p>
<pre><code class="language-bash">curl -sSL https://rover.apollo.dev/nix/latest | sh
</code></pre>
<p>Then you need to restart a terminal or use this command to configure the rover CLI with the current shell:</p>
<pre><code class="language-bash">exec /bin/zsh -l
</code></pre>
<p>Note: this command can be different for your terminal. Please follow the instructions visible in the terminal after the Rover CLI installation.</p>
<h3>Export Magento schema to the file</h3>
<p>To export GraphQL Magento schema to the file, you can use this command:</p>
<pre><code class="language-bash">rover graph introspect &lt;your_magento_graphql_endpoint&gt;  &gt; &lt;file_name&gt;
</code></pre>
<p>Assuming your Magento is available on <a href="https://yoursupermagentostore.com">https://yoursupermagentostore.com</a>, you want to export schema to magento.graphql file, the command you need to use looks like this:</p>
<pre><code class="language-bash">rover graph introspect https://yoursupermagentostore.com &gt; magento.graphql
</code></pre>
<hr>
<h2>Import schema to the Postman</h2>
<p>Once you export the schema to the file, you can import it into Postman. Open your Postman App, and click the import button</p>
<p>Click &quot;Upload Files&quot;</p>
<p>Select the file that you previously exported using the Rover CLI.</p>
<p>You can optionally enable the option to import deprecated fields. Next, just click Import, and your schema should be imported.</p>
<p>You should see queries and mutations imported:</p>
<p>let&#39;s take a look at one of query: route</p>
<p>I marked some areas in the screenshots with the red square.</p>
<ol>
<li><p>Here you have a URL for the request. It is bound to a variable. In next step I will show you where you can add this variable</p>
</li>
<li><p>This is a query body</p>
</li>
<li><p>In this area, you can add/modify query variables</p>
</li>
</ol>
<hr>
<h3>How to set variables in Postman</h3>
<p>You can see variables in two ways:</p>
<ol>
<li><p>By filling variable in Collection&#39;s settings</p>
</li>
<li><p>By creating environment</p>
</li>
</ol>
<p>This time, let&#39;s use the first option:</p>
<p>If you set up a URL to Magento correctly, a request should be sent to Magento:</p>
<hr>
<h2>Bonus: how to disable GraphQL schema introspecting in Magento</h2>
<p>To be honest GraphQL API intersection enabled by default has danger in terms of security because it&#39;s possible to show a GraphQL schema of any Magento shop and use it for shameful acts such as attacks on the store, data theft, etc.</p>
<p>Fortunately, there is an easy way to disable introspection by configuration in Magento.</p>
<p>To do so, please add this entry to your app/etc/env.php file:</p>
<pre><code class="language-javascript">&#39;graphql&#39; =&gt; [
    &#39;disable_introspection&#39; =&gt; true
]
</code></pre>
<hr>
<h2>Summary</h2>
<p>In this short article, I showed you how to export Magento GraphQL schema and import it to Postman. On the other hand, remember that it is recommended to disable API introspection in Magento for any store on production.</p>
<p>#WebDevelopment #BackendDevelopment #JavaScript #PHP #GraphQL #REST #Magento #Apollo #Postman #Tutorial #QuickTip #BestPractices #Intermediate #APISecurity</p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Hexagonal architecture (ports and adapters) - pros and cons]]></title>
            <description><![CDATA[<em>Last updated: 20/04/2023</em>]]></description>
            <link>https://frodigo.com/Blog/Archive/Frontend/Software+architecture/Hexagonal+architecture+(ports+and+adapters)+-+pros+and+cons</link>
            <guid isPermaLink="false">https://frodigo.com/Blog/Archive/Frontend/Software+architecture/Hexagonal+architecture+(ports+and+adapters)+-+pros+and+cons</guid>
            <pubDate>Thu, 20 Apr 2023 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p><em>Last updated: 20/04/2023</em></p>
<p>Hexagonal architecture, also known as octagonal architecture or ports and adapters architecture, is an approach to software design in which business logic is isolated from the technical layer.</p>
<p>In hexagonal architecture, the application consists of three main parts:</p>
<ol>
<li><p><strong>Ports</strong> - interfaces that define how the application communicates with the external environment. Ports are entry and exit points of the application through which information flows.</p>
</li>
<li><p><strong>Adapters</strong> - implementations of ports that enable communication between the application and the external environment. Adapters translate information from a format understandable by the external system to a format understandable by the application and vice versa.</p>
</li>
<li><p><strong>Business logic</strong> - the central part of the application that processes information passed by ports and adapters. Business logic is isolated from implementation details, which enables easy interchangeability of adapters and ports without changes to the business logic.</p>
</li>
</ol>
<hr>
<h2>Ports</h2>
<p>Separating business logic from infrastructure components is the main task of the hexagonal architecture. In the business logic layer, instead of directly accessing infrastructure components, you define &quot;ports&quot; as doors for lower-level components.</p>
<p>Think of these ports as USB ports. They are a socket into which you can plug something. You can plug a method for saving data into a database or save data to a file. The business logic doesn&#39;t care where the data is saved. It sends the data to the port, and what happens to the data next depends on what is &quot;plugged&quot; into that port.</p>
<h3>Adapters</h3>
<p>An adapter is exactly what you want to plug into a port. Based on the example above, in which the business logic wants to save data, we can define such a port:</p>
<pre><code class="language-javascript">interface Writer {
 saveData(data: string): boolean
}
</code></pre>
<p>You can see an interface that contains the <strong>saveData</strong> function, which takes a string argument named data and returns true or false.</p>
<p>Now, let&#39;s take a look at some possible adapters for this port:</p>
<pre><code class="language-javascript">const databaseWriter: Writer = {
    saveData: (data: string): boolean =&gt; {
        console.log(`&quot;${data}&quot; is saved in the database...`);

        return true;
    }
}

const fileWriter: Writer = {
    saveData: (data: string): boolean =&gt; {
        console.log(`&quot;${data}&quot; is saved in the file...`);

        return true;
    }
}
</code></pre>
<p>Now you are defining a class that has quite an enigmatic name for the purposes of this example: <strong>BusinessLogic</strong>:</p>
<pre><code class="language-javascript">class BussinesLogic {
    constructor(
        private writer: Writer
    ) {}

    execute(): void {
        this.writer.saveData(&#39;my data&#39;);
    }
}
</code></pre>
<p>The class expects to receive a <strong>Writer</strong> as an argument. In this simple example, the <strong>execute</strong> method saves the data and doesn’t care where <strong>Writer</strong> will save it.</p>
<p>Now, having defined two different adapters, you can use them as shown below:</p>
<pre><code class="language-javascript">const app1 = new BussinesLogic(fileWriter);
app1.execute(); // &quot;my data&quot; is saved in the file...&quot;

const app2 = new BussinesLogic(databaseWriter);
app2.execute(); // &quot;my data&quot; is saved in the database...&quot;
</code></pre>
<p>Does this make sense?</p>
<p>Now let&#39;s look at a more complex example where you are creating a product page for an online store. You have the following use cases:</p>
<ol>
<li><p>As a user, I can view information about the product and its price.</p>
</li>
<li><p>As a user, I can add the product to my cart.</p>
</li>
</ol>
<p>Let&#39;s start by defining the types and interfaces for the three entities we need:</p>
<pre><code class="language-javascript">type Price = {
    currency: string;
    amount: number;
}

interface Product {
    id: string
    getDescription(): string|null
    getPrice(): Price|null
}

interface Cart {
    addToCart(productId: string): void
}
</code></pre>
<p>Here we have the price, product, and cart. I said it would be a bit more complicated example and I think it is. :)</p>
<p>Now see what concrete implementation of the business logic can look like:</p>
<pre><code class="language-javascript">class CommerceBussinesLogic {
    constructor(
        private product: Product,
        private cart: Cart
    ) {}

    execute(): void {
        this.product.getDescription();
        this.product.getPrice()
    }

    public addProductToCart(productId: string) {
        return this.cart.addToCart(productId);
    }
}
</code></pre>
<p>Business logic exposes two ports: <strong>product</strong> and <strong>cart</strong>. Now, let&#39;s write adapters for these ports. The client says that they want to integrate with the Magento eCommerce system. They say and have:</p>
<pre><code class="language-javascript">class MagentoProductAdapter implements Product {

    private description: string|null = null;
    public price: Price|null = null;

    constructor(
        public id: string
    ) {
        console.log(&#39;Imagine that you fetch product data from ecommerce here...&#39;) // ex. fetch(&#39;&lt;ecommerce_api_url&gt;/product/{id}&#39;)
        this.price = {
            amount: 199.00,
            currency: &#39;EUR&#39;
        }
        this.description = &#39;Lorem ipsum dolor sit amet&#39;;
    }

    public getDescription() {
        return this.description;
    }

     public getPrice() {
        return this.price;
    }
}

class MagentoCartAdapter implements Cart {
        private items: Array&lt;Product&gt;
        private subtotal: Price

    constructor() {
        console.log(&#39;Imagine that you fetch cart here ...&#39;) // ex. fetch(&#39;&lt;ecommerce_api_url&gt;/cart&#39;)
        this.items = []
        this.subtotal = {
            amount: 0,
            currency: &#39;EUR&#39;
        }
    }

     public addToCart(productId: string) {
        console.log(&#39;addToCart clicked&#39;) // send request to eccommerce here
    }
}
</code></pre>
<p>By the way, these examples are very simple pseudo-code in TypeScript, more to show you the idea than to provide production-ready code, so if you see something like this:</p>
<pre><code class="language-javascript">console.log(&quot;Imagine that you fetch cart here ...&quot;); // ex. fetch(&#39;&lt;ecommerce_api_url&gt;/cart&#39;)
</code></pre>
<p>Now, close your eyes briefly and imagine that this code sends a request to an eCommerce system and retrieves real data. My code doesn&#39;t have imagination, so I had to hardcode some data there.</p>
<pre><code class="language-javascript">this.items = [];
this.subtotal = {
  amount: 0,
  currency: &quot;EUR&quot;,
};
</code></pre>
<p>Anyway - the above adapters retrieve code from eCommerce. In this case, it is Magento. See how this code can be executed:</p>
<pre><code class="language-javascript">const myProductid = &quot;123&quot;;
const myCommerce = new CommerceBussinesLogic(
  new MagentoProductAdapter(myProductid),
  new MagentoCartAdapter(),
);

myCommerce.execute();
// imagine that a user clicks add to cart button...
myCommerce.addProductToCart(myProductid);
</code></pre>
<p>The console prints something like this:</p>
<pre><code class="language-javascript">[LOG]: &quot;Imagine that you fetch product data from ecommerce here...&quot;
[LOG]: &quot;Imagine that you fetch cart here ...&quot;
[LOG]: &quot;addToCart clicked&quot;
</code></pre>
<p>Well done, we have just written the code in Ports and Adapters architecture!</p>
<p>That&#39;s not all. Now imagine that after three months it turns out that your client is emotionally unstable and has decided that he want to integrate with the BigCommerce system. The business logic remains the same. What do you do?</p>
<p>You add adapters for BigCommerce:</p>
<pre><code class="language-javascript">class BigCommerceProductAdapter implements Product {

    private description: string|null = null;
    public price: Price|null = null;

    constructor(
        public id: string
    ) {
        console.log(&#39;Imagine that you fetch product data from BigCommerce here...&#39;) // ex. fetch(&#39;&lt;ecommerce_api_url&gt;/product/{id}&#39;)
        this.price = {
            amount: 199.00,
            currency: &#39;EUR&#39;
        }
        this.description = &#39;Lorem ipsum dolor sit amet&#39;;
    }

    public getDescription() {
        return this.description;
    }

     public getPrice() {
        return this.price;
    }
}

class BigCommerceCartAdapter implements Cart {
        private items: Array&lt;Product&gt;
        private subtotal: Price

    constructor() {
        console.log(&#39;Imagine that you fetch cart from BigCommerce here ...&#39;) // ex. fetch(&#39;&lt;ecommerce_api_url&gt;/cart&#39;)
        this.items = []
        this.subtotal = {
            amount: 0,
            currency: &#39;EUR&#39;
        }
    }

     public addToCart(productId: string) {
        console.log(&#39;addToCart Bigcommerce clicked&#39;) // send request to eccommerce here
    }
}
</code></pre>
<p>And you push them into your ports:</p>
<pre><code class="language-javascript">const bigCommerce = new CommerceBussinesLogic(
  new BigCommerceProductAdapter(myProductid),
  new BigCommerceCartAdapter(),
);
</code></pre>
<pre><code class="language-javascript">bigCommerce.execute();
// imagine that a user clicks add to cart button...
bigCommerce.addProductToCart(myProductid);
</code></pre>
<p>Console says:</p>
<pre><code class="language-javascript">[LOG]: &quot;Imagine that you fetch product data from BigCommerce here...&quot;
[LOG]: &quot;Imagine that you fetch cart from BigCommerce here ...&quot;
[LOG]: &quot;addToCart Bigcommerce clicked&quot;
</code></pre>
<hr>
<h2>Infrastructure</h2>
<p>In hexagonal architecture, the presentation layer and data access layer integrate with external components such as:</p>
<ul>
<li><p>Database</p>
</li>
<li><p>UI</p>
</li>
<li><p>External provider</p>
</li>
<li><p>Message bus</p>
</li>
</ul>
<h3><strong>Driving side</strong></h3>
<p>The mobile application user interface code or user interface (UI) code of a web application initiates interaction with the application. User data from the UI is supported by the adapter and sent to the business logic through the port.</p>
<h3><strong>Driven side</strong></h3>
<p>Even databases and external services need an application to function. In this case, the application calls an external service or sends a request to the database. Then, the adapter implements the port to be used.</p>
<hr>
<h2>Dependency inversion principle</h2>
<p>Dependency Inversion Principle states that high-level modules that implement business logic should not depend on low-level modules. This means that interfaces should be defined by high-level modules. This makes the system more flexible and easier to modify, because changes made in one module will not affect the other modules, as long as the interfaces remain unchanged.</p>
<hr>
<p><img src="layered-architecture.png" alt="traditional layered architecture"></p>
<p>In the layered architecture, it&#39;s exactly the opposite - higher-level modules and, frankly speaking, the core business logic that depends on lower-level modules.</p>
<p>The business logic is not mixed with implementation details or technological problems by inverting the dependencies.</p>
<hr>
<h2>Hexagonal architecture - benefits</h2>
<p><img src="image-8.png" alt="business rules are not mixed with implementation details"></p>
<ul>
<li><p>Easy scalability</p>
</li>
<li><p>Application development</p>
</li>
<li><p>Easy integration with other systems</p>
</li>
<li><p>Isolation of business logic from the technical layer, making it easier to introduce changes without affecting the entire system</p>
</li>
</ul>
<p><strong>Hexagonal architecture - drawbacks</strong></p>
<ul>
<li><p>Increased complexity - hexagonal architecture adds components that act as intermediaries, which affects complexity</p>
</li>
<li><p>Debugging - applications created using the hexagonal architecture pattern may be more difficult to debug because they do not directly use specific implementations.</p>
</li>
<li><p>Translation - when the business domain is modeled independently of the database or other technology, translation between the models used for persistence or <strong>communication</strong> and the domain model can be inconvenient. This problem is exacerbated when the models differ significantly from each other both technically and conceptually.</p>
</li>
<li><p>Learning curve - Hexagonal architecture differs from traditional architectural patterns, which are often imposed on developers by frameworks. This can be more difficult for new programmers due to the need for mediation, translation, and design patterns.</p>
</li>
</ul>
<hr>
<h2>When to use hexagonal architecture?</h2>
<p>The correct answer is probably as always: <strong>it depends.</strong></p>
<p>If you are building a simple CRUD application, it&#39;s probably not worth getting into ports and adapters.</p>
<p>The ports and adapters architecture is more suitable for complex business logic than the layered architecture.</p>
<p>It is worth considering the hexagonal architecture when using different external systems, frameworks, and data reading and writing methods.</p>
<p>You can also consider implementing only some aspects of the architecture to improve problem separation. There are many ways to do this, and it&#39;s something to discuss with your team of developers, as each project&#39;s answer may differ.</p>
<hr>
<h2>Hexagonal Architecture and DDD (Domain Driven Design)</h2>
<p>Hexagonal Architecture and <strong>Domain Driven Design (DDD)</strong> are two complementary approaches to software design that aim to facilitate flexibility, scalability, and ease of maintenance of various software applications and systems.</p>
<p>Both approaches emphasize the importance of separating business logic from the technical layer and both use interfaces to define how different parts of the system communicate with each other.</p>
<p>In DDD, the goal is to create a clear and consistent model of the business domain and use it to design the software system.</p>
<p>Hexagonal architecture allows for the introduction of this model in a flexible and scalable way, by separating business logic from implementation details and providing transparent communication interfaces.</p>
<p>By combining the principles of hexagonal architecture with DDD modeling techniques, it is possible to create software systems that are both flexible and easy to maintain, and perfectly tailored to the needs of the client.</p>
<p><strong>However, it is important to remember that both approaches require careful planning and design, and may not be suitable for all software projects.</strong></p>
<hr>
<h2>Summary</h2>
<p>Hexagonal architecture, also known as ports and adapters, is an architectural pattern that separates business logic from the technical layer and facilitates the introduction of changes without affecting the entire system.</p>
<p>In this pattern, the business logic exposes ports, whose implementation depends on adapters written for specific technologies. In this way, each layer is separated and can be developed independently.</p>
<p>Hexagonal architecture is particularly useful in complex projects that require integration with various external systems, such as databases, UI, external providers, and message buses.</p>
<p>The disadvantages of this architecture are increased complexity, difficulties in debugging, translation, and learning curve.</p>
<p>It is worth considering using hexagonal architecture in projects that require separating the business layer from implementation details and easy integration with various external systems.</p>
<p>You can also combine the principles of hexagonal architecture with DDD modeling techniques to create flexible, easy-to-maintain software systems that are perfectly tailored to the client&#39;s needs.</p>
<hr>
<h2>Sources</h2>
<p><a href="https://www.goodreads.com/book/show/179133.Domain_Driven_Design">https://www.goodreads.com/book/show/179133.Domain_Driven_Design</a></p>
<p><a href="https://www.goodreads.com/book/show/57573212-learning-domain-driven-design">https://www.goodreads.com/book/show/57573212-learning-domain-driven-design</a></p>
<p>#ProgrammingFundamentals #SoftwareArchitecture #TypeScript #JavaScript #ConceptExplanation #ArchitectureReview #DeepDive #Intermediate #Microservices #Scalability</p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Magento GraphQL - How to resolve URL]]></title>
            <description><![CDATA[<em>Last updated at 12/02/2023</em>]]></description>
            <link>https://frodigo.com/Blog/Archive/Frontend/Magento/Magento+GraphQL+-+How+to+resolve+URL</link>
            <guid isPermaLink="false">https://frodigo.com/Blog/Archive/Frontend/Magento/Magento+GraphQL+-+How+to+resolve+URL</guid>
            <pubDate>Mon, 20 Feb 2023 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p><em>Last updated at 12/02/2023</em></p>
<p>Recently  I described the basics of Magento GraphQL. This time I want to show you a practical example of how to scaffold a headless app connected with Magento GraphQL and fetch appropriate data from Magento based on a given URL. This article is about:</p>
<ul>
<li><p>concept/idea of how to create routing in a custom Magento headless project</p>
</li>
<li><p>which GraphQL is appropriate to resolve URLs</p>
</li>
<li><p>how to fetch information about categories, products, and CMS pages from API based on a given URL</p>
</li>
<li><p>how to implement the URL resolving functionality in the frontend app (in the big picture)</p>
</li>
</ul>
<hr>
<h2>Magento GraphQL routing concept</h2>
<p>Let me explain the idea of routing in a headless app from not the best pattern I have already seen. Typically in an eCommerce app, you have the following pages: home/cms pages, category page, product page, my account, checkout, etc.</p>
<p>So you can create some custom routes in a headless app to handle all of them, for example:</p>
<ul>
<li><p>Home - <strong>baseUrl/</strong> - to handle homepage</p>
</li>
<li><p>Page - <strong>baseUrl/:pageId</strong> - to handle CMS pages</p>
</li>
<li><p>Category - <strong>baseUrl/category/:categoryId</strong></p>
</li>
<li><p>Product - <strong>baseUrl/product/:productId</strong></p>
</li>
<li><p>Checkout - <strong>baseUrl/checkout</strong></p>
</li>
<li><p>My account - <strong>baseUrl/account</strong></p>
</li>
</ul>
<p>That solution works, but the huge disadvantage is that the URLs are not (user and SEO) friendly. In Magento Luma (Monolith version), you can visit pages like in following links:</p>
<ul>
<li><p><a href="https://magento2demo.frodigo.com/women.html">https://magento2demo.frodigo.com/women.html</a> - category page</p>
</li>
<li><p><a href="https://magento2demo.frodigo.com/joust-duffle-bag.html">https://magento2demo.frodigo.com/joust-duffle-bag.html</a> - product page</p>
</li>
<li><p><a href="https://magento2demo.frodigo.com/about-us">https://magento2demo.frodigo.com/about-us</a> - CMS page</p>
</li>
</ul>
<hr>
<h2>The route query</h2>
<p>Fortunately, Magento GraphQL supports URL resolving for the category, product, and CMS pages. There is the route query that receives the URL key and returns information about the entity that matches the given URL key or null if it&#39;s not possible to resolve the URL.</p>
<pre><code class="language-javascript">query resolveRoute($url: String!) {
  route(url: $url) {
    redirect_code,
    relative_url,
    type
    ... on SimpleProduct {
      sku
      url_key
      uid
      type
      name
    }
    ... on CategoryTree {
      name
      product_count
      uid
    }
    ... on CmsPage {
      identifier
      content_heading
      content
    }
  }
}
</code></pre>
<p>Complete documentation about the route query: <a href="https://devdocs.magento.com/guides/v2.4/graphql/queries/route.html">https://devdocs.magento.com/guides/v2.4/graphql/queries/route.html</a></p>
<p>The route query returns three fields:</p>
<ul>
<li><p><strong>redirect_code (Int)</strong> - Contains 0 when there is no redirect error. A value of 301 indicates the URL of the requested resource has been changed permanently, while a value of 302 indicates a temporary redirect.</p>
</li>
<li><p><strong>relative_url (String)</strong> - The relative internal URL. If the specified URL is a redirect, the query returns the redirected URL, not the original</p>
</li>
<li><p><strong>type (UrlRewriteEntityTypeEnum)</strong> - One of <strong>PRODUCT</strong>, <strong>CATEGORY</strong>, or <strong>CMS_PAGE</strong></p>
</li>
</ul>
<p>The route query has one obligatory parameter: &quot;URL,&quot; which is a string.</p>
<p>Moreover, the route query is implemented by SimpleProduct, DownloadableProduct, BundleProduct, GroupedProduct, VirtualProduct, ConfigurableProduct, CategoryTree, and CmsPaage so that you can get information about those entities in the route query. In the example above, I used SimpleProduct, CmsPage, and CategoryTree.</p>
<h3>Types of pages in Magento</h3>
<p>Three types of URLs can be resolved using Magento GraphQL API and the route query.</p>
<h4>CMS</h4>
<p>CMS pages contain content like about us, homepage, privacy policy, etc.</p>
<p>Take a look at an example response for the About us page:</p>
<pre><code class="language-json">// variables:
{
   &quot;url&quot;: &quot;about-us&quot;
}

// results:
{
  &quot;data&quot;: {
    &quot;route&quot;: {
      &quot;redirect_code&quot;: 0,
      &quot;relative_url&quot;: &quot;about-us&quot;,
      &quot;type&quot;: &quot;CMS_PAGE&quot;,
      &quot;identifier&quot;: &quot;about-us&quot;,
      &quot;content_heading&quot;: &quot;About us&quot;,
      &quot;content&quot;: &quot;&lt;div class=\&quot;about-info cms-content\&quot;&gt;\n      &lt;p class=\&quot;cms-content-important\&quot;&gt;With more than 230 stores spanning 43 states and growing, Luma is a nationally recognized active wear manufacturer and retailer. We’re passionate about active lifestyles – and it goes way beyond apparel.&lt;/p&gt;\n\n      &lt;p&gt;At Luma, wellness is a way of life. We don’t believe age, gender or past actions define you, only your ambition and desire for wholeness... today.&lt;/p&gt;\n\n      &lt;p&gt;We differentiate ourselves through a combination of unique designs and styles merged with unequaled standards of quality and authenticity. Our founders have deep roots in yoga and health communities and our selections serve amateur practitioners and professional athletes alike.&lt;/p&gt;\n\n      &lt;ul style=\&quot;list-style: none; margin-top: 20px; padding: 0;\&quot;&gt;\n          &lt;li&gt;&lt;a href=\&quot;https://magento2demo.frodigo.com/contact/\&quot;&gt;Contact Luma&lt;/a&gt;&lt;/li&gt;\n          &lt;li&gt;&lt;a href=\&quot;https://magento2demo.frodigo.com/customer-service/\&quot;&gt;Customer Service&lt;/a&gt;&lt;/li&gt;\n          &lt;li&gt;&lt;a href=\&quot;https://magento2demo.frodigo.com/privacy-policy/\&quot;&gt;Luma Privacy Policy&lt;/a&gt;&lt;/li&gt;\n          &lt;li&gt;&lt;a href=\&quot;https://magento2demo.frodigo.com/\&quot;&gt;Shop Luma&lt;/a&gt;&lt;/li&gt;\n      &lt;/ul&gt;\n  &lt;/div&gt;\n&quot;
    }
  }
}
</code></pre>
<p>The following query that received the &quot;about-us&quot; URL returns type equals CMS_PAGE and information about the CMS page that we wanted to fetch</p>
<h4>Product</h4>
<p>Product pages present information about products. Take a look at the example &quot;Joust Duffle Bag&quot; product that has the &quot;joust-duffle-bag.html&quot; URL key:</p>
<pre><code class="language-json">// variables:
{
   &quot;url&quot;: &quot;joust-duffle-bag.html&quot;
}

// results:
{
  &quot;data&quot;: {
    &quot;route&quot;: {
      &quot;redirect_code&quot;: 0,
      &quot;relative_url&quot;: &quot;joust-duffle-bag.html&quot;,
      &quot;type&quot;: &quot;PRODUCT&quot;,
      &quot;sku&quot;: &quot;24-MB01&quot;,
      &quot;url_key&quot;: &quot;joust-duffle-bag&quot;,
      &quot;uid&quot;: &quot;MQ==&quot;,
      &quot;name&quot;: &quot;Joust Duffle Bag&quot;
    }
  }
}
</code></pre>
<p>The following query that received the &quot;joust-duffle-bag.html&quot; URL returns type equals PRODUCT and information about the product we wanted to fetch.</p>
<h4>Category</h4>
<p>Category pages display information about the category. Moreover, Magento has a pretty nice feature called <strong>Category Landing Pages</strong> that allows rendering CMS blocks (you can display any CMS content on the category page).</p>
<p>Take a look at examples for the women category.</p>
<pre><code class="language-json">// variables:
{
  &quot;url&quot;: &quot;women.html&quot;
}

// results:
{
  &quot;data&quot;: {
    &quot;route&quot;: {
      &quot;redirect_code&quot;: 0,
      &quot;relative_url&quot;: &quot;women.html&quot;,
      &quot;type&quot;: &quot;CATEGORY&quot;,
      &quot;name&quot;: &quot;Women&quot;,
      &quot;product_count&quot;: 0,
      &quot;uid&quot;: &quot;MjA=&quot;
    }
  }
}
</code></pre>
<p>When passing the &#39;women.html&#39; URL, Magento returns the type equals CATEGORY and information about the category that we wanted to fetch</p>
<h4>No route</h4>
<p>When you pass an URL that doesn&#39;t exist in Magento, the route query will return null. Take a look at the example:</p>
<pre><code class="language-javascript">// query:
query resolveRoute($url: String!) {
  route(url: $url) {
    redirect_code,
    relative_url,
    type
  }
}

// variables:
{
  &quot;url&quot;: &quot;this-is-not-exist-i-am-sure&quot;
}

// results:
{
  &quot;data&quot;: {
    &quot;route&quot;: null
  }
}
</code></pre>
<p>Once you know that the URL doesn&#39;t have corresponding information in the Magento backend, you can render the 404 page.</p>
<h4>Other routes</h4>
<p>Of course, there are more types of pages in eCommerce stores, like checkout pages or customer account pages, but the three types I mentioned before are crucial in terms of SEO and how they are rendered, and what URL is used.</p>
<h3>Deprecated URL resolver query</h3>
<p>Note there is the urlResolver <a href="https://marcin-kwiatkowski.com/blog/graphql/the-full-stack-guide-to-the-graphql-query">https://marcin-kwiatkowski.com/blog/graphql/the-full-stack-guide-to-the-graphql-query</a> that basically does the same as the route query, but it&#39;s deprecated and should not be used.</p>
<hr>
<h2>Sample implementation in a headless app</h2>
<p>Today I want to show some pseudo-code and an idea in the big picture of how to implement URL resolving logic in a NextJS app.</p>
<p>Basically, for those three types of pages that can be resolved (category, product, and CMS), you can create a dynamic route in the pages directory: [[...slug]]. You can create separate routes for other pages like checkout, cart, and my-account. So your pages directory can look like this:</p>
<ul>
<li><p>pages/ [[...slug]].tsx</p>
</li>
<li><p>pages/checkout.tsx</p>
</li>
<li><p>pages/cart.tsx</p>
</li>
<li><p>pages/account.tsx</p>
</li>
</ul>
<p>That&#39;s it. All dynamic routes like category, product, and CMS will be handled by [[...slug]] route, and other pages will be operated by custom routes like the cart.tsx or account.tsx</p>
<p>Take a look at the pseudo-code implementation for the [[...slug]] route:</p>
<pre><code class="language-javascript">import React, { Suspense } from &#39;react&#39;
/** Route GraphQL Magento type: **/
import type { Route } from &#39;../types/magento&#39;

/**
 * helper to fetch data on server side
 * signature: function useData&lt;Type&gt;(key, fetcher): ApiData&lt;Type&gt;
 * returns:
 * interface ApiData&lt;Type&gt; {
 *   data: Type,
 *   error: Error
 * }
 */
import useData from &#39;../lib/use-data&#39;;
/**
 * Wrapper for GraphQL function that allows to query GraphQL API
 * async &lt;TYPE, VARIABLES&gt;(query, variables = null): Promise&lt;TYPE&gt;
 */
import { query as apiQuery } from &#39;../providers/Api&#39;;
/**
 * Route GraphQL queury:
 */
import routeQuery from &quot;../queries/route.gql&quot;;
import Loader from &quot;../components/Loader&quot;; // Loaded component
import CmsPage from &quot;../modules/cms/components/CmsPage/CmsPage.server&quot;; // CMS Page component
import ProductPage from &quot;../modules/product/components/ProductPage/ProductPage.server&quot;; // Product Page component
import CategoryPage from &quot;../modules/category/components/CategoryPage/CategoryPage.server&quot;; // Category Page component

type RouteQuery = {
    route: Route
}

/**
 * Enum that represent three possible types of pages
 */
enum PageTypes {
    Product = &#39;PRODUCT&#39;,
    Category = &#39;CATEGORY&#39;,
    CmsPage = &#39;CMS_PAGE&#39;
}

/**
 * render page content based on given type and url key
 */
const renderPageContent = (route: Route) =&gt; {
    const pages = {
        [PageTypes.CmsPage]: &lt;CmsPage data={route}/&gt;,
        [PageTypes.Product]: &lt;ProductPage data={route}/&gt;,
        [PageTypes.Category]: &lt;CategoryPage data={route}/&gt;,

    }

    return pages[route.type]
}

export default function slug({ router }) {
    const { query } = router;
    const slug = query?.slug ? query?.slug?.join(&#39;/&#39;) : &#39;home&#39;; // handle dynamic slugs

    /**
     * Fetch route information based on slug
     */
    const { data: { route }} = useData(
        `route/${slug}`,
        () =&gt; apiQuery&lt;RouteQuery, { url: string }&gt;(routeQuery, { url: slug }))

    /**
     * content to render:
     */
    const content = renderPageContent(route);

    if (content === null) {
        // redirect to 404 page
    }

    return &lt;Suspense fallback={&lt;Loader/&gt;}&gt;
        {content}
    &lt;/Suspense&gt;
}
</code></pre>
<p>I hope that the comments in the code are helpful. Besides explaining that code, I would say I use <strong>React Server Components</strong> (experimental). RSC allows the rendering of React components on the server-side. Because of that, components there has a `.server` prefix. Server components are game-changers in frontend development, and I can&#39;t wait where they will be released as stable features!</p>
<p>Moreover, I use TypeScript to that code for a better developer experience. The last thing that deserves to mention is the render page content function that receives the route data and renders an appropriate component.</p>
<p>It&#39;s possible to do the same using the switch statement, but I wanted to do that more declaratively.</p>
<p>In the GraphQL examples above, I showed you how to fetch product details, category data, and CMS pages. Components rendered by renderPageContent are perfect places to display that information. They receive the data as a prop, and then its responsibility is to say information on the screen.</p>
<hr>
<h2>Conclusion</h2>
<p>This article showed you basic concepts about routing in headless apps connected with Magento GraphQL API.</p>
<p>There is the route query that resolves the URL and returns one of three page types: CMS_PAGE, Category, and Product. You can render those pages using one dynamic route on the front-end side.</p>
<p>You can create static routes for other pages like checkout or my account add implementation.</p>
<p>Thanks to state-of-the-art features like React Server Components, you can do all of that on the server-side, so the performance of a page will be outstanding.</p>
<p>#WebDevelopment #BackendDevelopment #FrontendDevelopment #JavaScript #TypeScript #GraphQL #React #NextJS #Magento #ConceptExplanation #Tutorial #DeepDive #Intermediate #API #Routing #Headless #SEO</p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to build a high-quality PWA Studio extensio]]></title>
            <description><![CDATA[<em>Published at: 2023-02-11</em>]]></description>
            <link>https://frodigo.com/Blog/Archive/Frontend/Magento/How+to+build+a+high-quality+PWA+Studio+extension</link>
            <guid isPermaLink="false">https://frodigo.com/Blog/Archive/Frontend/Magento/How+to+build+a+high-quality+PWA+Studio+extension</guid>
            <pubDate>Sat, 11 Feb 2023 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p><em>Published at: 2023-02-11</em></p>
<p>First, we need to set up our local PWA Studio instance.</p>
<pre><code class="language-bash">yarn create @magento/pwa
</code></pre>
<p>It would be best if you answered some questions on the console.</p>
<p>Then please generate a unique, secure, custom domain for your new project.</p>
<pre><code class="language-bash">cd &lt;PWA Studio root directory&gt;&gt;
yarn run buildpack create-custom-origin .
</code></pre>
<hr>
<h2>Scaffolding extension</h2>
<p>For development, I recommend creating a module in the <em>pwa-studio-root</em>/src directory and linking it as symlinks in package.json.</p>
<pre><code class="language-bash">cd src/
yarn create @larsroettig/pwa-extension
</code></pre>
<p>Again, you will have to answer a few questions.</p>
<hr>
<p>Now we need to link our module in the package.json file. Please add this entry as a dependency:</p>
<pre><code class="language-json">&quot;@marcinkwiatkowski/account-information&quot;: &quot;link:&lt;absolute_path_to_module&quot;
</code></pre>
<p>Now we need to install our module and run the PWA Studio instance.</p>
<pre><code class="language-bash">yarn install
yarn run watch
</code></pre>
<p>Before we start development, we should do four things:</p>
<ul>
<li><p>add @magento/venia-ui as a peer dependency</p>
</li>
<li><p>add @magento/peregrine as a peer dependency</p>
</li>
<li><p>run the yarn install command in our module directory</p>
</li>
<li><p>drink coffee, water, or whatever you like</p>
</li>
</ul>
<h2>A few words about the extension</h2>
<p>Today we will create an account information page where Customers will find the necessary information about themselves.</p>
<h3>Requirements</h3>
<ol>
<li><p>The Page should be visible at the ‘base_url/account-information’ URL</p>
<ol>
<li>the Page should be visible only for logged-in customers</li>
<li>the Customer’s first name and last name will be displayed on the Page.</li>
</ol>
</li>
<li><p>A link to the Page should be visible in the customer menu</p>
</li>
</ol>
<hr>
<h2>Adding a new route</h2>
<p>Now it’s time to coding!</p>
<h3>Defining the route</h3>
<p>Thanks to targets, we can add a new route to PWA Studio in an easy way. Please add the following content to the intercept.js file:</p>
<pre><code class="language-javascript">targets.of(&quot;@magento/venia-ui&quot;).routes.tap((routes) =&gt; {
  routes.push({
    name: &quot;AccountInformation&quot;,
    pattern: &quot;/account-information&quot;,
    path: require.resolve(
      &quot;@marcinkwiatkowski/account-information/src/lib/components/AccountInformation/&quot;,
    ),
  });
  return routes;
});
</code></pre>
<p>As you can see on line 6, we defined the component which will be rendered when the user opens the Page.</p>
<h3>Adding AccountInformation component</h3>
<p>To start, please create a straightforward React component:</p>
<pre><code class="language-javascript">import React from &quot;react&quot;;

const AccountInformation = () =&gt; {
  return (
    &lt;div&gt;
      &lt;h1&gt;Hello world&lt;/h1&gt;
    &lt;/div&gt;
  );
};

export default AccountInformation;
</code></pre>
<h2>Remember to add an index.js file, which exports the component</h2>
<h2>Adding customer data</h2>
<p>We need to have three pieces of information about the Customer:</p>
<ol>
<li><p>Is the Customer signed in?</p>
</li>
<li><p>First name</p>
</li>
<li><p>Last name</p>
</li>
</ol>
<p>We are going to create the hook which will use the useUserContext talon. Please create a file at <strong>src / lib / talons / AccountInformation / useAccountInformation.js</strong> with the following content:</p>
<pre><code class="language-javascript">import { useUserContext } from &quot;@magento/peregrine/lib/context/user&quot;;

/**
 * useAccountInformation hook which provides data for AccountInformation component
 * @returns {{currentUser: {id, email, firstname, lastname, is_subscribed}, isSignedIn: boolean}}
 */
export const useAccountInformation = () =&gt; {
  const [{ currentUser, isSignedIn }] = useUserContext();

  return {
    currentUser,
    isSignedIn,
  };
};
</code></pre>
<hr>
<h2>Display information about Customer on the Page</h2>
<p>To display the Customer’s information, we have to import the hook and add JSX Markup to the component:</p>
<pre><code class="language-javascript">import React from &quot;react&quot;;
import { useAccountInformation } from &quot;../../talons/AccountInformation/useAccountInformation&quot;;

const AccountInformation = () =&gt; {
  const { currentUser, isSignedIn } = useAccountInformation();

  return (
    &lt;div&gt;
      &lt;h1&gt;Account information&lt;/h1&gt;

      &lt;ul&gt;
        &lt;li&gt;
          &lt;strong&gt;First name: &lt;/strong&gt;
          &lt;span&gt;{currentUser.firstname}&lt;/span&gt;
        &lt;/li&gt;
        &lt;li&gt;
          &lt;strong&gt;Last name: &lt;/strong&gt;
          &lt;span&gt;{currentUser.lastname}&lt;/span&gt;
        &lt;/li&gt;
      &lt;/ul&gt;
    &lt;/div&gt;
  );
};

export default AccountInformation;
</code></pre>
<hr>
<h2>Adding styles</h2>
<p>The next thing which we do is to add styles for our component. Please create the <em>accountInformation.css</em> file in the component directory:</p>
<pre><code class="language-css">.root {
  display: grid;
  padding: 2.5rem 3rem;
  row-gap: 2rem;
}

.title {
  justify-self: center;
  font-family: var(--venia-global-fontFamily-serif);
  font-weight: var(--venia-global-fontWeight-bold);
}

.list {
  justify-self: center;
  margin: 15px auto;
  text-align: center;
}
</code></pre>
<p>Then you can import these styles as a module component and use them.</p>
<pre><code class="language-javascript">import React from &quot;react&quot;;
import { useAccountInformation } from &quot;../../talons/AccountInformation/useAccountInformation&quot;;
import { mergeClasses } from &quot;@magento/venia-ui/lib/classify&quot;;
import defaultClasses from &quot;./accountInformation.css&quot;;

const AccountInformation = (props) =&gt; {
  const classes = mergeClasses(defaultClasses, props.classes);
  const { currentUser, isSignedIn } = useAccountInformation();

  return (
    &lt;div className={classes.root}&gt;
      &lt;h1 className={classes.title}&gt;Account information&lt;/h1&gt;

      &lt;ul className={classes.list}&gt;
        &lt;li&gt;
          &lt;strong&gt;First name: &lt;/strong&gt;
          &lt;span&gt;{currentUser.firstname}&lt;/span&gt;
        &lt;/li&gt;
        &lt;li&gt;
          &lt;strong&gt;Last name: &lt;/strong&gt;
          &lt;span&gt;{currentUser.lastname}&lt;/span&gt;
        &lt;/li&gt;
      &lt;/ul&gt;
    &lt;/div&gt;
  );
};

export default AccountInformation;
</code></pre>
<hr>
<h2>Checking if the Customer is logged in</h2>
<p>We want to redirect to the homepage when the Customer is not logged in. To achieve this, we are going to use the @magento/venia-drivers Redirect component.</p>
<pre><code class="language-javascript">import React from &quot;react&quot;;
import { useAccountInformation } from &quot;../../talons/AccountInformation/useAccountInformation&quot;;
import { Redirect } from &quot;@magento/venia-ui/lib/drivers&quot;;
import { mergeClasses } from &quot;@magento/venia-ui/lib/classify&quot;;
import defaultClasses from &quot;./accountInformation.css&quot;;

const AccountInformation = (props) =&gt; {
  const classes = mergeClasses(defaultClasses, props.classes);
  const { currentUser, isSignedIn } = useAccountInformation();

  if (!isSignedIn) {
    return &lt;Redirect to=&quot;/&quot; /&gt;;
  }

  return (
    &lt;div className={classes.root}&gt;
      &lt;h1 className={classes.title}&gt;Account information&lt;/h1&gt;

      &lt;ul className={classes.list}&gt;
        &lt;li&gt;
          &lt;strong&gt;First name: &lt;/strong&gt;
          &lt;span&gt;{currentUser.firstname}&lt;/span&gt;
        &lt;/li&gt;
        &lt;li&gt;
          &lt;strong&gt;Last name: &lt;/strong&gt;
          &lt;span&gt;{currentUser.lastname}&lt;/span&gt;
        &lt;/li&gt;
      &lt;/ul&gt;
    &lt;/div&gt;
  );
};

export default AccountInformation;
</code></pre>
<hr>
<h2>Adding a link to the customer menu</h2>
<p>The Page looks good, but Customers need to know that this Page exists, so we will add a \ link to the customer menu. If the user clicks the link, PWA Studio will redirect to the account-information Page.</p>
<p>Note: I’m using overwrites here to extend the VeniaUI component and overwrite talon, but in the next version of PWA Studio, there will be a better way to extend talons using a new called Targetables.</p>
<hr>
<h2>Overwriting components</h2>
<p>Create the file <strong>lib / components / AccountMenu / accountMenuItems.js</strong></p>
<pre><code class="language-javascript">import React from &quot;react&quot;;
import { func, shape, string } from &quot;prop-types&quot;;
import { FormattedMessage } from &quot;react-intl&quot;;

import { Link } from &quot;@magento/venia-drivers&quot;;
import { mergeClasses } from &quot;@magento/venia-ui/lib/classify&quot;;
import { useAccountMenuItems } from &quot;../../talons/AccountMenu/useAccountMenuItems&quot;;

import defaultClasses from &quot;@magento/venia-ui/lib/components/AccountMenu/accountMenuItems.css&quot;;

const AccountMenuItems = (props) =&gt; {
  const { onSignOut } = props;

  const talonProps = useAccountMenuItems({ onSignOut });
  const { handleSignOut, menuItems } = talonProps;

  const classes = mergeClasses(defaultClasses, props.classes);

  const menu = menuItems.map((item) =&gt; {
    return (
      &lt;Link className={classes.link} key={item.name} to={item.url}&gt;
        &lt;FormattedMessage id={item.id} /&gt;
      &lt;/Link&gt;
    );
  });

  return (
    &lt;div className={classes.root}&gt;
      {menu}
      &lt;button className={classes.signOut} onClick={handleSignOut} type=&quot;button&quot;&gt;
        &lt;FormattedMessage id={`Sign Out`} /&gt;
      &lt;/button&gt;
    &lt;/div&gt;
  );
};

export default AccountMenuItems;

AccountMenuItems.propTypes = {
  classes: shape({
    link: string,
    signOut: string,
  }),
  onSignOut: func,
};
</code></pre>
<p>The most important thing here is line 7 where we change the path to the useAccountMenuItems talons.</p>
<p>The next thing we need is the lib / talons / AccountInformation / useAccountInformation.js talon.</p>
<pre><code class="language-javascript">import { useCallback } from &quot;react&quot;;

/**
 * @param {Object}      props
 * @param {Function}    props.onSignOut - A function to call when sign out occurs.
 *
 * @returns {Object}    result
 * @returns {Function}  result.handleSignOut - The function to handle sign out actions.
 */
export const useAccountMenuItems = (props) =&gt; {
  const { onSignOut } = props;

  const handleSignOut = useCallback(() =&gt; {
    onSignOut();
  }, [onSignOut]);

  const MENU_ITEMS = [
    {
      name: &quot;Communications&quot;,
      id: &quot;accountMenu.communicationsLink&quot;,
      url: &quot;/communications&quot;,
    },
    {
      name: &quot;Account Information&quot;,
      id: &quot;accountMenu.accountInfoLink&quot;,
      url: &quot;/account-information&quot;,
    },
  ];

  return {
    handleSignOut,
    menuItems: MENU_ITEMS,
  };
};
</code></pre>
<p>The last thing is to add <strong>moduleOverrideebpackPlugin</strong>, add a mapping, and modify the intercept.js file.</p>
<p><strong>I’ll let you handle this one!</strong></p>
<p>If you do it correctly, you will see a link to the Page in the menu:</p>
<hr>
<h2>Unit tests</h2>
<p>Thanks to the generator, we have already configured the environment for unit tests. We can use the Jest Testing Framework.</p>
<p>Please add a <strong>src / lib / components / AccountInformation / __tests__/ AccountInformation.spec.js</strong> file with the following content:</p>
<pre><code class="language-javascript">import React from &quot;react&quot;;
import { default as createTestInstance } from &quot;@magento/peregrine/lib/util/createTestInstance&quot;;
import { useAccountInformation } from &quot;../../../talons/AccountInformation/useAccountInformation&quot;;
import AccountInformation from &quot;../AccountInformation&quot;;

jest.mock(
  &quot;@marcinkwiatkowski/customer-menu/src/lib/talons/AccountInformation/useAccountInformation&quot;,
);
jest.mock(&quot;@magento/venia-ui/lib/classify&quot;);
jest.mock(&quot;@magento/peregrine/lib/context/user&quot;, () =&gt; {
  const userState = {
    isGettingDetails: false,
    getDetailsError: null,
  };
  const userApi = {
    getUserDetails: jest.fn(),
    setToken: jest.fn(),
    signIn: jest.fn(),
  };
  const useUserContext = jest.fn(() =&gt; [userState, userApi]);

  return { useUserContext };
});

jest.mock(&quot;@magento/venia-ui/lib/drivers&quot;, () =&gt; ({
  Redirect: (props) =&gt; &lt;mock-Redirect {...props} /&gt;,
}));

test(&quot;Redirects when not authenticated&quot;, () =&gt; {
  useAccountInformation.mockReturnValue({
    isSignedIn: false,
    currentUser: null,
  });

  const tree = createTestInstance(&lt;AccountInformation /&gt;);
  expect(tree.toJSON()).toMatchSnapshot();
});

test(&quot;Display page when user is signed in&quot;, () =&gt; {
  useAccountInformation.mockReturnValue({
    isSignedIn: true,
    currentUser: {
      firstname: &quot;Marcin&quot;,
      lastname: &quot;Kwiatkowski&quot;,
    },
  });

  const tree = createTestInstance(&lt;AccountInformation /&gt;);
  expect(tree.toJSON()).toMatchSnapshot();
});
</code></pre>
<h3>Running tests</h3>
<p>If you want to run a test, just run this command:</p>
<pre><code class="language-bash">yarn run test
</code></pre>
<p>The results should look like this:</p>
<h2>Other generator features</h2>
<p>The generator has a few other features which help you to create excellent quality components.</p>
<h3>Linting</h3>
<p><strong>ESLint checks all JavaScript code.</strong> You can run this linter manually using this command:</p>
<pre><code class="language-bash">yarn run lint
</code></pre>
<p>Configuration for esLint is in the <strong>.eslintrc.js</strong> file.</p>
<h3>Code formatting</h3>
<p>Also, you can automatically format your code by running this command:</p>
<pre><code class="language-bash">yarn run format
</code></pre>
<h3>Husky</h3>
<p>Thanks to Husky, these commands (lint and format) run automatically before each git commit.</p>
<hr>
<h2>Source code</h2>
<p>You can find the source code of the extension which we just made on my <a href="https://github.com/Frodigo/pwa-studio-extension-example">https://github.com/Frodigo/pwa-studio-extension-example</a></p>
<hr>
<h2>Summary</h2>
<p>As you can see, thanks to <strong>PWA Studio Extension Generator</strong>, you can start developing your extension quickly, and you don’t have to worry about configuring things from scratch. You can grab base files and configs and change whatever you want. Happy Hacking!</p>
<p>#WebDevelopment #FrontendDevelopment #ProgrammingFundamentals #JavaScript #CSS #Bash #React #PWAStudio #Jest #Yarn #GraphQL #Tutorial #ProjectSetup #BestPractices #Intermediate #Testing</p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Vue component here, Vue components there. Components everywhere]]></title>
            <description><![CDATA[Spoiler alert: there will even be a third series, but to the point!]]></description>
            <link>https://frodigo.com/Blog/Archive/Frontend/Vue/Vue+component+here,+Vue+components+there.+Components+everywhere!</link>
            <guid isPermaLink="false">https://frodigo.com/Blog/Archive/Frontend/Vue/Vue+component+here,+Vue+components+there.+Components+everywhere!</guid>
            <pubDate>Fri, 27 Jan 2023 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p><em>Published at 27/01/2023</em>
Previously I showed you how to get started with Vue, and I have promised that there will be a second part, and here you go!</p>
<p>Spoiler alert: there will even be a third series, but to the point!</p>
<p>So since we know the basics of Vue, we now have to dig into the Vue platform to create a custom component. We first create a component representing every player in our football (soccer??) team. We learn several basic concepts throughout the process, like calling components within another component, sending the data to the component via props, or using a provide/inject approach.</p>
<hr>
<h2>Components Basics</h2>
<p>Components are reusable Vue instances. In the App that I started work on in the last article, there is the main component: App.vue.</p>
<p>Any other components accept the same properties as the root component: data, computed, methods, watch, etc.</p>
<h2>Three parts of a component in Vue</h2>
<p>Single-file component vue file comprises three pieces: HTML syntax to determine the visual view for the component. JavaScript provides a list of properties for creating the component, using standard JavaScript module syntax exports in JS. Style sheets are used in defining the best user interfaces.</p>
<p>You have to make a new file (typically in the components folder). Let&#39;s name this file: PlayerCard.vue.</p>
<p>First, add a template section with HTML elements:</p>
<pre><code class="language-javascript">&lt;template&gt;
  &lt;h1&gt;This is a new component&lt;/h1&gt;
&lt;/template&gt;
</code></pre>
<h3>Export Vue components</h3>
<p>To be honest, for a long time, I thought that I needed to add another section beneath the Templates section: <code>&lt;script&gt;&lt;/script&gt;</code> and add an export object, like this:</p>
<pre><code class="language-javascript">&lt;script&gt;export default {}&lt;/script&gt;
</code></pre>
<p>But it is not necessary. You can have components only with the template section. Anyway, typically, you have more sophisticated components in apps, so it&#39;s good to add an export default object with a name property.</p>
<pre><code class="language-javascript">&lt;template&gt;
    &lt;h1&gt;This is a new component&lt;/h1&gt;
&lt;/template&gt;
&lt;script&gt;
export default {
    name: &#39;PlayerdCard&#39;
}
&lt;/script&gt;
</code></pre>
<hr>
<h3>How to import Vue components</h3>
<p>Since we have exported components, we can use them in another place. In this case, we are going to import the PlayerdCard Vue component into the App component. To do so, let&#39;s:</p>
<h4>Import child components into parent components inside script tags</h4>
<pre><code class="language-javascript">&lt;script&gt;
import PlayerCard from &#39;./components/PlayerCard.vue&#39;;

export default {
  name: &#39;App&#39;,
(...)
</code></pre>
<h4>Register child component in a parent component</h4>
<p>We need to let the App component know that it can use the PlayerdCard component. There is a <strong>components property</strong> that is responsible for registering components:</p>
<pre><code class="language-javascript">&lt;script&gt;
import PlayerCard from &#39;./components/PlayerCard.vue&#39;;


export default {
  name: &#39;App&#39;,
  components: { PlayerCard },
(...)
</code></pre>
<h4>FINALLY. Use child component to render a child component template</h4>
<p>To render the PlayerCard component, you must put a custom element inside the App component template. We imported the component as PlayerCard, so this is the name of the custom element:</p>
<pre><code class="language-javascript">&lt;template&gt;
  &lt;PlayerCard/&gt;
&lt;/template&gt;

&lt;script&gt;
import PlayerCard from &#39;./components/PlayerCard.vue&#39;;

export default {
  name: &#39;App&#39;,
  components: { PlayerCard },
  // other stuff below
  data() {
    return {
      players: [
      ]
    }
  },
  computed: {
  },
  methods: {
  watch: {
  }
}
&lt;/script&gt;
&lt;style&gt;
&lt;/style&gt;
</code></pre>
<hr>
<h2>Global and local components</h2>
<p>We registered globally our first component in App.vue, a root component. This is a typical approach, but sometimes you have specific components used in a minor part of the App.</p>
<p>Vue allows you to register components in other components. So you can register the component in the PlayerCard component instance, and you can use it and in any children of it then.</p>
<p>It means that the component is registered locally, and it will not be available in parent components - in our case, in the app component object.</p>
<hr>
<h2>Component communication</h2>
<p>The PlayerdCard component is working, but it&#39;s not too helpful. Let&#39;s add some content to it:</p>
<pre><code class="language-html">&lt;template&gt;
  &lt;div class=&quot;player&quot;&gt;
    &lt;h1&gt;{{ player.name }}&lt;/h1&gt;
    &lt;img :src=&quot;player.image&quot; :alt=&quot;player.name&quot; /&gt;
    &lt;p&gt;&lt;strong&gt;{{ player.club }}, {{ player.country }}&lt;/strong&gt;&lt;/p&gt;
    &lt;p&gt;Position: {{ player.position }}&lt;/p&gt;
    &lt;p&gt;
      Price: {{ player.price }}
      &lt;strong v-if=&quot;player.playerLabel&quot;&gt;{{ player.playerLabel }}&lt;/strong&gt;
    &lt;/p&gt;
    &lt;button type=&quot;button&quot; @click=&quot;toggleForSale()&quot;&gt;
      &lt;span v-if=&quot;player.forSale&quot;&gt;Remove from transfer list&lt;/span&gt;
      &lt;span v-if=&quot;!player.forSale&quot;&gt;Add to transfer list&lt;/span&gt;
    &lt;/button&gt;
  &lt;/div&gt;
&lt;/template&gt;
</code></pre>
<p>A player object is used in many places, but the component object does not include player data. How to pass player data to the component?</p>
<hr>
<h3>Introducing props</h3>
<p>To pass data to the components, you can use props. Props are HTML attributes that can be, let&#39;s say, moved to the component scope.</p>
<p>First, we need to define that the PlayerCard component can receive a player prop:</p>
<pre><code class="language-javascript">// PlayerCard.vue
export default {
  name: &quot;PlayerdCard&quot;,
  props: [&quot;player&quot;],
};
</code></pre>
<p>Then we can pass the player prop to the PlayerCard in App.vue</p>
<pre><code class="language-html">&lt;template&gt;
  &lt;PlayerCard :player=&quot;players[0]&quot; /&gt;
&lt;/template&gt;
</code></pre>
<p>A player is an array defined in data of the App component:</p>
<pre><code class="language-javascript">export default {
  name: &#39;App&#39;,
  components: { PlayerCard },
  data() {
    return {
      players: [
        {
          id: 1,
          name: &#39;Mo Salah&#39;,
          club: &#39;Liverpool FC&#39;,
          country: &#39;Egypt&#39;,
          position: &#39;Striker&#39;,
          price: 2000,
          image: &#39;./avatar.png&#39;,
          forSale: false
        },
        {
          id: 2,
          name: &#39;Robert Lewandowski&#39;,
          club: &#39;FC Bayern&#39;,
          country: &#39;Poland&#39;,
          position: &#39;Striker&#39;,
          price: 3000,
          image: &#39;./avatar.png&#39;,
          forSale: true
        }
      ]
    }
  }
}
&lt;/script&gt;
</code></pre>
<p>To make the example more accurate, let&#39;s iterate through the players&#39; array using v for directive and display all players on the screen:</p>
<pre><code class="language-html">&lt;template&gt;
  &lt;PlayerCard v-for=&quot;player in players&quot; :key=&quot;player.id&quot; :player=&quot;player&quot; /&gt;
&lt;/template&gt;
</code></pre>
<hr>
<h3>Supported props</h3>
<p>Above, we passed an object as a prop, but you can also give other types. Take a look at prop types that Vue supports:</p>
<ul>
<li>String</li>
<li>Number</li>
<li>Boolean</li>
<li>Array</li>
<li>Object</li>
<li>Date</li>
<li>Function</li>
<li>Symbol</li>
</ul>
<h3>Props validation</h3>
<p>When I registered a prop in the PlayerCard component, I just pasted a name into the props array:</p>
<pre><code class="language-javascript">props: [&quot;player&quot;];
</code></pre>
<p>but there is an option to validate props. Take a look:</p>
<pre><code class="language-javascript">props: {
  player: Object;
}
</code></pre>
<p>Now, the component expects that player will be an object, so any other type passed to the component will throw a warning in the console.</p>
<p>Also, you can specify that prop is required:</p>
<pre><code class="language-javascript">props: {
    player: Object,
    required: true
}
</code></pre>
<p>Default value:</p>
<pre><code class="language-javascript">props: {
    player: Object,
    default: {
        name: &#39;Bot&#39;
   }
}
</code></pre>
<p>Besides, there are more options. Please follow Vue docs to meet them all. The last thing that I want to do here is repeating Vue docs.</p>
<p>After this step, the App renders two players.</p>
<hr>
<h2>Custom events: child-parent communication</h2>
<p>Our very first Vue component works, but there are some bugs. When I click on &quot;add to transfer list,&quot; I see the error in the console:</p>
<pre><code class="language-bash">Uncaught TypeError: _ctx.toggleForSale is not a function
</code></pre>
<p>That happens because, in the PlayerCard, we don&#39;t have the toggleForSale function. That function is in the scope of the App Vue component.:</p>
<pre><code class="language-javascript">methods: {
  toggleForSale() {
    this.forSale = !this.forSale;
  }
},
</code></pre>
<p>Anyway, that function is outdated. It worked in the case when we had one player. Now we have an array of players, so we have to update the toggleForSale function to make that possible.</p>
<pre><code class="language-javascript">methods: {
  toggleForSale(playerId) {
    const player = this.players.find(player =&gt; player.id === playerId);
    player.forSale = !player.forSale;
  }
},
</code></pre>
<p>Now the function receives the playerId parameter to identify which player should be modified, but still – the toggleForSale function is in the scope of the App component. Is it possible to call this method from other Vue components? Especially from child components?</p>
<p>There is a way to do that: custom events!</p>
<hr>
<h3>Defining custom events</h3>
<p>To define an event, you should add it to the emits array like this:</p>
<pre><code class="language-javascript">export default {
  name: &quot;PlayerdCard&quot;,
  props: [&quot;player&quot;],
  emits: [&quot;toggle-for-sale&quot;],
};
</code></pre>
<p>Technically, it&#39;s not obligatory but is a good practice to define events because then it&#39;s more clear to understand how components work.</p>
<p>The next step is emitting our custom event when the user clicks on the button:</p>
<pre><code class="language-html">&lt;button type=&quot;button&quot; @click=&quot;$emit(&#39;toggle-for-sale&#39;, player.id)&quot;&gt;
  &lt;span v-if=&quot;player.forSale&quot;&gt;Remove from transfer list&lt;/span&gt;
  &lt;span v-if=&quot;!player.forSale&quot;&gt;Add to transfer list&lt;/span&gt;
&lt;/button&gt;
</code></pre>
<p>Lastly, we have to listen to this event in the App component:</p>
<pre><code class="language-html">1// App.vue
&lt;template&gt;
  &lt;PlayerCard
    v-for=&quot;player in players&quot;
    :key=&quot;player.id&quot;
    :player=&quot;player&quot;
    @toggle-for-sale=&quot;toggleForSale&quot;
  /&gt;
&lt;/template&gt;
</code></pre>
<p>For the @toggle-for-sale custom event, I bound toggleForSale function available in App.js, and now the functionality works as expected.</p>
<hr>
<h3>Validating custom events</h3>
<p>As props, the custom events can be validated. It&#39;s possible by defining emits not as an array but as objects. Each property of that object is a custom event name, and it&#39;s a validation function:</p>
<pre><code class="language-javascript">emits: {
    &#39;toggle-for-sale&#39;: (playerId) =&gt; {
        if (!playerId) {
            console.warn(&#39;Player ID is missing&#39;)

            return false;
        }

        return true
    }
}
</code></pre>
<hr>
<h2>Prop drilling problem</h2>
<p>If you came here from React, you are probably familiar with a common problem called: props drilling. If not, let me explain quickly by example.</p>
<p>We are going to add two components to the PlayerCard component:</p>
<ul>
<li>PlayerData</li>
<li>PlayerAttributes</li>
</ul>
<p>before we start implementing those components, we add some additional data to our players:</p>
<pre><code class="language-javascript">players: [
  {
    id: 1,
    name: &quot;Mo Salah&quot;,
    club: &quot;Liverpool FC&quot;,
    country: &quot;Egypt&quot;,
    position: &quot;Striker&quot;,
    price: 2000,
    image: &quot;./avatar.png&quot;,
    forSale: false,
    birthday: &quot;10/10/2000&quot;,
    growth: &quot;180cm&quot;,
    betterLeg: &quot;right&quot;,
    speed: 94,
    shooting: 90,
    passes: 89,
    dribble: 99,
    defense: 30,
    physical: 85,
  },
  {
    id: 2,
    name: &quot;Robert Lewandowski&quot;,
    club: &quot;FC Bayern&quot;,
    country: &quot;Poland&quot;,
    position: &quot;Striker&quot;,
    price: 3000,
    image: &quot;./avatar.png&quot;,
    forSale: false,
    birthday: &quot;20/05/2000&quot;,
    growth: &quot;190cm&quot;,
    betterLeg: &quot;right&quot;,
    speed: 90,
    shooting: 99,
    passes: 89,
    dribble: 85,
    defense: 78,
    physical: 89,
  },
];
</code></pre>
<hr>
<h3>The PlayerData component</h3>
<pre><code class="language-html">// PlayerCard/PlayerData.vue

&lt;template&gt;
  &lt;div class=&quot;player-data&quot;&gt;
    &lt;ul&gt;
      &lt;li&gt;
        &lt;strong&gt;Birthday: &lt;/strong&gt;
        &lt;span&gt;{{ player.birthday }}&lt;/span&gt;
      &lt;/li&gt;

      &lt;li&gt;
        &lt;strong&gt;Growth: &lt;/strong&gt;
        &lt;span&gt;{{ player.growth }}&lt;/span&gt;
      &lt;/li&gt;

      &lt;li&gt;
        &lt;strong&gt;Better leg: &lt;/strong&gt;
        &lt;span&gt;{{ player.betterLeg }}&lt;/span&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/div&gt;
&lt;/template&gt;
&lt;script&gt;
  export default {
    name: &quot;PlayerData&quot;,
    props: [&quot;player&quot;],
  };
&lt;/script&gt;
</code></pre>
<p>This component renders some information about a player like a birthday, growth, and better leg.</p>
<p>Let&#39;s import it and register in the playerCard Vue component:</p>
<pre><code class="language-javascript">&lt;script&gt;
import PlayerData from &#39;./PlayerCard/PlayerData.vue&#39;

export default {
    name: &#39;PlayerdCard&#39;,
    props: [&#39;player&#39;],
    emits: [&#39;toggle-for-sale&#39;],
    components: {
        PlayerData
    }
}
&lt;/script&gt;
</code></pre>
<p>Now we can use it in the template section:</p>
<pre><code class="language-javascript">// PlayerCard.vue

&lt;template&gt;
    &lt;div class=&quot;player&quot;&gt;
        (...)

        &lt;div class=&quot;additional-data&quot;&gt;
            &lt;PlayerData :player=&quot;player&quot;/&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/template&gt;
</code></pre>
<hr>
<h3>The PlayerAttributes component</h3>
<p>This component will be pretty the same. The difference is that it renders other data:</p>
<pre><code class="language-html">&lt;template&gt;
  &lt;div class=&quot;player-attributes&quot;&gt;
    &lt;ul&gt;
      &lt;li&gt;
        &lt;strong&gt;Speed: &lt;/strong&gt;
        &lt;span&gt;{{ player.speed }}&lt;/span&gt;
      &lt;/li&gt;

      &lt;li&gt;
        &lt;strong&gt;Shooting: &lt;/strong&gt;
        &lt;span&gt;{{ player.shooting }}&lt;/span&gt;
      &lt;/li&gt;

      &lt;li&gt;
        &lt;strong&gt;Passes: &lt;/strong&gt;
        &lt;span&gt;{{ player.passes }}&lt;/span&gt;
      &lt;/li&gt;

      &lt;li&gt;
        &lt;strong&gt;Dribble: &lt;/strong&gt;
        &lt;span&gt;{{ player.passes }}&lt;/span&gt;
      &lt;/li&gt;

      &lt;li&gt;
        &lt;strong&gt;Passes: &lt;/strong&gt;
        &lt;span&gt;{{ player.dribble }}&lt;/span&gt;
      &lt;/li&gt;

      &lt;li&gt;
        &lt;strong&gt;Defense: &lt;/strong&gt;
        &lt;span&gt;{{ player.defense }}&lt;/span&gt;
      &lt;/li&gt;

      &lt;li&gt;
        &lt;strong&gt;Physical: &lt;/strong&gt;
        &lt;span&gt;{{ player.physical }}&lt;/span&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/div&gt;
&lt;/template&gt;
&lt;script&gt;
  export default {
    name: &quot;PlayerData&quot;,
    props: [&quot;player&quot;],
  };
&lt;/script&gt;
</code></pre>
<p>Note: for now, those components looks the same, but in the future, I am going to add more logic there, so don&#39;t worry, I have an idea 😆</p>
<p>The last thing in this step is registering, importing, and using the new component in PlayerCard.</p>
<p>The final code of the PlayerCard component:</p>
<pre><code class="language-html">&lt;template&gt;
  &lt;div class=&quot;player&quot;&gt;
    &lt;h1&gt;{{ player.name }}&lt;/h1&gt;
    &lt;img :src=&quot;player.image&quot; :alt=&quot;player.name&quot; /&gt;
    &lt;p&gt;&lt;strong&gt;{{ player.club }}, {{ player.country }}&lt;/strong&gt;&lt;/p&gt;
    &lt;p&gt;Position: {{ player.position }}&lt;/p&gt;
    &lt;p&gt;
      Price: {{ player.price }}
      &lt;strong v-if=&quot;player.playerLabel&quot;&gt;{{ player.playerLabel }}&lt;/strong&gt;
    &lt;/p&gt;
    &lt;button type=&quot;button&quot; @click=&quot;$emit(&#39;toggle-for-sale&#39;, player.id)&quot;&gt;
      &lt;span v-if=&quot;player.forSale&quot;&gt;Remove from transfer list&lt;/span&gt;
      &lt;span v-if=&quot;!player.forSale&quot;&gt;Add to transfer list&lt;/span&gt;
    &lt;/button&gt;

    &lt;div class=&quot;additional-data&quot;&gt;
      &lt;PlayerData :player=&quot;player&quot; /&gt;
      &lt;PlayerAttributes :player=&quot;player&quot; /&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/template&gt;
&lt;script&gt;
  import PlayerData from &quot;./PlayerCard/PlayerData.vue&quot;;
  import PlayerAttributes from &quot;./PlayerCard/PlayerAttributes.vue&quot;;

  export default {
    name: &quot;PlayerdCard&quot;,
    props: [&quot;player&quot;],
    emits: [&quot;toggle-for-sale&quot;],
    components: {
      PlayerData,
      PlayerAttributes,
    },
  };
&lt;/script&gt;
</code></pre>
<p>I wanted to show you a <strong>props drilling</strong> problem if you forgot, and here you go. We have the PlayerdCard component that receives the player as a prop, and that component has children: PlayerData and PlayerAttributes components that also receive the player as a prop.</p>
<p>Moreover, PlayerCard&#39;s child components also can have children that need a player. A prop drilling problem is about passing props from parent to child. It can be problematic and frustrating if you have a big tree of components.</p>
<p>In React application, there is a way to handle that differently – by using Context. In vue, there is something familiar.</p>
<hr>
<h3>provide/inject as a solution</h3>
<p>Instead of passing props to every child component, you can <strong>provide</strong> data in the parent component and inject this data into children. Take a look:</p>
<pre><code class="language-javascript">export default {
  name: &quot;App&quot;,
  components: { PlayerCard },
  data() {
    return {
      players: [
        {
          id: 1,
          name: &quot;Mo Salah&quot;,
          club: &quot;Liverpool FC&quot;,
          country: &quot;Egypt&quot;,
          position: &quot;Striker&quot;,
          price: 2000,
          image: &quot;./avatar.png&quot;,
          forSale: false,
          birthday: &quot;10/10/2000&quot;,
          growth: &quot;180&quot;,
          betterLeg: &quot;right&quot;,
          speed: 94,
          shooting: 90,
          passes: 89,
          dribble: 99,
          defense: 30,
          physical: 85,
        },
        {
          id: 2,
          name: &quot;Robert Lewandowski&quot;,
          club: &quot;FC Bayern&quot;,
          country: &quot;Poland&quot;,
          position: &quot;Striker&quot;,
          price: 3000,
          image: &quot;./avatar.png&quot;,
          forSale: false,
          birthday: &quot;20/05/2000&quot;,
          growth: &quot;190&quot;,
          betterLeg: &quot;right&quot;,
          speed: 90,
          shooting: 99,
          passes: 89,
          dribble: 85,
          defense: 78,
          physical: 89,
        },
      ],
    };
  },
  provide: {
    players: this.players,
  },
};
</code></pre>
<p>Then in any child, you can inject players:</p>
<pre><code class="language-javascript">export default {
  name: &quot;PlayerData&quot;,
  inject: [&quot;player&quot;],
};
</code></pre>
<p>Once I injected player into the PlayerData and PlayerAttributes component, I can remove the player prop from them in the PlayerCard component:</p>
<pre><code class="language-html">&lt;div class=&quot;additional-data&quot;&gt;
  &lt;PlayerData /&gt;
  &lt;PlayerAttributes /&gt;
&lt;/div&gt;
</code></pre>
<p>The App still works, but data is passed differently.</p>
<hr>
<h2>Dynamic components</h2>
<p>Last thing that I want to show you are dynamic components. Now we have two components in the PlayerCard component: PlayerData and PlayerAttributes, and they are rendered on the screen. I would like to have something like tabs and the possibility to change the active tab. Then only one component corresponding to chosen tab will be visible.</p>
<p>First, create a nav for tabs:</p>
<pre><code class="language-html">// PlayerCard.js
&lt;nav&gt;
  &lt;button type=&quot;button&quot; @click=&quot;selectTab(&#39;PlayerData&#39;)&quot;&gt;Player Data&lt;/button&gt;
  &lt;button type=&quot;button&quot; @click=&quot;selectTab(&#39;PlayerAttributes&#39;)&quot;&gt;
    Player Attributes
  &lt;/button&gt;
&lt;/nav&gt;
</code></pre>
<p>I bound the selectTab method to click event so let&#39;s create this method:</p>
<pre><code class="language-javascript">methods: {
    selectTab(tab) {
        this.selectedTab = tab;
    }
},
</code></pre>
<p>Besides, add a new data property with the selectedTab field:</p>
<pre><code class="language-javascript">data() {
    return {
        selectedTab: &#39;PlayerData&#39;
    }
},
</code></pre>
<p>Lastly, let&#39;s render our components dynamically by adding a <code>&lt;component&gt;</code> vue particular component and binding selectedTab property to is prop on that component like this:</p>
<pre><code class="language-html">&lt;div class=&quot;additional-data&quot;&gt;
  &lt;component :is=&quot;selectedTab&quot; /&gt;
&lt;/div&gt;
</code></pre>
<p>Thanks to that, we can render dynamic components based on data property like this example. When a property is changed, Vue dynamically switch component and render them.</p>
<p>Complete code including dynamic components:</p>
<pre><code class="language-html">&lt;template&gt;
  &lt;div class=&quot;player&quot;&gt;
    &lt;h1&gt;{{ player.name }}&lt;/h1&gt;
    &lt;img :src=&quot;player.image&quot; :alt=&quot;player.name&quot; /&gt;
    &lt;p&gt;&lt;strong&gt;{{ player.club }}, {{ player.country }}&lt;/strong&gt;&lt;/p&gt;
    &lt;p&gt;Position: {{ player.position }}&lt;/p&gt;
    &lt;p&gt;
      Price: {{ player.price }}
      &lt;strong v-if=&quot;player.playerLabel&quot;&gt;{{ player.playerLabel }}&lt;/strong&gt;
    &lt;/p&gt;
    &lt;button type=&quot;button&quot; @click=&quot;$emit(&#39;toggle-for-sale&#39;, player.id)&quot;&gt;
      &lt;span v-if=&quot;player.forSale&quot;&gt;Remove from transfer list&lt;/span&gt;
      &lt;span v-if=&quot;!player.forSale&quot;&gt;Add to transfer list&lt;/span&gt;
    &lt;/button&gt;

    &lt;nav&gt;
      &lt;button type=&quot;button&quot; @click=&quot;selectTab(&#39;PlayerData&#39;)&quot;&gt;
        Player Data
      &lt;/button&gt;
      &lt;button type=&quot;button&quot; @click=&quot;selectTab(&#39;PlayerAttributes&#39;)&quot;&gt;
        Player Attributes
      &lt;/button&gt;
    &lt;/nav&gt;

    &lt;div class=&quot;additional-data&quot;&gt;
      &lt;component :is=&quot;selectedTab&quot; /&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/template&gt;
&lt;script&gt;
  import PlayerData from &quot;./PlayerCard/PlayerData.vue&quot;;
  import PlayerAttributes from &quot;./PlayerCard/PlayerAttributes.vue&quot;;

  export default {
    name: &quot;PlayerdCard&quot;,
    props: [&quot;player&quot;],
    emits: [&quot;toggle-for-sale&quot;],
    provide() {
      return {
        player: this.player,
      };
    },
    components: {
      PlayerData,
      PlayerAttributes,
    },
    data() {
      return {
        selectedTab: &quot;PlayerData&quot;,
      };
    },
    methods: {
      selectTab(tab) {
        this.selectedTab = tab;
      },
    },
  };
&lt;/script&gt;
</code></pre>
<p>It isn&#39;t lovely:😎 but, don&#39;t worry – next time, I will show you how to style Vue applications.</p>
<hr>
<h2>Summary</h2>
<p>This Vue is a Javascript framework for building web apps. As with other frameworks, it allows using reusable components. Thanks to that, frontend development and building single-page applications are easy and efficient.</p>
<p>Today I showed you basics about components:</p>
<ul>
<li>how to create and register vue component</li>
<li>how components communicate with other components</li>
<li>how to use props</li>
<li>what is a prop drilling problem</li>
<li>how to use provide/inject mechanism</li>
<li>how to use dynamic components</li>
<li>and so on</li>
</ul>
<p>#WebDevelopment #FrontendDevelopment #JavaScript #VueJS  #Tutorial #ConceptExplanation #DeepDive #Intermediate #ComponentArchitecture</p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to get started with Vue (part1)]]></title>
            <description><![CDATA[<em>Published at 14/01/2023</em>]]></description>
            <link>https://frodigo.com/Blog/Archive/Frontend/Vue/How+to+get+started+with+Vue+(part1)</link>
            <guid isPermaLink="false">https://frodigo.com/Blog/Archive/Frontend/Vue/How+to+get+started+with+Vue+(part1)</guid>
            <pubDate>Sat, 14 Jan 2023 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p><em>Published at 14/01/2023</em></p>
<p>Unknown word: Vue - this is the sentence that one of my tools that check grammar stands out to me. It&#39;s funny because, probably for most front-end developers, Vue is a known word. After all, we all love JavaScript frameworks.</p>
<p>I had been working with AngularJS a lot. Later I needed to learn KnockoutJS because Magento uses it. I wouldn&#39;t say I liked it(really), so I started work with React, and a few months ago, I had the opportunity to work with Vue apps, so I have begun to learn Vue.</p>
<p>It was not too difficult for me, and in the end, I joined the Vue Storefront team. My tiny conclusion here is frameworks are only frameworks, and when you are experienced in one of them, you can quickly learn from others.</p>
<p>Of course, in terms of different frameworks, API is other, the approach is different, but at the end of the day, there are if-else statements, memory leaks, and issues to solve.</p>
<p>The framework does not matter. It makes building user interfaces easy, but JavaScvript knowledge is a clue here, so if you are good at JavaScript, just learn the API of a framework and solve problems. In this article, I show you how to get started with Vue from the perspective of a developer who has experience in other frameworks. In this article, you won&#39;t find anything about how to print hello world in console, add 1+1, what component is, and so on. I hope you have already known the secret to how to console.log the Hello world. ;-)</p>
<hr>
<h2>Project setup using Vue CLI</h2>
<p>In React, we have Create React App. Here you can find something similar and just as good: Vue CLI. It helps you install the Vue application. Using Vue CLI, you can manually select features you want, such as vuex, Vue router, prettier, support for TypeScript, and so on. Vue CLI will generate project files for you.</p>
<p>It&#39;s fair to say that setting up a Vue app using Vue CLI is an easy task. Let&#39;s try it.</p>
<p>First, make sure that you have Node and package manager installed. I use Node v14 and Yarn.</p>
<pre><code class="language-bash">yarn global add @vue/cli @vue/cli-service-global
vue --version    // @vue/cli 4.5.15 for me
</code></pre>
<p>Navigate to the directory where you want to create a new project and use the following command:</p>
<pre><code class="language-bash">vue create another-cool-project
</code></pre>
<p>I used the Default (Vue 3) ([Vue 3] babel, eslint) option, and my project was installed successfully.</p>
<pre><code class="language-bash">🎉  Successfully created project another-cool-project.
👉  Get started with the following commands:

$ cd another-cool-project
$ yarn serve
</code></pre>
<p>There is a Yarn serve command that runs the development server, so let&#39;s use them, and in the meantime, I will show them the most important directories and files of the newly created project.</p>
<hr>
<h3>Vue project structure</h3>
<h4>Public directory</h4>
<p>It contains public static files like index.html and favicon</p>
<h4>src folder</h4>
<p>You will spend a lot of time here because it&#39;s a place where you can find source files like components, assets, CSS files, and so on.</p>
<h5>main.js file</h5>
<p>Main.js is an entry point of the Vue app, and there is a JavaScript code responsible for Vue app initialization.</p>
<h5>components directory</h5>
<p>If you want to create a Vue component, this is the place where you will do that.</p>
<h5>App.vue file</h5>
<p>It&#39;s a primary component, let&#39;s say the app&#39;s root component. It&#39;s used in main.js to create a new Vue app like this:</p>
<pre><code class="language-javascript">import { createApp } from &quot;vue&quot;;
import App from &quot;./App.vue&quot;;

createApp(App).mount(&quot;#app&quot;);
</code></pre>
<p>All project files and directories should look like this in visual studio code:</p>
<hr>
<h2>Understanding Vue Files</h2>
<p>When you see the .vue file the first time, it can be strange because there is a mix of JavaScript code, HTML, and CSS. Take a look:</p>
<pre><code class="language-javascript">&lt;template&gt;
  &lt;h1&gt;This is the title&lt;/h1&gt;
&lt;/template&gt;

&lt;script&gt;
export default {
  name: &#39;Title&#39;,
}
&lt;/script&gt;

&lt;style&gt;
h1 {
  color: violet
}
&lt;/style&gt;
</code></pre>
<p>So there are three tags: template, script, and style. The first one is the component&#39;s template, and it describes the structure of your HTML. Vue template syntax allows you to use HTML + special, let&#39;s say, directives to bind data, methods and events with HTML.</p>
<p>In the script tag, you define components and write JavaScript code.</p>
<p>The style tag is for styling. For example, you can use global styles or local (scoped), CSS, or SCSS. Details later.</p>
<hr>
<h2>Data binding</h2>
<p>I want to create a simple app that will show cards of football players (lastly, I started to play Fifa Ultimate team, so it inspires me).</p>
<p>Each player has name, team, country, position, and price information. For now, we can put plain HTML to App.js:</p>
<pre><code class="language-html">&lt;template&gt;
  &lt;div class=&quot;player&quot;&gt;
    &lt;h1&gt;Robert Lewandowski&lt;/h1&gt;
    &lt;p&gt;&lt;strong&gt;FC Bayern, Poland&lt;/strong&gt;&lt;/p&gt;
    &lt;p&gt;Position: Striker&lt;/p&gt;
    &lt;p&gt;Price: 1000&lt;/p&gt;
  &lt;/div&gt;
&lt;/template&gt;
</code></pre>
<p>How makes that dynamic? Each component can have a data property, a function that returns an object, and then you can use that object in templates and JavaScript code of the component.</p>
<p>Add data prop to the component:</p>
<pre><code class="language-javascript">export default {
  name: &quot;App&quot;,
  data() {
    return {
      name: &quot;Robert Lewandowski&quot;,
      club: &quot;FC Bayern&quot;,
      country: &quot;Poland&quot;,
      position: &quot;Striker&quot;,
      price: 1000,
    };
  },
};
</code></pre>
<p>Now we can use this data in the template, and we need to bind each property to HTML. To do so we have to use {{ }} syntax for example <strong>{{ name }}</strong> will replace name with &#39;Robert Lewandowski&#39; string.</p>
<p>Take a look:</p>
<pre><code class="language-html">&lt;template&gt;
  &lt;div class=&quot;player&quot;&gt;
    &lt;h1&gt;{{ name }}&lt;/h1&gt;
    &lt;p&gt;&lt;strong&gt;{{ club }}, {{ country }}&lt;/strong&gt;&lt;/p&gt;
    &lt;p&gt;Position: { position }&lt;/p&gt;
    &lt;p&gt;Price: { price }&lt;/p&gt;
  &lt;/div&gt;
&lt;/template&gt;
</code></pre>
<h3>Binding attributes or components property</h3>
<p>Let&#39;s add a new property to the player called: image:</p>
<pre><code class="language-json">image: &#39;./avatar.png&#39;
</code></pre>
<p>to bind this property to the src attribute of the image, you can use the v-bind directive so this code:</p>
<pre><code class="language-html">&lt;img v-bind:src=&quot;image&quot; v-bind:alt=&quot;name&quot; /&gt;
</code></pre>
<p>will be rendered in the browser like this:</p>
<pre><code class="language-html">&lt;img src=&quot;./avatar.png&quot; alt=&quot;Robert Lewandowski&quot; /&gt;
</code></pre>
<p>Tip: there is a shorthand for v-bind. Take a look:</p>
<pre><code class="language-html">&lt;img :src=&quot;image&quot; :alt=&quot;name&quot; /&gt;
</code></pre>
<hr>
<h2>Conditional rendering</h2>
<p>I have an idea to add a particular label to the player that shows if the price of a player is low or high.</p>
<p>For example:</p>
<ul>
<li><p>if the price is less than 5000, display &#39;low.&#39;</p>
</li>
<li><p>otherwise, display &#39;height.&#39;</p>
</li>
</ul>
<p>Vue provides vue-if and vue-else directive that allows rendering markup conditionally:</p>
<pre><code class="language-html">&lt;p&gt;
  Price: {{ price }}
  &lt;strong v-if=&quot;price &lt; 2000&quot;&gt;Low&lt;/strong&gt;
  &lt;strong v-else&gt;High&lt;/strong&gt;
&lt;/p&gt;
</code></pre>
<p>Remember that v-else works only when the HTML node is next to the previous, which has the v-if directive set up.</p>
<hr>
<h2>Computed properties</h2>
<p>Let&#39;s modify the feature with labeling and add logic to our App component:</p>
<blockquote>
<p>Show the label only if player is on for sale</p>
</blockquote>
<p>To do so, let&#39;s add a new property to a player object called forSale:</p>
<pre><code class="language-javascript">forSale: false;
</code></pre>
<p>and modify v-if in the template:</p>
<pre><code class="language-html">&lt;p&gt;
  Price: {{ price }}
  &lt;strong v-if=&quot;price &lt; 2000 &amp;&amp; forSale&quot;&gt;Low&lt;/strong&gt;
  &lt;strong v-if=&quot;price &lt;= 2000 &amp;&amp; forSale&quot;&gt;High&lt;/strong&gt;
&lt;/p&gt;
</code></pre>
<p>It works but is a little bit freaky. Putting logic to the temple is not a good idea, even in such a simple example. Don&#39;t worry. Vue has a lovely mechanism for handling situations like that: computed properties responsible for managing complex logic.</p>
<p>Take a look at how to define computed property:</p>
<pre><code class="language-javascript">export default {
  name: &#39;App&#39;,
  data() {
   // data here
  },
  computed: {
    playerLabel: function () {
      if (!this.forSale) {
          return null;
        }

        return (this.price &lt; 2000) ? &#39;Low&#39; : &#39;High&#39;
    }
  }
}
&lt;/script&gt;
</code></pre>
<p><strong>Note</strong>: The &#39;this&#39; keyword in the code refers to the Vue instance.</p>
<p>Now you can use it in the template like this:</p>
<pre><code class="language-html">&lt;strong v-if=&quot;playerLabel&quot;&gt;{{ playerLabel }}&lt;/strong&gt;
</code></pre>
<p><strong>Important note</strong>: computed properties are cached based on their reactive dependencies. In our case, that means that the template will re-render computed property only if the forSale flag or price is changed.</p>
<p>If you come from React, you can compare computed properties to the useMemo hook.</p>
<hr>
<h2>Methods</h2>
<p>When you create a web app, the common thing is to bind events to a javascript code or, let say, methods.</p>
<p>A methods option allows you to add methods to the Vue components. Let&#39;s create a method that toggles the <strong>forSale</strong> flag.</p>
<pre><code class="language-javascript">export default {
  name: &#39;App&#39;,
  data() {
    // data here
  },
  computed: {
    // computed properties
  },
  methods: {
    toggleForSale() {
      this.forSale = !this.forSale;
    }
  }
}
&lt;/script&gt;
</code></pre>
<p>So we have the method declared and bind it to the DOM element? First, let&#39;s create a button:</p>
<pre><code class="language-html">&lt;button type=&quot;button&quot;&gt;
  &lt;span v-if=&quot;forSale&quot;&gt;Remove from transfer list&lt;/span&gt;
  &lt;span v-if=&quot;!forSale&quot;&gt;Add to transfer list&lt;/span&gt;
&lt;/button&gt;
</code></pre>
<p>To attach an event to the method, we need to use the <strong>v-on</strong> directive. In our case, we want to connect the method to the click event:</p>
<pre><code class="language-html">&lt;button type=&quot;button&quot; v-on:click=&quot;toggleForSale&quot;&gt;&lt;/button&gt;
</code></pre>
<p>You can also use something like this:</p>
<pre><code class="language-html">&lt;button type=&quot;button&quot; @click=&quot;toggleForSale&quot;&gt;&lt;/button&gt;
</code></pre>
<p><strong>Note</strong>: I will show you more about events later.</p>
<hr>
<h2>Watchers</h2>
<p>Sometimes you need to watch when the specific property is changed and run some code. Vue provided a <strong>watch</strong> option. Take a look:</p>
<pre><code class="language-javascript">export default {
  name: &quot;App&quot;,
  data() {},
  computed: {},
  methods: {},
  watch: {
    forSale(oldValue, newValue) {
      console.log(oldValue, newValue);
      // here you can a logic fired when forSale property is changed
      // for example an http request to external API can be fired here
      // axios.post(...)
    },
  },
};
</code></pre>
<hr>
<h2>Iterating through data</h2>
<p>Let&#39;s imagine that we have more than one player, and we want to display all of them. First, of course, we need to modify our data object:</p>
<pre><code class="language-javascript">data() {
  return {
    players: [
      {
        id: 1,
        name: &#39;Mo Salah&#39;,
        club: &#39;Liverpool FC&#39;,
        country: &#39;Egypt&#39;,
        position: &#39;Striker&#39;,
        price: 2000,
        image: &#39;./avatar.png&#39;,
        forSale: false
      },
      {
        id: 2
        name: &#39;Robert Lewandowski&#39;,
        club: &#39;FC Bayern&#39;,
        country: &#39;Poland&#39;,
        position: &#39;Striker&#39;,
        price: 3000,
        image: &#39;./avatar.png&#39;,
        forSale: true
      }
    ]
  }
},
</code></pre>
<p>Please notice that I added the id property to each player object. We will use it later.</p>
<p>To iterate through the array we need to use the v-for directive:</p>
<pre><code class="language-html">&lt;template&gt;
  &lt;div class=&quot;player&quot; v-for=&quot;player in players&quot; :key=&quot;player.id&quot;&gt;
    &lt;h1&gt;{{ player.name }}&lt;/h1&gt;
    &lt;img :src=&quot;player.image&quot; :alt=&quot;player.name&quot; /&gt;
    &lt;p&gt;&lt;strong&gt;{{ player.club }}, {{ player.country }}&lt;/strong&gt;&lt;/p&gt;
    &lt;p&gt;Position: {{ player.position }}&lt;/p&gt;
    &lt;p&gt;
      Price: {{ player.price }}
      &lt;strong v-if=&quot;player.playerLabel&quot;&gt;{{ player.playerLabel }}&lt;/strong&gt;
    &lt;/p&gt;
    &lt;button type=&quot;button&quot; @click=&quot;toggleForSale()&quot;&gt;
      &lt;span v-if=&quot;player.forSale&quot;&gt;Remove from transfer list&lt;/span&gt;
      &lt;span v-if=&quot;!player.forSale&quot;&gt;Add to transfer list&lt;/span&gt;
    &lt;/button&gt;
  &lt;/div&gt;
&lt;/template&gt;
</code></pre>
<p>So using v-for, we repeat in the players&#39; array, and we get a player. Then we can access a player&#39;s object properties using a player reference like a player.name, player. image etc.</p>
<p>Note: after this change, our price label and event listeners stopped working, and this is great because we have another problem to solve!</p>
<p>We will solve that by creating a player component, but... in the following article.</p>
<hr>
<h2>Summary</h2>
<p>Vue is a great, progressive framework that can be a good choice even for a large application when you think about a view layer.</p>
<p>Today, I showed you some basics about creating web apps using Vue, but only a minor part. In the following articles, I would like to show you some more advanced features like:</p>
<ul>
<li>Vue components</li>
<li>styling</li>
<li>events</li>
<li>state management</li>
<li>forms</li>
<li>communication with APIs</li>
<li>testing</li>
<li>and so on</li>
</ul>
<p>#WebDevelopment #FrontendDevelopment #JavaScript #VueJS #Angular #React #VueCLI #Tutorial #ConceptExplanation #QuickTip #Beginner</p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to use GraphQL mutations in React and Apollo Client]]></title>
            <description><![CDATA[<em>Last updated: 27/10/2022</em>]]></description>
            <link>https://frodigo.com/Blog/Archive/Frontend/Graphql/How+to+use+GraphQL+mutations+in+React+and+Apollo+Client</link>
            <guid isPermaLink="false">https://frodigo.com/Blog/Archive/Frontend/Graphql/How+to+use+GraphQL+mutations+in+React+and+Apollo+Client</guid>
            <pubDate>Thu, 27 Oct 2022 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p><em>Last updated: 27/10/2022</em></p>
<p>In one of my previous articles, I described GraphQL queries. Today I would like to show you how to work with GraphQL mutations.</p>
<h2>What is a mutation in GraphQL?</h2>
<p>A mutation is a function that allows you to modify data. In REST APIs, data is typically fetched by the GET method and added or modified by the POST or PUT method.</p>
<p>In GraphQL, there are two types of operations:</p>
<ol>
<li><p>Query: fetching data</p>
</li>
<li><p>Mutation: modifying/adding data</p>
</li>
</ol>
<h2>Mutation example</h2>
<p>Take a look at the simple mutation from the Magento eCommerce platform&#39;s GraphQL API that creates an empty cart:</p>
<pre><code class="language-javascript">mutation {
  createEmptyCart(input: {})
}
</code></pre>
<p>Mutation receives an empty input object and returns a token of the newly created cart.</p>
<p>Let&#39;s try something more complicated - a mutation that takes some input via variables:</p>
<pre><code class="language-javascript">
mutation {
  addSimpleProductsToCart(input: { cart_id: &quot;B06YCRcXAaOHGTtZ2Iij8SDHAq0AR49F&quot;, cart_items: [{ data:{sku: &quot;24-MB01&quot;, quantity: 1} }] }) {
    cart {
      total_quantity
      items {
        product {
          name
        }
      }
    }
  }
}
</code></pre>
<p>In this example, you can see the addSimpleProductsToCart mutation that takes an input object with the following variables:</p>
<ul>
<li><p><strong>cart_id</strong>: Cart ID to which the product will be added</p>
</li>
<li><p><strong>cart_items</strong>: array of objects that contains the SKU of the product and its quantity. The example above adds one product of SKU <em>24-MB0_1 to the cart with ID _B06YCRcXAaOHGTtZ2Iij8SDHAq0AR49F</em></p>
</li>
</ul>
<p>Mutation returns the cart object, and in this example, I requested total_quantity of the cart and information about the cart items&#39; names.</p>
<p>The response to that GraphQL mutation looks like this:</p>
<pre><code class="language-javascript">{
  &quot;data&quot;: {
    &quot;addSimpleProductsToCart&quot;: {
      &quot;cart&quot;: {
        &quot;total_quantity&quot;: 1,
        &quot;items&quot;: [
          {
            &quot;product&quot;: {
              &quot;name&quot;: &quot;Joust Duffle Bag&quot;
            }
          }
        ]
      }
    }
  }
}
</code></pre>
<p>Hard-coding variables is not a good idea in real projects, so take a look at an example where I pass variables by arguments:</p>
<pre><code class="language-javascript">mutation addSimpleProductsToCart($input: AddSimpleProductsToCartInput ) {
  addSimpleProductsToCart(input: $input) {
    cart {
      total_quantity
      items {
        product {
          name
        }
      }
    }
  }
}
</code></pre>
<p>In this case, you can pass variables as an object:</p>
<pre><code class="language-javascript">{
  &quot;input&quot;: {
    &quot;cart_id&quot;: &quot;B06YCRcXAaOHGTtZ2Iij8SDHAq0AR49F&quot;,
    &quot;cart_items&quot;: [
      {
        &quot;data&quot;: {
          &quot;sku&quot;: &quot;24-MB01&quot;,
          &quot;quantity&quot;: 1
        }
      }
    ]
  }
}
</code></pre>
<hr>
<h2>Apollo Client</h2>
<p>Apollo Client is one of the most popular GraphQL clients for React, and in this article, I want to show you how to work with mutations using Apollo Client and React.</p>
<h3>Prerequisites</h3>
<p>If you want to run the examples below on your computer, you should:</p>
<ol>
<li><p>Set app a new React App using <a href="https://create-react-app.dev/docs/getting-started">https://create-react-app.dev/docs/getting-started</a></p>
</li>
<li><p><a href="https://www.apollographql.com/docs/react/get-started/">https://www.apollographql.com/docs/react/get-started/</a> (note: I used Magento GraphQL API in examples, but it is up to you which API you would like to use)</p>
</li>
</ol>
<h2>Execute mutation in Apollo client</h2>
<p>Apollo Client offers predefined hooks to execute GraphQL queries and mutations. These hooks offer much more than only sending requests and receiving responses. Let&#39;s go dive to see what the useMutation hook provides.</p>
<h3>The useMutation hook</h3>
<p>Let&#39;s see how the <strong>useMutation</strong> hook works with a real example*.* To do so, I use the createEmptyCart mutation. First, I need to define that mutation in the code:</p>
<pre><code class="language-javascript">import { gql, useMutation } from &quot;@apollo/client&quot;;

const CREATE_EMPTY_CART = gql`
  mutation {
    createEmptyCart(input: {})
  }
`;
</code></pre>
<p>That GraphQL operation takes no arguments so executing is very simple and looks like this:</p>
<pre><code class="language-javascript">useMutation(CREATE_EMPTY_CART);
</code></pre>
<p>Full example:</p>
<pre><code class="language-javascript">import { gql, useMutation } from &quot;@apollo/client&quot;;

const CREATE_EMPTY_CART = gql`
  mutation {
    createEmptyCart(input: {})
  }
`;

function App() {
  const [mutateFunction, { data, loading, error, called, reset }] =
    useMutation(CREATE_EMPTY_CART);

  if (loading) {
    return &lt;&gt;Loading...&lt;/&gt;;
  }

  return (
    &lt;main style={{ padding: &quot;10px&quot; }}&gt;
      &lt;h1&gt;GraphQL mutations tutorial&lt;/h1&gt;
      &lt;button type=&quot;button&quot; onClick={() =&gt; mutateFunction()}&gt;
        Create cart
      &lt;/button&gt;

      &lt;div&gt;
        &lt;h2&gt;Data: &lt;/h2&gt;
        {data &amp;&amp; data.createEmptyCart}
      &lt;/div&gt;
      {error &amp;&amp; (
        &lt;div&gt;
          &lt;h2&gt;Error:&lt;/h2&gt;
          {error.toString()}
        &lt;/div&gt;
      )}
      &lt;div&gt;
        &lt;h2&gt;Called: {called.toString()}&lt;/h2&gt;
        &lt;button type=&quot;button&quot; onClick={() =&gt; reset()}&gt;
          Reset
        &lt;/button&gt;
      &lt;/div&gt;
    &lt;/main&gt;
  );
}

export default App;
</code></pre>
<p>This part of the code:</p>
<pre><code class="language-javascript">const [mutateFunction, { data, loading, error }] = useMutation(CREATE_EMPTY_CART);
</code></pre>
<p>Is responsible for initializing the useMutation hook in the component</p>
<h3>Mutation response</h3>
<p>The useMutation hook returns an array that contains mutate function and an object with some properties.</p>
<h4>mutate Function</h4>
<p>A function that must be called to execute mutation. This example is named as mutateFunction, but you can name it what you want for example, you can use the operation name: createEmptyCart, or whatever you want.</p>
<p>The code below is responsible for executing mutation:</p>
<pre><code class="language-javascript">&lt;button type=&quot;button&quot; onClick={() =&gt; mutateFunction()}&gt;Create cart&lt;/button&gt;
</code></pre>
<p>Basically, there is a button that has bounded the mutate function on the click event. So when the user clicks that button, the mutation will be executed.</p>
<h4>data</h4>
<p>The data fields contain the mutation response. Then you can use this data in the component. In this example, I display the mutation response on the screen:</p>
<pre><code class="language-html">&lt;div&gt;
  &lt;h2&gt;Data:&lt;/h2&gt;
  {data &amp;&amp; data.createEmptyCart}
&lt;/div&gt;
</code></pre>
<h4>LOADING</h4>
<p>The loading fields show the state of the mutation. If it&#39;s true, that means that mutation is in progress and fetches data at the moment. When mutation returns value and execution is finished, the loading field equals false.</p>
<p>In this example, I use the loading field only to display the loading information on the screen:</p>
<pre><code class="language-javascript">if (loading) {
  return &lt;&gt;Loading...&lt;/&gt;;
}
</code></pre>
<h4>error</h4>
<p>When mutation meets some errors on the backend side, they will be returned here. The error object can contain either an array of <strong>graphQLErrors</strong> objects or one single <strong>networkError</strong> object. If GraphQL server doesn&#39;t produce any error, the error object is <strong>undefined</strong>.</p>
<p>In my example, I display potential errors on the screen:</p>
<pre><code class="language-javascript">{
  error &amp;&amp; (
    &lt;div&gt;
      &lt;h2&gt;Error:&lt;/h2&gt;
      {error.toString()}
    &lt;/div&gt;
  );
}
</code></pre>
<h4>called</h4>
<p>The <strong>called</strong> is a flag that describes if mutate function is called or not.</p>
<h4>Reset</h4>
<p>When you want to rest the mutation state to initial, you call the reset() function. That means Apolo will restore all fields returned by the useMutation hook. to default values.</p>
<h3>Mutation options</h3>
<p>The previous example was straightforward. I didn&#39;t even pass any variables there. Let&#39;s see something more sophisticated. To pass additional parameters to the useMutation hook, you must pass an object as a second argument to the operation.</p>
<pre><code class="language-javascript">const [mutateFunction, { data, loading, error, called, reset }] = useMutation(
  MUTATION_STRING,
  {
    // additional options here
  },
);
</code></pre>
<p>Note: MUTATION_STRING is a GraphQL query string parsed with the <strong>gql</strong> template literal.</p>
<h4>Variables</h4>
<p>The variables object allows passing GraphQL variables to the mutation. Each key in this object represents one variable.</p>
<pre><code class="language-javascript">const CREATE_EMPTY_CART = gql`
   mutation createCart($input: createEmptyCartInput) {
    createEmptyCart(input: $input)
  }
`;

function App() {
  const [mutateFunction, { data, loading, error, called, reset }] = useMutation(CREATE_EMPTY_CART, {
    variables: {
      input: {
        cart_id: &#39;lTO8UjTglq5djnpIOseLN7RWvpvwSRba&#39;
      }
    }
  });

 (...) // rest of the code
</code></pre>
<p>I updated the mutation string, and from now it accepts one argument: $input object:</p>
<pre><code class="language-javascript">mutation createCart($input: createEmptyCartInput) {
    createEmptyCart(input: $input)
}
</code></pre>
<p>Then I passed the variables object to the useMutation hook:</p>
<pre><code class="language-javascript">const [mutateFunction, { data, loading, error, called, reset }] = useMutation(
  CREATE_EMPTY_CART,
  {
    variables: {
      input: {
        cart_id: &quot;lTO8UjTglq5djnpIOseLN7RWvpvwSRba&quot;,
      },
    },
  },
);
</code></pre>
<h4>errorPolicy</h4>
<p>The errorPolicy field allows specifying how the hook handles errors.</p>
<p>Possible values:</p>
<ul>
<li><p><strong>None</strong> (default value) - if the response contains errors, they are returned on the error object, and data is undefined.</p>
</li>
<li><p><strong>Ignore</strong> - errors are skipped, so the error object is not populated even if something goes wrong.</p>
</li>
<li><p><strong>All</strong> - two objects: data and error, are populated. This is useful if you want to settle partial data even if something went wrong during the mutation execution.</p>
</li>
</ul>
<h4>onCompleted</h4>
<p>The onCompleted is a callback function called when the mutation was executed without errors. In this function, you can access mutation results and options passed to the mutation. Take a look at the example:</p>
<pre><code class="language-javascript">import { gql, useMutation } from &quot;@apollo/client&quot;;

const ADD_PRODUCT_TO_CART = gql`
  mutation AddProductsToCart($cartId: String!, $cartItems: [CartItemInput!]!) {
    addProductsToCart(cartId: $cartId, cartItems: $cartItems) {
      cart {
        id
        items {
          quantity
          prices {
            row_total {
              currency
              value
            }
          }
        }
        prices {
          grand_total {
            currency
            value
          }
        }
        total_quantity
      }
    }
  }
`;

const [addProductToCart] = useMutation(ADD_PRODUCT_TO_CART, {
  variables: {
    cartId: &quot;koESgYi6WU5BiCJDRkgfilDB4z1IsfHV&quot;,
    cartItems: [
      {
        selected_options: [&quot;Y29uZmlndXJhYmxlLzE0NC8yMTU&quot;],
        quantity: 1,
        sku: &quot;GC-747-SOCK&quot;,
      },
    ],
  },
  onCompleted: (data, options) =&gt; {
    console.log(data, options);
  },
});

// (...)
&lt;button type=&quot;button&quot; onClick={() =&gt; addProductToCart()}&gt;
  Add product to cart
&lt;/button&gt;;
</code></pre>
<h4>onError</h4>
<p>The onError is a callback function called when a mutation returns some error. In this function, you can access the error object and options passed to the mutation.</p>
<h3>Updating the cache</h3>
<p>Apollo client has its own caching system and, by default, writes data to the cache. The cache impacts the speed of operation, because some operations can retrieve data only from the cache or first from the cache and then from the server.</p>
<p>In addition, a technique called optimistic responses can be used in working with mutations. Let me explain this to you with an example:</p>
<ol>
<li><p>The user enters the website</p>
</li>
<li><p>Apollo downloads a list of products and displays them on the screen</p>
</li>
<li><p>The user adds a product to the basket</p>
</li>
<li><p>Storefront needs to update the shopping cart details.</p>
</li>
</ol>
<p>After executing the mutation, we have two to accomplish this:</p>
<ol>
<li><p>Update the cart data in the cache</p>
</li>
<li><p>Perform the shopping cart inquiry again</p>
</li>
</ol>
<p>Let&#39;s see how to implement this in Apollo.</p>
<h4>How Apollo Updates the cache</h4>
<p>First, let&#39;s create a new query that fetches the cart:</p>
<pre><code class="language-javascript">const GET_CART = gql`
  query getCart {
    cart(cart_id: &quot;koESgYi6WU5BiCJDRkgfilDB4z1IsfHV&quot;) {
      id
      items {
        quantity
        prices {
          row_total {
            currency
            value
          }
        }
      }
      prices {
        grand_total {
          currency
          value
        }
      }
      total_quantity
    }
  }
`;
</code></pre>
<p>Second, use the useLazyQuery hook to execute the query:</p>
<pre><code class="language-javascript">const [getCart] = useLazyQuery(GET_CART, {
  onCompleted: (data) =&gt; {
    console.log(&quot;cart data: &quot;, data);
  },
});
</code></pre>
<p>Third, add a button and bind the getCart function to the onClick event of that button:</p>
<pre><code class="language-javascript">&lt;button type=&quot;button&quot; onClick={() =&gt; getCart()}&gt;
  Get cart
&lt;/button&gt;
</code></pre>
<p>When the user clicks that button, the query is executed and the onCompleted callback prints cart data to the console.</p>
<p>At the same time, Apollo writes data of that cart to the cache.</p>
<p>Now, when the user clicks Add Product to cart button, Apollo runs the addProductToCart mutation, and the product will be added to the cart on the server side. Under the hood, Apollo updates the cart data in the cache.</p>
<h5>Updating cache manually</h5>
<p>In some cases, Apollo is not able to update the cache automatically. Then you can define your own update function and write to the cache manually. You can pass an update function as a field of the settings object that useMutation hook receives:</p>
<pre><code class="language-javascript">import { gql, useMutation } from &quot;@apollo/client&quot;;

const [addProductToCart] = useMutation(ADD_PRODUCT_TO_CART, {
  // (...)
  update(cache, { data: { addProductsToCart } }) {
    cache.modify({
      fields: {
        cart(existingCartRef = {}) {
          const newCartRef = cache.writeFragment({
            data: addProductsToCart.cart,
            fragment: gql`
              fragment NewCart on Cart {
                id
                items {
                  quantity
                  prices {
                    row_total {
                      currency
                      value
                    }
                  }
                }
                prices {
                  grand_total {
                    currency
                    value
                  }
                }
                total_quantity
              }
            `,
          });
          return { ...existingCartRef, newCartRef };
        },
      },
    });
  },
});
</code></pre>
<p>The update function receives the cache object and data returned by the mutation.</p>
<p>On the cache object, there is the modify function that allows updating the cache. In that function, I create a new cart fragment that contains data returned from mutation, and at the end, I return the merged cache cart with a new cart. Then Apollo updates the cache.</p>
<h4>refetchQueries</h4>
<p>Another mechanism to update the state after mutation execution is refetching queries. You can specify which queries should be executed by passing them to the refetchQueries array.</p>
<pre><code class="language-javascript">const [addProductToCart] = useMutation(ADD_PRODUCT_TO_CART, {
  // (...)
  refetchQueries: [&quot;getCart&quot;],
});
</code></pre>
<p>the &quot;getCart&quot; is the query defined earlier:</p>
<pre><code class="language-javascript">const GET_CART = gql`
  query getCart {
     // (...)
  }
`;
</code></pre>
<p>Alternatively, you can pass the query as an object like this:</p>
<pre><code class="language-javascript">const GET_CART = gql`
  query getCart {
     // (...)
  }
`;

// (...)

const [addProductToCart] = useMutation(ADD_PRODUCT_TO_CART, {
  // (...)
  refetchQueries: [{ query: GET_CART }],
});
</code></pre>
<h3>Optimistic responses</h3>
<p>When you perform graphql mutations, they modify server-side data, which can take some time. You would have new data as fast as possible and display it to the user. Apollo provides an interesting mechanism: <strong>Optimistic mutation results</strong>, and thanks to this feature, you can update your UI before your server responds. Take a look at how it works:</p>
<pre><code class="language-javascript">const [addProductToCart] = useMutation(ADD_PRODUCT_TO_CART, {
  // (...)
  optimisticResponse: {
    addProductsToCart: {
      cart: {
        id: &quot;koESgYi6WU5BiCJDRkgfilDB4z1IsfHV&quot;,
        __typename: &quot;Cart&quot;,
        total_quantity: 40,
        // other cart fields
      },
    },
  },
});
</code></pre>
<p>The optimisticResponse object takes an object with the same structure as the expected mutation response. When the mutate function is called, UI will be updated. Finally, when a request is done, Apollo replaces the fake data you specified with real server side data.</p>
<hr>
<h2>Conclusion</h2>
<p>Mutations next to queries are the core of Apollo Client&#39;s API. Sending a request and receiving a response is not all that Apollo Client offers. Thanks to the fact that Apollo has its state, developers can reliably program communication with the API and provide users with a very good UX. One of the most interesting features is optimistic responses, thanks to which the UI will seem fast even when the server is slow.</p>
<p>#WebDevelopment #FrontendDevelopment #BackendDevelopment #JavaScript #React #GraphQL #ApolloClient #REST #ConceptExplanation #Tutorial #DeepDive #Intermediate #DataPipeline #PerformanceOptimization</p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Introduction to the Apollo local state and reactive variables]]></title>
            <description><![CDATA[<em>Last updated: 13/10/2022</em>]]></description>
            <link>https://frodigo.com/Blog/Archive/Frontend/Graphql/Introduction+to+the+Apollo+local+state+and+reactive+variables</link>
            <guid isPermaLink="false">https://frodigo.com/Blog/Archive/Frontend/Graphql/Introduction+to+the+Apollo+local+state+and+reactive+variables</guid>
            <pubDate>Thu, 13 Oct 2022 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p><em>Last updated: 13/10/2022</em></p>
<p>In one of my previous articles, I described the useReducer hook as an excellent way to manage the state of React apps, including apps connected with GraphQL APIs using the apollo client.</p>
<p>Typically in an app, you have a remote state (from sever, for API, here I mean from GraphQL API), but also you can have a local state that does not exist on the server side.</p>
<p>I said that useReducer is suitable for managing situations like that – moreover – using Apollo Client, there is another way to manage the local state.</p>
<h2>Apollo Client for State Management</h2>
<p>Apollo client 3 enables the management of a local state by incorporating field policies and reactive variables. Field Policy lets you specify what happens if you query specific fields, including those not specified for your GraphQL servers. Field policies define local fields so the field is populated with information stored anywhere, like local storage and reactive variables.</p>
<p>So Apollo Client (version &gt;=3) provides two mechanisms to handle the local state:</p>
<ul>
<li><p>Local-only fields and field policies</p>
</li>
<li><p>Reactive variables</p>
</li>
</ul>
<h3>What is the Apollo client?</h3>
<p>The Apollo client connects React App and GraphQL API. Moreover, it is a state management library.</p>
<p>It helps you to connect your React App with GraphQL API. It provides methods to communicate with API, cache mechanisms, helpers, etc.</p>
<p>Besides, Apollo client provides an integrated state management system that allows you to manage the state of your whole application.</p>
<hr>
<h3>What is Apollo State?</h3>
<p>Apollo Client has its state management system using GraphQL to communicate directly with external servers and provide scalability.</p>
<p>Apollo Client supports managing the local and remote state of applications, and you will be able to interact with any state on any device with the same API.</p>
<h2>Local-only fields and field policies</h2>
<p>This mechanism allows you to create your client schema. You can extend a server schema or add new fields.</p>
<p>Then, you can define field policies that describe wherefrom data came from. You can use Apollo cache or local storage.</p>
<p>The crucial advantage of this mechanism is using the same API as when you work with server schema.</p>
<h3>Local state example</h3>
<p>If you want to handle local data inside a standard GraphQL query, you have to use a @client directive for local fields:</p>
<pre><code class="language-javascript">query getMissions ($limit: Int!){
    missions(limit: $limit) {
        id
        name
        twitter
        website
        wikipedia
        links @client // this field is local
    }
}
</code></pre>
<h3>Define local state using local-only fields</h3>
<h4>InMemory cache from Apollo</h4>
<p>Apollo client provides a caching system for local data. Normalized data is saved in memory, and thanks to that, already cached data can get fast.</p>
<h4>Field type policies</h4>
<p>You can read and write to the Apollo client cache. Moreover, you can customize how a specific field in your cache is handled. You can specify read, write, and merge functions and add custom logic.</p>
<p>To define a local state, you need to:</p>
<ol>
<li><p>Define field policy and pass it to the InMemoryCache</p>
</li>
<li><p>Add field to the query with @client directive</p>
</li>
</ol>
<h3>Local-only fields tutorial</h3>
<p>Let&#39;s go deeper with the local-only field and check how they work in action.</p>
<h4>Initialize the project using Create React App</h4>
<pre><code class="language-bash">npx create-react-app local-only-fields
</code></pre>
<h4>Install apollo client</h4>
<pre><code class="language-bash">npm install @apollo/client graphql   
</code></pre>
<h4>Initialize Apollo client</h4>
<p>Import apollo client stuff in index.js:</p>
<pre><code class="language-javascript">import { ApolloClient, InMemoryCache, ApolloProvider } from &quot;@apollo/client&quot;;
</code></pre>
<p>Create client instance</p>
<pre><code class="language-javascript">const client = new ApolloClient({
  uri: &quot;https://api.spacex.land/graphql/&quot;,
  cache: new InMemoryCache(),
});
</code></pre>
<p>API.spacex.land/graphql is a fantastic free public demo of GraphQL API, so I use it here. If you want to explore that API, copy the URL to the browser: <a href="https://api.spacex.land/graphql/">https://api.spacex.land/graphql/</a></p>
<p>Connect Apollo with React by wrapping the App component with ApolloProvider:</p>
<pre><code class="language-javascript">&lt;ApolloProvider client={client}&gt;
       
  &lt;App /&gt;
&lt;/ApolloProvider&gt;
</code></pre>
<p>ApolloProvider takes the client argument, which is our already declared Apollo Client. We can use Apollo Client features in the App component and every child component, thanks to that.</p>
<h4>The query for missions data</h4>
<p>Let&#39;s get some data from the API. I want to get missions:</p>
<pre><code class="language-javascript">query getMissions ($limit: Int!){
  missions(limit: $limit) {
    id
    name
    twitter
    website
    wikipedia
  }
}
</code></pre>
<p>Results for this query when I passed 3 as a limit variable:</p>
<pre><code class="language-json">{
  &quot;data&quot;: {
    &quot;missions&quot;: [
      {
        &quot;id&quot;: &quot;9D1B7E0&quot;,
        &quot;name&quot;: &quot;Thaicom&quot;,
        &quot;twitter&quot;: &quot;https://twitter.com/thaicomplc&quot;,
        &quot;website&quot;: &quot;http://www.thaicom.net/en/satellites/overview&quot;,
        &quot;wikipedia&quot;: &quot;https://en.wikipedia.org/wiki/Thaicom&quot;
      },
      {
        &quot;id&quot;: &quot;F4F83DE&quot;,
        &quot;name&quot;: &quot;Telstar&quot;,
        &quot;twitter&quot;: null,
        &quot;website&quot;: &quot;https://www.telesat.com/&quot;,
        &quot;wikipedia&quot;: &quot;https://en.wikipedia.org/wiki/Telesat&quot;
      },
      {
        &quot;id&quot;: &quot;F3364BF&quot;,
        &quot;name&quot;: &quot;Iridium NEXT&quot;,
        &quot;twitter&quot;: &quot;https://twitter.com/IridiumBoss?lang=en&quot;,
        &quot;website&quot;: &quot;https://www.iridiumnext.com/&quot;,
        &quot;wikipedia&quot;: &quot;https://en.wikipedia.org/wiki/Iridium_satellite_constellation&quot;
      }
    ]
  }
}
</code></pre>
<p>Let&#39;s create a React component that receives that data and, for now, displays the name of the Mission on the screen.</p>
<p>First, create a unit test: src/components/Missions/__tests__/Missions.spec.js</p>
<pre><code class="language-javascript">import { render } from &quot;@testing-library/react&quot;;
import Missions from &quot;../Missions&quot;;
describe(&quot;Missions component&quot;, () =&gt; {
  it(&quot;Should display name of each mission&quot;, () =&gt; {
    const { getByText } = render(&lt;Missions /&gt;);
    getByText(&quot;Missions component should be here.&quot;);
  });
});
</code></pre>
<p>Of course, the test fails because we event doesn&#39;t have a component created yet.</p>
<p>Add Component: src/components/Missions/Missions.js</p>
<pre><code class="language-javascript">import React from &quot;react&quot;;
export const Missions = () =&gt; {
  return &lt;div&gt;       Missions component should be here.    &lt;/div&gt;;
};
export default Missions;
</code></pre>
<p>Now the test is passing</p>
<p>Let&#39;s re-export component in src/components/Missions/index.js</p>
<pre><code class="language-javascript">export { default } from &quot;./Missions&quot;;
</code></pre>
<p>We need to query for data using the useQuery hook provided by the Apollo client.</p>
<p>In unit tests, you need to have a component wrapped by <strong>ApolloProvider</strong>. For testing purposes, Apollo provides a unique Provider: <strong>MockedProvider,</strong> and it allows you to add some mock data. Let&#39;s use it.</p>
<pre><code class="language-javascript">// src/components/Missions/__tests__/Missions.spec.js

import MockedProvider:
 
import { MockedProvider } from &#39;@apollo/client/testing&#39;;
</code></pre>
<p>Define mocks:</p>
<pre><code class="language-javascript">const mocks = [
   {
       request: {
           query: GET_MISSIONS,
           variables: {
               limit: 3,
           },
       },
       result: {
           &quot;data&quot;: {
               &quot;missions&quot;: [
               {
                   &quot;id&quot;: &quot;9D1B7E0&quot;,
                   &quot;name&quot;: &quot;Thaicom&quot;,
                   &quot;twitter&quot;: &quot;https://twitter.com/thaicomplc&quot;,
                   &quot;website&quot;: &quot;http://www.thaicom.net/en/satellites/overview&quot;,
                   &quot;wikipedia&quot;: &quot;https://en.wikipedia.org/wiki/Thaicom&quot;
               },
               {
                   &quot;id&quot;: &quot;F4F83DE&quot;,
                   &quot;name&quot;: &quot;Telstar&quot;,
                   &quot;twitter&quot;: null,
                   &quot;website&quot;: &quot;https://www.telesat.com/&quot;,
                   &quot;wikipedia&quot;: &quot;https://en.wikipedia.org/wiki/Telesat&quot;
               },
               {
                   &quot;id&quot;: &quot;F3364BF&quot;,
                   &quot;name&quot;: &quot;Iridium NEXT&quot;,
                   &quot;twitter&quot;: &quot;https://twitter.com/IridiumBoss?lang=en&quot;,
                   &quot;website&quot;: &quot;https://www.iridiumnext.com/&quot;,
                   &quot;wikipedia&quot;: &quot;https://en.wikipedia.org/wiki/Iridium_satellite_constellation&quot;
               }
               ]
           }
       }
   },
];
</code></pre>
<p>The test fails because we don&#39;t have the GET_MISSIONS query defined yet.</p>
<p>Create the file queries/missions.gql.js with the following content:</p>
<pre><code class="language-javascript">import { gql } from &quot;@apollo/client&quot;;
export const GET_MISSIONS = gql`
   query getMissions ($limit: Int!){
       missions(limit: $limit) {
           id
           name
           twitter
           website
           wikipedia
       }
   } 
`;
</code></pre>
<p>Import query in the src/components/Missions/__tests__/Missions.spec.js</p>
<pre><code class="language-javascript">import { GET_MISSIONS } from &quot;../../../queries/missions.gql&quot;;
</code></pre>
<p>Now let&#39;s wrap the Missions component by the Mocked provider.</p>
<pre><code class="language-javascript">const { getByText } = render(
  &lt;MockedProvider mocks={mocks}&gt;
          
    &lt;Missions /&gt;
       
  &lt;/MockedProvider&gt;,
);
</code></pre>
<p>Now, we can expect that three product missions are visible on the screen because, in our mock response, we have an array with three missions with corresponding names: &#39;Thaicom,&#39; &#39;Telstar,&#39; and &#39;Iridium NEXT.&#39;</p>
<p>To do so, update the test case.</p>
<p>First, make the test case asynchronous by adding the async keyword before <strong>the it</strong> callback function.</p>
<p>Second, replace the <strong>getByText</strong> query with the <strong>findByText,</strong> which works asynchronously.</p>
<pre><code class="language-javascript">it(&quot;Should display name of each mission&quot;, async () =&gt; {
  const { findByText } = render(
    &lt;MockedProvider mocks={mocks}&gt;
                     
      &lt;Missions /&gt;
                 
    &lt;/MockedProvider&gt;,
  );
  await findByText(&quot;Thaicom&quot;);
  await findByText(&quot;Telstar&quot;);
  await findByText(&quot;Iridium NEXT&quot;);
});
</code></pre>
<p>The test fails because we don&#39;t query for the data in React component.</p>
<p>By the way, maybe, you think I don&#39;t wrap findBytext by the <strong>expect…toBe</strong>. I do not do that because the findByText query throws an error when it cannot find provided text as an argument, so I don&#39;t have to create an assertion because the test will fail if the text is not found.</p>
<p>Let&#39;s update the React component.</p>
<p>First import useQuery hook, and GET_MISSIONS query in src/components/Missions/Missions.js</p>
<pre><code class="language-javascript">import { useQuery } from &quot;@apollo/client&quot;;
import { GET_MISSIONS } from &quot;../../queries/missions.gql&quot;;
</code></pre>
<p>Let&#39;s query for the data in the component body:</p>
<pre><code class="language-javascript">const { data } = useQuery(GET_MISSIONS, {
  variables: {
    limit: 3,
  },
});
</code></pre>
<p>Now, let&#39;s prepare content that Component will render for us. If missions exist, allow&#39;s display the name of each Mission. Otherwise, let&#39;s show the &#39;There is no missions&#39; paragraph.</p>
<pre><code class="language-javascript">const shouldDisplayMissions = useMemo(() =&gt; {
  if (data?.missions?.length) {
    return data.missions.map((mission) =&gt; {
      return (
        &lt;div key={mission.id}&gt;
          &lt;h2&gt;{mission.name}&lt;/h2&gt;
        &lt;/div&gt;
      );
    });
  }

  return &lt;h2&gt;There are no missions&lt;/h2&gt;;
}, [data]);
</code></pre>
<p>In the end, the Component needs to return shouldDisplayMissions:</p>
<pre><code class="language-javascript">import React, { useMemo } from &quot;react&quot;;
import { useQuery } from &quot;@apollo/client&quot;;
import { GET_MISSIONS } from &quot;../../queries/missions.gql&quot;;
export const Missions = () =&gt; {
  const { data } = useQuery(GET_MISSIONS, {
    variables: {
      limit: 3,
    },
  });
  const shouldDisplayMissions = useMemo(() =&gt; {
    if (data?.missions?.length) {
      return data.missions.map((mission) =&gt; {
        return (
          &lt;div key={mission.id}&gt;
                               &lt;h2&gt;{mission.name}&lt;/h2&gt;
                           
          &lt;/div&gt;
        );
      });
    }
    return &lt;h2&gt;There are no missions&lt;/h2&gt;;
  }, [data]);
  return shouldDisplayMissions;
};
export default Missions;
</code></pre>
<p>Now, the test pass!</p>
<p>The last thing for this step is to inject components into the app and see missions in the browser.</p>
<pre><code class="language-javascript">// App.js

import Missions from &quot;./components/Missions&quot;;
function App() {
  return &lt;Missions /&gt;;
}
export default App;
</code></pre>
<p>It works but, initially, it shows, &quot;There are no missions.&quot; Let fix it by adding a loading indicator in Missions.js.</p>
<p>First, grab the loading flag from the useQuery hook results:</p>
<pre><code class="language-javascript">const { data, loading } = useQuery(GET_MISSIONS, {
  variables: {
    limit: 3,
  },
});
</code></pre>
<p>Add loading indicator:</p>
<pre><code class="language-javascript">if (loading) {
    return &lt;p&gt;Loading...&lt;/p&gt;
}

return shouldDisplayMissions;
</code></pre>
<p>Besides, add a little bit of styling.</p>
<pre><code class="language-javascript">//src/components/Missions/Missions.module.css
.mission {
   border-bottom: 1px solid black;
   padding: 15px;
}
</code></pre>
<p>Now, import CSS module in Missions.js file:</p>
<pre><code class="language-javascript">import classes from &#39;./Missions.module.css&#39;
</code></pre>
<p>and add mission class to the mission div:</p>
<pre><code class="language-javascript">return (
  &lt;div key={mission.id} className={classes.mission}&gt;
    &lt;h2&gt;{mission.name}&lt;/h2&gt;
  &lt;/div&gt;
);
</code></pre>
<hr>
<h2>Add local-only field</h2>
<p>OK, so we have data from API. The next task is to display links for the Mission. API returns three fields:</p>
<ul>
<li><p>twitter</p>
</li>
<li><p>website</p>
</li>
<li><p>Wikipedia</p>
</li>
</ul>
<p>We can create our local field called: <strong>links</strong>. It will be an array with links, so we can loop through that array and just display links.</p>
<p>First, let&#39;s add a new test case:</p>
<pre><code class="language-javascript">it(&quot;Should display links for the mission&quot;, async () =&gt; {
  const localMocks = [
    {
      ...mocks[0],
      result: {
        data: {
          missions: [
            {
              id: &quot;F4F83DE&quot;,
              name: &quot;Telstar&quot;,
              links: [&quot;https://www.telesat.com/&quot;],
            },
          ],
        },
      },
    },
  ];
  const { findByText } = render(
    &lt;MockedProvider mocks={localMocks}&gt;
      &lt;Missions /&gt;
    &lt;/MockedProvider&gt;,
  );

  await findByText(&#39;https://www.telesat.com/&quot;&#39;);
});
</code></pre>
<p>So, we expect that there will be rendered one link: &quot;<a href="https://www.telesat.com/">https://www.telesat.com/</a>&quot;</p>
<h3>Define field policy</h3>
<p>First, we must define the field policy for our local links field.</p>
<p>When you inspect docs for missions query in GraphQL API, you can see that it returns a Mission type.</p>
<p>So we need to add a links client field to the Mission type.</p>
<p>To do so, we need to add a configuration to InMeMoryCache in the src/index.js file like this:</p>
<pre><code class="language-javascript">const client = new ApolloClient({
 uri: &#39;https://api.spacex.land/graphql/&#39;,
 cache: new InMemoryCache({
   typePolicies: {
     Mission: {
       fields: {
         links: {
           read(_, { readField }) {
             // logic will be added here in the next step
           }
         }
       }
     }
   }
 })
</code></pre>
<p>Now let&#39;s return an array with links collected from the Mission. The read function has two arguments. The first one is the field&#39;s currently cached value if one exists. The second one is an object that provides several properties and helper functions. We will use the readField function to read other field data.</p>
<p>Our logic for the links local field:</p>
<pre><code class="language-javascript">read(_, { readField }) {
    const twitter = readField(&#39;twiiter&#39;);
    const wikipedia = readField(&#39;wikipedia&#39;);
    const website = readField(&#39;website&#39;);
    const links = [];


    if (twitter) {
      links.push(twitter);
    }


    if (wikipedia) {
      links.push(wikipedia);
    }


    if (website) {
      links.push(website);
    }


    return links;
  }
</code></pre>
<h3>The query for local-only field</h3>
<p>The next step is to include the links field in the query. Let&#39;s modify the GET_MISSIONS query:</p>
<pre><code class="language-javascript">query getMissions ($limit: Int!){
    missions(limit: $limit) {
        id
        name
        twitter
        website
        wikipedia
        links @client
    }
}
</code></pre>
<p>You can define the local-only field by adding the @client directive after the field name.</p>
<h3>Display local-only field on the screen</h3>
<p>We have made good progress, but the test still fails because the Component does not render any links yet.</p>
<p>Please update the Missions component by modifying the shouldDisplayMissions Memo function.</p>
<pre><code class="language-javascript">const shouldDisplayMissions = useMemo(() =&gt; {
  if (data?.missions?.length) {
    return data.missions.map((mission) =&gt; {
      const shouldDisplayLinks = mission.links?.length
        ? mission.links.map((link) =&gt; {
            return (
              &lt;li key={`${mission.id}-${link}`}&gt;
                &lt;a href={link}&gt;{link}&lt;/a&gt;
              &lt;/li&gt;
            );
          })
        : null;

      return (
        &lt;div key={mission.id} className={classes.mission}&gt;
          &lt;h2&gt;{mission.name}&lt;/h2&gt;
          {shouldDisplayLinks}
        &lt;/div&gt;
      );
    });
  }

  return &lt;h2&gt;There are no missions&lt;/h2&gt;;
}, [data]);
</code></pre>
<p>We are good now. Everything work as well in the browser:</p>
<hr>
<h2>Working Demo</h2>
<p>Here you can see the demo of the app:</p>
<p><a href="https://apollo-client-local-only-fields-tutorial.vercel.app/">https://apollo-client-local-only-fields-tutorial.vercel.app/</a></p>
<h2><strong>Source code</strong></h2>
<p>Here you can find the source code for this tutorial: <a href="https://github.com/Frodigo/apollo-client-local-only-fields-tutorial">https://github.com/Frodigo/apollo-client-local-only-fields-tutorial</a></p>
<p>Here are the commits for each step:</p>
<ol>
<li><p><a href="https://github.com/Frodigo/apollo-client-local-only-fields-tutorial/commit/57cef8a41077571f8a60675ec81882d8b71a6b89">https://github.com/Frodigo/apollo-client-local-only-fields-tutorial/commit/57cef8a41077571f8a60675ec81882d8b71a6b89</a></p>
</li>
<li><p><a href="https://github.com/Frodigo/apollo-client-local-only-fields-tutorial/commit/595bf812f93c9a104d1f6983c6d046d432104bbe">https://github.com/Frodigo/apollo-client-local-only-fields-tutorial/commit/595bf812f93c9a104d1f6983c6d046d432104bbe</a></p>
</li>
<li><p><a href="https://github.com/Frodigo/apollo-client-local-only-fields-tutorial/commit/e32ab19d6a54b70bb44d92a2754a9538c73ad667">https://github.com/Frodigo/apollo-client-local-only-fields-tutorial/commit/e32ab19d6a54b70bb44d92a2754a9538c73ad667</a></p>
</li>
<li><p><a href="https://github.com/Frodigo/apollo-client-local-only-fields-tutorial/commit/0212eeca03b4768c1b163da355083e984e276668">https://github.com/Frodigo/apollo-client-local-only-fields-tutorial/commit/0212eeca03b4768c1b163da355083e984e276668</a></p>
</li>
<li><p><a href="https://github.com/Frodigo/apollo-client-local-only-fields-tutorial/commit/71b004f013826c4eb58da045230d36334716acde">https://github.com/Frodigo/apollo-client-local-only-fields-tutorial/commit/71b004f013826c4eb58da045230d36334716acde</a></p>
</li>
</ol>
<h2>Reactive variables</h2>
<p>OK, you met local-only fields, and now let&#39;s look at another mechanism called: Reactive variables.</p>
<p>You can write and read data anywhere in your app using reactive variables.</p>
<p>Apollo client doesn&#39;t store reactive variables in its cache, so you don&#39;t have to keep the strict cached data structure.</p>
<p>Apollo client detects changes in reactive variables, and when the value changes, a variable is automatically updated in all places.</p>
<h3>Reactive variables in action</h3>
<p>This time I would like to show you the case using reactive variables. I don&#39;t want to repeat Apollo docs, so you can see the basics of reading and modifying reactive variables <a href="https://www.apollographql.com/docs/react/local-state/reactive-variables/">https://www.apollographql.com/docs/react/local-state/reactive-variables/</a>.</p>
<h3>The case</h3>
<p>I&#39;ve started work on the cart and mini-cart in my react-apollo-storefront app. The first thing that I needed to do was create an empty cart.</p>
<p>In Magento GraphQL API, there is the mutation <strong>createEmptyCart</strong>. That mutation returns the cart ID.</p>
<p>I wanted to get a cart ID, store it in my app, and after the page refresh, check if a value exists in the local state and, if yes, get it from it without running mutation.</p>
<h3>Implementation</h3>
<p>First, let&#39;s define the reactive variable:</p>
<pre><code class="language-javascript">import { makeVar } from &quot;@apollo/client&quot;;

export const CART_ID_IDENTIFER = &quot;currentCardId&quot;;
export const cartId = makeVar(localStorage.getItem(CART_ID_IDENTIFER));
</code></pre>
<p>Second, use that variable in a component, context, or hook and make it reactive:</p>
<pre><code class="language-javascript">import { useReactiveVar } from &#39;@apollo/client&#39;;

export const CartProvider = ({ children }) =&gt; {
    const currentCartId = useReactiveVar(cartId);
};
</code></pre>
<p>Third, define the mutation to collect a cart Id from the server:</p>
<pre><code class="language-javascript">// mutation:
import { gql } from &quot;@apollo/client&quot;;

export const CREATE_EMPTY_CART = gql`
    mutation createEmptyCart {
        createEmptyCart
    }
`;

// component
// imports
import { useMutation } from &quot;@apollo/client&quot;;
import { CREATE_EMPTY_CART } from &quot;../../mutations/cart.gql&quot;;

// ... in component body
const [createEmptyCart] = useMutation(CREATE_EMPTY_CART, {
  update(_, { data: { createEmptyCart } }) {
    cartId(createEmptyCart);
    localStorage.setItem(CART_ID_IDENTIFER, createEmptyCart);
  },
});
</code></pre>
<p>I used the <strong>update</strong> callback there, and I updated the reactive variable:</p>
<pre><code class="language-javascript">cartId(createEmptyCart)
</code></pre>
<p>Then I also updated a value in the local storage.</p>
<p>Last, check if the cart ID exists in the local state and if not, send the mutation to a sever.</p>
<pre><code class="language-javascript">const currentCartId = useReactiveVar(cartId);

useEffect(() =&gt; {
  if (!currentCartId) {
    createEmptyCart();
  }
}, [createEmptyCart, currentCartId]);
</code></pre>
<hr>
<h2>Summary</h2>
<p>Today I showed you two techniques for managing local data in Apollo. Local-only fields and reactive variables. Those mechanisms provide a lot of flexibility, and they should be considered when architecting state management in your React application.</p>
<p>#WebDevelopment #FrontendDevelopment #ProgrammingFundamentals #JavaScript #React #Apollo #GraphQL #REST #Tutorial #DeepDive #ConceptExplanation #Intermediate #StateManagement #DataPipeline #APIIntegration</p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to create a quick search component using Apollo lazy query]]></title>
            <description><![CDATA[<em>Published at 29/09/2022</em>]]></description>
            <link>https://frodigo.com/Blog/Archive/Frontend/Graphql/How+to+create+a+quick+search+component+using+Apollo+lazy+query</link>
            <guid isPermaLink="false">https://frodigo.com/Blog/Archive/Frontend/Graphql/How+to+create+a+quick+search+component+using+Apollo+lazy+query</guid>
            <pubDate>Thu, 29 Sep 2022 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p><em>Published at 29/09/2022</em></p>
<p>When React renders a component that calls the <strong>useQuery</strong> hook, the Apollo Client runs the query automatically, but sometimes you need to query for data on some event. A great case is a quick search component that allows users to search for products in an eCommerce store.</p>
<p>There is a <strong>useLazyQuery</strong> hook that returns a query function. You can use this function wherever you want, and when you fire it, Apollo will execute a query for you.</p>
<h2>When would you use lazy queries?</h2>
<p>Typically custom Apollo&#39;s hook <strong>useQuery</strong> is used when you want to query data. That kook is called when React mounts and renders the component, and Apollo Client automatically executes a query then.</p>
<p>What to do when you want to call a query manually thought? For instance, when a user clicks on a specific button, or if you&#39;re running a query in a <strong>useEffect</strong> function?</p>
<h3>Lazy queries for the rescue</h3>
<p>In that case, you can use the <strong>useLazyQuery hook</strong>. As I mentioned earlier, It&#39;s pretty much the same as useQuery with one exception. When <strong>useLazyQuery</strong> is called, it does not immediately execute its associated query.</p>
<p>Instead, it returns a function in its result tuple that you can call whenever you&#39;re ready to execute the query.</p>
<p>On the other hand, the useLazyQuery hook is ideal for executing queries in response to different events, such as user actions. Whenever the useLazyQuery command occurs, the application will not immediately run any queries. You need to trigger a query <strong>manually</strong>.</p>
<h3>Take a look at the example</h3>
<pre><code class="language-javascript">import React from &quot;react&quot;;
import { useLazyQuery } from &quot;@apollo/client&quot;;
import { GET_SWEETIES } from &quot;./somewhere&quot;;

const MySweeties = () =&gt; {
  const [getSweeties, { loading, data }] = useLazyQuery(GET_SWEETIES);

  if (loading) return &lt;p&gt;Loading ...&lt;/p&gt;;

  const shouldDisplaySweeties =
    data &amp;&amp; data.sweeties ? (
      &lt;img src={data.sweeties.image} alt=&quot;sweeties&quot; /&gt;
    ) : (
      &lt;button onClick={() =&gt; getSweeties({ variables: { type: &quot;chocolate&quot; } })}&gt;
            Get sweeties!   
      &lt;/button&gt;
    );

  return shouldDisplaySweeties;
};
</code></pre>
<p>How useful is that?</p>
<h2>Refetch function</h2>
<p>The <strong>useQuery</strong> returns data, and the <strong>useLazyQuery</strong> returns data as well. Those hooks return cached data or server data. It doesn&#39;t matter, they return the requested data and the component renders that data. The default behavior of the useQuery hook is to perform a query when the component re renders. Lazy query allows performing a query on demand.</p>
<p>Moreover, an additional mechanism allows to <strong>refetch</strong> queries on demand to particular <strong>user action</strong>. Take a look at the example:</p>
<pre><code class="language-javascript">const { loading, error, data, refetch } = useQuery(YOUR_QERY, {
  variables: { sampleVar: &quot;abc&quot; },
});
</code></pre>
<p>You can bind refetch function in the JSX code like this:</p>
<pre><code class="language-javascript">&lt;button onClick={() =&gt; refetch({ sampleVar: &quot;xyz&quot; })}&gt;Refetch&lt;/button&gt;
</code></pre>
<p>As you can see, you can pass new variables to the function.</p>
<h2>Practical usage</h2>
<p>Let&#39;s use that knowledge about lazy queries and implement a QuickSearch component in the React app.</p>
<h3>QuickSearch component</h3>
<p>Create a quick search component with the following content:</p>
<pre><code class="language-javascript">import React, { useState } from &quot;react&quot;;
import { Form, FormControl } from &quot;react-bootstrap&quot;;
import QuickSearchSuggestions from &quot;../QuickSearchSuggestions&quot;;

const QuickSearch = () =&gt; {
  const [isValid, setIsValid] = useState(false);
  const [searchQuery, setSearchQuery] = useState(&quot;&quot;);
  const handleChange = (value) =&gt; {
    const valueEntered = !!value;
    const isValid = valueEntered &amp;&amp; value.length &gt; 3;

    setIsValid(isValid);
    setSearchQuery(value);
  };

  return (
    &lt;div className=&quot;justify-content-center d-flex position-relative&quot;&gt;
                  
      &lt;Form inline className=&quot;w-100&quot;&gt;
                        
        &lt;FormControl
          type=&quot;text&quot;
          placeholder=&quot;Search entire shop&quot;
          className=&quot;w-100&quot;
          onChange={(e) =&gt; handleChange(e.target.value)}
        /&gt;
                    
      &lt;/Form&gt;
                  
      &lt;QuickSearchSuggestions isValid={isValid} searchQuery={searchQuery} /&gt;
              
    &lt;/div&gt;
  );
};

export default QuickSearch;
</code></pre>
<p>I define two states there: isValid and searchQuery, and I pass them to the child component. I check the length of the value entered by a user in the search input, and if the length is higher than 3, I send the query.</p>
<h3>Quick Search Suggestion component</h3>
<p>Create a new component called <strong>QuickSearchSuggestion</strong> with the following content:</p>
<pre><code class="language-javascript">import React from &quot;react&quot;;

import { ListGroup } from &quot;react-bootstrap&quot;;
import PropTypes from &quot;prop-types&quot;;
import useQuickSearchSuggestions from &quot;../../hooks/useQuickSearchSuggestions&quot;;
import classes from &quot;./QuickSearchSuggestions.module.css&quot;;

const QuickSearchSuggestions = (props) =&gt; {
  const { isValid, searchQuery } = props;
  const { hasSuggestions, isLoading, isOpen, items } =
    useQuickSearchSuggestions({ isValid, searchQuery });

  const suggestions = items.map((product) =&gt; {
    return (
      &lt;ListGroup.Item key={product.id}&gt;
                    {product.name}
                
      &lt;/ListGroup.Item&gt;
    );
  });

  const shouldDisplaySuggestions = suggestions ? (
    &lt;div className={classes.suggestions}&gt;
              
      &lt;ListGroup&gt;
                    {suggestions}
                
      &lt;/ListGroup&gt;
          
    &lt;/div&gt;
  ) : null;

  if (isOpen &amp;&amp; hasSuggestions) {
    return shouldDisplaySuggestions;
  } else if (isLoading) {
    return (
      &lt;div className={classes.suggestions}&gt;
                    &lt;ListGroup.Item&gt;Loading...&lt;/ListGroup.Item&gt;
                
      &lt;/div&gt;
    );
  } else if (isOpen &amp;&amp; !hasSuggestions) {
    return (
      &lt;div className={classes.suggestions}&gt;
                    
        &lt;ListGroup&gt;
                          &lt;ListGroup.Item&gt;No products found&lt;/ListGroup.Item&gt;
                      
        &lt;/ListGroup&gt;
                
      &lt;/div&gt;
    );
  } else {
    return null;
  }
};

QuickSearchSuggestions.propTypes = {
  isValid: PropTypes.bool.isRequired,
  searchQuery: PropTypes.string.isRequired,
};

export default QuickSearchSuggestions;
</code></pre>
<p>I use a custom hook there: <strong>useQuickSearchSuggestion</strong>. That hook&#39;s responsibility is to provide data and business logic for the component. I am going to define that hook in the next step.</p>
<p>To finish the QuickSearchSuggestion, I create a CSS module <strong>QuickSearchSuggestions.module.cs</strong>s with the following styles:</p>
<pre><code class="language-css">.suggestions {
    left: 0;
    position: absolute;
    top: 100%;
    right: 0;
    z-index: 10;
}
</code></pre>
<h3>use Quick Search Suggestions custom hook</h3>
<pre><code class="language-javascript">import { useEffect, useState } from &quot;react&quot;;
import { useLazyQuery } from &quot;@apollo/client&quot;;
import { GET_QUICK_SEARCH_SUGGESTIONS } from &quot;../../queries/product.gql&quot;;
/**
 * The useQuickSearchSuggestions hook provides data and business logic for the QuickSearchSuggestions component
 *
 * @return {
 *  hasSuggestions {bool} - determines are products found based on provided search query
 *  isLoading {bool} - determines is data is currently loading
 *  isOpen {bool} - determines is component is opened
 *  items {array} - array with products returned from the API based on provided search query
 * }
 */
export const useQuickSearchSuggestions = (props) =&gt; {
  const { isValid, searchQuery } = props;
  const [isOpen, setIsOpen] = useState(false);
  const [hasSuggestions, setHasSuggestions] = useState(false);
  const [fetchSuggestions, { loading, data }] = useLazyQuery(
    GET_QUICK_SEARCH_SUGGESTIONS,
  );

  useEffect(() =&gt; {
    if (isValid) {
      fetchSuggestions({
        variables: {
          searchQuery,
        },
      });
      setIsOpen(true);
    } else {
      setIsOpen(false);
    }
  }, [fetchSuggestions, isValid, searchQuery]);

  useEffect(() =&gt; {
    data &amp;&amp; data.products &amp;&amp; data.products.items &amp;&amp; data.products.items.length
      ? setHasSuggestions(true)
      : setHasSuggestions(false);
  }, [data]);

  return {
    hasSuggestions,
    isLoading: loading,
    isOpen,
    items:
      data &amp;&amp; data.products &amp;&amp; data.products.items ? data.products.items : [],
  };
};

export default useQuickSearchSuggestions;
</code></pre>
<p>As you can see, I&#39;ve defined a lazy query that returns the <strong>fetchSuggestions</strong> function.</p>
<p>Then I use that function in effect. Before calling, I check if the search query is valid and update the isOpen flag. The fetchSugestions query function takes <strong>graphql</strong> variables (searchQuery), fetch the data, and returns it to the react component.</p>
<h3>graphQL query</h3>
<p>The last thing we need to do is create a graphQl query used by the useQuickSearchSuggestions hook.</p>
<pre><code class="language-javascript">export const GET_QUICK_SEARCH_SUGGESTIONS = gql`
    query getQuickSearchSuggestions($searchQuery: String!) {
        products(search: $searchQuery) {
            items {
                id
                name
                small_image {
                    url
                }
                url_key
                url_suffix
                price {
                    regularPrice {
                        amount {
                            value
                            currency
                        }
                    }
                }
            }
        }
    }
`;
</code></pre>
<hr>
<h2>Summary</h2>
<p>It looks like the Quick Search functionality works good:</p>
<p>So to execute GraphQL queries using <strong>Apollo GraphQL</strong> client, you can choose between two custom apollo react hooks:</p>
<ol>
<li><p>useQuery</p>
</li>
<li><p>useLazyQuery</p>
</li>
</ol>
<h3>What is the difference between useQuery hook and useLazyQuery?</h3>
<p>When using useLazyQuery, it does not perform the associated query. Instead, the function returns query functions in the result tuples called when the query is executed.</p>
<p>The useLazyQery hook is perfect when you, for example, need to fetch data for the GraphQL server on user action, user clicking, or typing.</p>
<h3>Apollo React Hooks and cached data</h3>
<p>Apollo client provides a cache mechanism called InMemoryCache. Thanks to that, the apollo cache query results in memory. Of course, Apollo react hooks supports that cache. You can set the default fetch policy globally and locally on each query.</p>
<h3>Handling GraphQL errors</h3>
<p>Lazy query error handling is pretty the same as when you use the <strong>useQuery</strong> hook.</p>
<p>#WebDevelopment #FrontendDevelopment #JavaScript #React #GraphQL #ApolloClient #ReactBootstrap #Tutorial #ConceptExplanation #BestPractices #Intermediate #API</p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[10 Answers to Your Questions About Magento Enterprise Versions]]></title>
            <description><![CDATA[<em>Published at 23/09/2022</em>]]></description>
            <link>https://frodigo.com/Blog/Archive/Frontend/Magento/10+Answers+to+Your+Questions+About+Magento+Enterprise+Versions</link>
            <guid isPermaLink="false">https://frodigo.com/Blog/Archive/Frontend/Magento/10+Answers+to+Your+Questions+About+Magento+Enterprise+Versions</guid>
            <pubDate>Fri, 23 Sep 2022 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p><em>Published at 23/09/2022</em></p>
<h2>How many versions are there in Magento?</h2>
<p>Usually, there is a division into two versions: Community (free version) and Enterprise (paid version). The enterprise version is not really called Magento anymore, but Adobe Commerce.</p>
<p>Adobe Commerce also offers the option of purchasing B2B modules (B2B for Adobe Commerce)</p>
<p>Regarding Magento Community, a fork of this version called Mage-OS is also available recently. It is a lightweight version of Magento maintained by the community.</p>
<p>In summary, there are versions of Magento available:</p>
<ul>
<li>Magento Community edition</li>
<li>Mage-OS</li>
<li>Adobe Commerce (Magento enterprise edition)</li>
<li>B2B for Adobe Commerce</li>
</ul>
<hr>
<h2>What is Magento Enterprise Edition?</h2>
<p>Magento Enterprise Version is Magento&#39;s paid version. Comparing Magento community, Enterprise Edition provides enhanced customization features on other advanced features. Additional features include promotional pricing for a range of stores, product categories, and more.</p>
<p>Moreover, you can also use hosting services (Magento Cloud), technical support, and security support.</p>
<h2>What is the difference between Magento CE and EE?</h2>
<hr>
<p>Community Edition is available free for Magento and does not charge additional for use. Enterprise Edition is available in Magento – costs are dependent on merchant sales. The higher the gross sales value of EE stores, the higher the fee is.</p>
<hr>
<h2>What features are available only in Magento 2 Enterprise Edition?</h2>
<p>Magento enterprise edition offers many valuable features that can help sellers to provide the best shopping experience for their clients</p>
<h3>Private sales</h3>
<p>Thanks to private sales, you can create personalized shopping experiences, limited-time sales, or create a private sales page.</p>
<h3>Gift Registry</h3>
<p>Adobe Commerce gives your customers the ability to create gift registries for special occasions and to invite their friends and family to purchase their gifts from the gift registry.</p>
<h3>Reward and Loyalty</h3>
<p>Possibility to implement unique programs and promote customer loyalty.</p>
<h3>Multiple wish lists</h3>
<p>Magento community edition allows customers to have only one wishlist, but in the Magento enterprise edition, customers can have more than one wishlist.</p>
<h3>Store credits</h3>
<p>Magento enterprise edition offers customers to pay for products by using store credits.</p>
<h3>Customer segmentation</h3>
<p>In Magento 2 enterprise edition, there is an option to display content and promotion to specific customer accounts.</p>
<h3>Customer attribute management</h3>
<p>Enterprise edition allows administrators to manage customer attributes in the admin panel so that they can add custom attributes to the customer model.</p>
<h3>Order management</h3>
<p>Magento Commerce offers integration with Order Management System (OMS) Connector</p>
<h3>Google tag manager</h3>
<p>Magento enterprise edition allows configuring Google Tag manager, while the Magento community edition doesn&#39;t have a google tag manager module out-of-the-box.</p>
<hr>
<h2>How do I know if Magento is a community or enterprise?</h2>
<p>Developers can easily check if a Magento enterprise edition or Community edition by inspecting the code or composer.json file. It&#39;s also possible to check the administration panel. If there are some features that are available only in the enterprise edition that means it is paid version</p>
<hr>
<h2>How much does Magento cost?</h2>
<p>The cost of Magento is specific to the needs of a particular business and requires a bespoke quote. Total costs are calculated from your retail store average sales value, as well as annual online sales. It starts at around $22,000 a year and reaches $125,000. Licenses are yours.</p>
<hr>
<h2>Why is Magento so expensive?</h2>
<p>Magento enterprise edition is a platform dedicated to large enterprise customers. Whether it is expensive is a subjective opinion. Magento will be expensive for some and not for others. I think that for customers considering Magento enterprise, price is not the most important factor.</p>
<hr>
<h2>What is the difference between the support of each version?</h2>
<p>Magento community support includes support on Community Forums, Open Source, documentation, StackExchange, Reddit, Blogs, and many other groups. The official forum documentation can be found in Adobe Commerce. Despite this support, the Magento business edition has its own support. Magento commerce customers that paid for the license can, of course, count on support from Adobe.</p>
<hr>
<h2>What is the feature coverage of the Magento enterprise edition in headless solutions?</h2>
<p>PWA Studio has little support for Magento enterprise features; Vue Storefront for Magento is integrated with the Magento community edition, so you won&#39;t find support for enterprise features there.</p>
<p>In both cases, you have to take custom development into account. You should also bear in mind that Magento GraphQL API does not support all enterprise features.</p>
<hr>
<h2>What is an alternative for Magento enterprise edition when I really want Magento?</h2>
<p>Magento community edition is good for many clients, but Magento commerce offers many interesting features. So community vs enterprise edition is theoretically a hard choice, but not really at all when you start thinking about license cost. You may ask, is it worth the money? The answer is: it depends on your business! I think small businesses should consider the Magento community edition with some third-party solutions and modules from the marketplace that can cover missing Magento 2 enterprise edition features.</p>
<hr>
<h2>Conclusion</h2>
<p>Magento community edition is a great proposition for small businesses, while Magento enterprise is a good solution for large players.</p>
<p>Magento enterprise is distinguished by additional features that are not available in the community version and support.</p>
<hr>
<h3>Resources</h3>
<p><a href="https://mage-os.org/">https://mage-os.org/</a></p>
<p><a href="https://developer.adobe.com/commerce/webapi/rest/b2b/">https://developer.adobe.com/commerce/webapi/rest/b2b/</a></p>
<p><a href="https://business.adobe.com/pl/products/magento/features.html">https://business.adobe.com/pl/products/magento/features.html</a></p>
<p>#WebDevelopment #ECommerce #Magento #AdobeCommerce #PWAStudio #VueStorefront #ConceptExplanation #ToolComparison #TerminologyDefinitions #Intermediate</p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to mock GraphQL queries and mutations]]></title>
            <description><![CDATA[<em>Last updated: 16/09/2022</em>]]></description>
            <link>https://frodigo.com/Blog/Archive/Frontend/Graphql/How+to+mock+GraphQL+queries+and+mutations</link>
            <guid isPermaLink="false">https://frodigo.com/Blog/Archive/Frontend/Graphql/How+to+mock+GraphQL+queries+and+mutations</guid>
            <pubDate>Fri, 16 Sep 2022 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p><em>Last updated: 16/09/2022</em></p>
<p>Nowadays, distributed architectures of software have become more popular, and along with the trend, software teams use the API-first approach to building products.</p>
<p>On the other hand, architects and developers decide to use the GraphQL API instead of REST more and more (but REST APIs are still good for sure)</p>
<p>I will not cite the well-known advantages and disadvantages of GraphQl here, but I want to stand the one that is not well-known but is quite important for developers:</p>
<blockquote>
<p><strong>One of the most significant advantages of using GraphQL is that a frontend developer can quickly mock sample data, and switch to real data when the backend is done.</strong></p>
</blockquote>
<h2>What exactly is mock-up data?</h2>
<p>Suppose some functionality needs data from the backend or somehow needs to communicate with the API, and this data is not available, or the API hasn’t been done yet. In that case, the Front-end developer needs to mock up some sample data. Take a look at the examples below:</p>
<h3>1. Displaying additional information about a product</h3>
<p>A development team is working on displaying additional information about a product on a product page. The data are called “Key features” and consist of an image, a name, and a description. This data will come from the backend. The backend team hasn’t started working on this functionality yet, so the Frontend developer decides to mock up this data and display this mockup on the front end. When the backend is done, the mockup data will be replaced with real data.</p>
<h3>2. Sending a message to a seller</h3>
<p>This functionality lets customers send a message to a seller. A customer fills out the form. He enters his name, surname, e-mail address, and message. Additionally, they must accept consent to the processing of personal data. The frontend developer has already built the form and validation and is at the stage of sending the form to the backend. The backend must pick up the form and return a success or error message, and this message will be displayed to the user. The backend part is not ready yet, so the Frontend developer needs to mock this interaction with the backend.</p>
<p>In summary, when some data like fields or even data collections have not been done yet in backend implementation, developers use fake values (mock data) and replace them with real data when the backend is ready.</p>
<p>In this article, I show how to mock:</p>
<ul>
<li><p>single fields</p>
</li>
<li><p>queries</p>
</li>
<li><p>mutation</p>
</li>
</ul>
<p>In the end, I will show you how to easily replace mock data with real backend data.</p>
<p>With this knowledge, you will be able to work on your front end more efficiently, even when the back end is lagging far behind.</p>
<hr>
<h2>Prerequisites</h2>
<p>Computer with a text editor, NodeJS, internet connection, basic JavaScript, React, and GraphQL skills.</p>
<h2>Create react app</h2>
<p>I use <a href="https://create-react-app.dev/docs/getting-started">https://create-react-app.dev/docs/getting-started</a> to scaffold a new project:</p>
<pre><code class="language-bash">npx create-react-app graphql-mocks
cd graphql-mocks
npm start
</code></pre>
<h2>Install Apollo Client</h2>
<p>Next, I use the command line to install the apollo client:</p>
<pre><code class="language-bash">npm install @apollo/client graphql
</code></pre>
<p>Apollo client allows connecting with GraphQL server and performing GraphQL operations like queries and mutations thanks to custom React hooks like useQuery or useMutation.</p>
<h2>Initialize Apollo client</h2>
<p>Once the client is installed, I can connect my front end with GraphQL API. This time I am going to use publically available <a href="https://api.spacex.land/graphql/">https://api.spacex.land/graphql/</a>.</p>
<p>First, I import Apolo Client, InMemoryCache, and ApolloProvider from @apollo/client.</p>
<pre><code class="language-javascript">import { ApolloClient, InMemoryCache, ApolloProvider } from &quot;@apollo/client&quot;;
</code></pre>
<p>Second, I initialize the client:</p>
<pre><code class="language-javascript">const client = new ApolloClient({
  uri: &quot;https://api.spacex.land/graphql/&quot;,
  cache: new InMemoryCache(),
});
</code></pre>
<p>That is the minimal config needed to make Apollo Client working - URL to API and in-memory cache initialized.</p>
<p>Third, I wrap the App component by ApolloProvider:</p>
<pre><code class="language-javascript">&lt;ApolloProvider client={client}&gt;
  &lt;App /&gt;
&lt;/ApolloProvider&gt;
</code></pre>
<p>ApolloProvided takes one argument: a client that we have already initialized. Once I wrap the App component with ApolloProvider, I can use the client in the App component and every child of the app component.</p>
<h2>How to mockup single fields</h2>
<p>In this example, I show you how to fetch existing fields from the API and how to add a field that doesn&#39;t exist in the response.</p>
<h3>Create the missions component</h3>
<p>In the beginning, I create the Missions component that will be responsible for displaying missions.</p>
<p>Create components \ missions \ Missions.jsx file:</p>
<pre><code class="language-javascript">export const Missions = () =&gt; {
  return (
    &lt;&gt;
      &lt;h1&gt;Missions&lt;/h1&gt;
      Missions will be listed here
    &lt;/&gt;
  );
};
</code></pre>
<p>Create components \ missions \ index.js file:</p>
<pre><code class="language-javascript">export { Missions } from &quot;./Missions&quot;;
</code></pre>
<p>Create components \ index.js file:</p>
<pre><code class="language-javascript">export { Missions } from &quot;./missions&quot;;
</code></pre>
<p>Import Missions component in the App.js:</p>
<pre><code class="language-javascript">import { Missions } from &quot;./components&quot;;
</code></pre>
<p>Render component:</p>
<pre><code class="language-javascript">function App() {
  return (
    &lt;main className=&quot;container&quot;&gt;
      &lt;Missions /&gt;
    &lt;/main&gt;
  );
}
</code></pre>
<p>Add styles to App.css:</p>
<pre><code class="language-css">.container {
  max-width: 1200px;
  margin: 0 auto;
}
</code></pre>
<hr>
<h3>The query for the data</h3>
<p>I want to fetch missions from the graphql server. To do so, I need to define a query.</p>
<p>I add file components \ missions\ missions.gql.js:</p>
<pre><code class="language-javascript">import { gql } from &quot;@apollo/client&quot;;

export default gql`
  {
    missions(limit: 10) {
      description
      id
      manufacturers
      name
      twitter
      website
      wikipedia
    }
  }
`;
</code></pre>
<p>I import the query and useQuery hook in components \ Missions \ missions.jsx:</p>
<pre><code class="language-javascript">import { useQuery } from &quot;@apollo/client&quot;;
import MISSIONS_QUERY from &quot;./missions.gql.js&quot;;
</code></pre>
<p>I use the useQuery hookto fetch graphql data:</p>
<pre><code class="language-javascript">const { loading, error, data } = useQuery(MISSIONS_QUERY);
</code></pre>
<p>I add a little logic to handle loading and errors state:</p>
<pre><code class="language-javascript">if (loading) return null;
if (error) return `Error! ${error}`;
if (!data?.missions?.length) {
  return &quot;No missions found&quot;;
}

const { missions } = data;
</code></pre>
<p>Finally, I render data returned from API:</p>
<pre><code class="language-javascript">{
  missions.map((mission) =&gt; {
    return (
      &lt;div className=&quot;mission&quot; key={mission.id}&gt;
        &lt;h2&gt;{mission.name}&lt;/h2&gt;
        &lt;p&gt;{mission.description}&lt;/p&gt;
        &lt;h3&gt;Manufacturers:&lt;/h3&gt;
        &lt;ol&gt;
          {mission.manufacturers?.map((manufacturer) =&gt; (
            &lt;li key={`${mission.id}-${manufacturer}`}&gt;{manufacturer}&lt;/li&gt;
          ))}
        &lt;/ol&gt;
        &lt;h3&gt;Links:&lt;/h3&gt;
        &lt;ul&gt;
          {mission.twitter?.length &amp;&amp; (
            &lt;li&gt;
              &lt;a href={mission.twitter}&gt;{mission.twitter}&lt;/a&gt;
            &lt;/li&gt;
          )}
          {mission.website?.length &amp;&amp; (
            &lt;li&gt;
              &lt;a href={mission.website}&gt;{mission.website}&lt;/a&gt;
            &lt;/li&gt;
          )}
          {mission.wikipedia?.length &amp;&amp; (
            &lt;li&gt;
              &lt;a href={mission.wikipedia}&gt;{mission.wikipedia}&lt;/a&gt;
            &lt;/li&gt;
          )}
        &lt;/ul&gt;
      &lt;/div&gt;
    );
  });
}
</code></pre>
<p>The complete code of the component looks like this:</p>
<pre><code class="language-javascript">import { useQuery } from &quot;@apollo/client&quot;;
import MISSIONS_QUERY from &quot;./missions.gql.js&quot;;

export const Missions = () =&gt; {
  const { loading, error, data } = useQuery(MISSIONS_QUERY);

  if (loading) return null;
  if (error) return `Error! ${error}`;
  if (!data?.missions?.length) {
    return &quot;No missions found&quot;;
  }

  const { missions } = data;

  return (
    &lt;&gt;
      &lt;h1&gt;Missions&lt;/h1&gt;
      {missions.map((mission) =&gt; {
        return (
          &lt;div className=&quot;mission&quot; key={mission.id}&gt;
            &lt;h2&gt;{mission.name}&lt;/h2&gt;
            &lt;p&gt;{mission.description}&lt;/p&gt;
            &lt;h3&gt;Manufacturers:&lt;/h3&gt;
            &lt;ol&gt;
              {mission.manufacturers?.map((manufacturer) =&gt; (
                &lt;li key={`${mission.id}-${manufacturer}`}&gt;{manufacturer}&lt;/li&gt;
              ))}
            &lt;/ol&gt;
            &lt;h3&gt;Links:&lt;/h3&gt;
            &lt;ul&gt;
              {mission.twitter?.length &amp;&amp; (
                &lt;li&gt;
                  &lt;a href={mission.twitter}&gt;{mission.twitter}&lt;/a&gt;
                &lt;/li&gt;
              )}
              {mission.website?.length &amp;&amp; (
                &lt;li&gt;
                  &lt;a href={mission.website}&gt;{mission.website}&lt;/a&gt;
                &lt;/li&gt;
              )}
              {mission.wikipedia?.length &amp;&amp; (
                &lt;li&gt;
                  &lt;a href={mission.wikipedia}&gt;{mission.wikipedia}&lt;/a&gt;
                &lt;/li&gt;
              )}
            &lt;/ul&gt;
          &lt;/div&gt;
        );
      })}
    &lt;/&gt;
  );
};
</code></pre>
<h3>Extend GraphQL schema on the client side</h3>
<p>Here, the main topic of the article begins! The Missions component renders some data about missions on the front like description, name, web links and so on.</p>
<p>I want to mock one single field, which is a sponsors field, and it is an array of strings. To do so, I create the graphql-type-defs.js file in the root of the project with the following content:</p>
<pre><code class="language-javascript">import { gql } from &quot;@apollo/client&quot;;

export default gql`
  extend type Mission {
    sponsors: [String]
  }
`;
</code></pre>
<p>That schema definition extends the Mission type and ads sponsors field to it.</p>
<p>Now I import my type definition in the index.js file:</p>
<pre><code class="language-javascript">import typeDefs from &quot;./graphql-type-defs&quot;;
</code></pre>
<p>And pass it to the ApolloClient constructor:</p>
<pre><code class="language-javascript">const client = new ApolloClient({
  uri: &quot;https://api.spacex.land/graphql/&quot;,
  cache: new InMemoryCache(),
  typeDefs,
});
</code></pre>
<h3>Define a read function with mock data</h3>
<p>The Next step is to define a custom read function to produce mock data for us.</p>
<p>Before I do that, I install FakerJS. it&#39;s a library (fake data generator) that helps produce fake and random data.</p>
<pre><code class="language-bash">npm install @faker-js/faker --save-dev
</code></pre>
<p>Then, I pass the configuration with object types policies to the InMemoryCache constructor:</p>
<pre><code class="language-javascript">cache: new InMemoryCache({
    typePolicies: {
        Mission: {
            fields: {
                sponsors: {
                    read() {
                        return [...faker.random.words(faker.datatype.number({
                            &#39;min&#39;: 1,
                            &#39;max&#39;: 5
                        })).split(&#39; &#39;)]
                    }
                },
            },
        },
    },
}),
</code></pre>
<p>That code defines the read() function for the sponsors&#39; field of the Mission type. The read() function returns fake objects. In this case, it returns a new array of from one to five elements. Elements in that array are random words.</p>
<h3>Query with the @client directive and display data</h3>
<p>To fetch the mock field, I need to add it to the query. To make it work, I need to use the @client directive. Take a look at the updated missions query:</p>
<pre><code class="language-javascript">export default gql`{
  missions(limit: 10) {
    description
    id
    manufacturers
    name
    twitter
    website
    wikipedia
    sponsors @client // here you go
  }
}
`;
</code></pre>
<p>Finally, I can render the sponsors field on the front end. I add this code to the render function of the missions component:</p>
<pre><code class="language-javascript">&lt;h3&gt;Sponsors:&lt;/h3&gt;
&lt;ol&gt;
    {mission.sponsors?.map(sponsor =&gt; &lt;li key={`${mission.id}-${sponsor}`}&gt;{sponsor}&lt;/li&gt;)}
&lt;/ol&gt;
</code></pre>
<hr>
<h2>How to mockup an entire query</h2>
<p>Mocking single fields is so useful. Moreover, sometimes devs want to mock a query or mutation that doesn&#39;t exist in the backend. Let&#39;s start by mocking a query.</p>
<h3>Add a new query to the schema</h3>
<p>Let&#39;s add the publications query that returns an array of publications (name of the publication and URL).</p>
<p>I extend the graphql-type-defs.js by adding new types:</p>
<pre><code class="language-javascript">type Query {
    publications: [Publication]
}

type Publication {
    name: String!
    url: String!
 }
</code></pre>
<h3>Define resolver</h3>
<p>Next, I need to define a resolver that will produce fake data for the publications query.</p>
<p>I create a graphql-resolvers.js file:</p>
<pre><code class="language-javascript">import { faker } from &quot;@faker-js/faker&quot;;

export default {
  Query: {
    publications: () =&gt; {
      const publications = [];
      const publicationLength = faker.datatype.number({
        min: 1,
        max: 5,
      });

      for (let i = 0; i &lt; publicationLength; i++) {
        publications.push({
          name: faker.lorem.sentence(),
          url: faker.internet.url(),
        });
      }
      return publications;
    },
  },
};
</code></pre>
<p>I defined the publications function that returns a new array of fake publications.</p>
<h3>Register resolver</h3>
<p>To register the resolver, you need to pass it to the ApolloClient constructor:</p>
<pre><code class="language-javascript">import resolvers from &quot;./graphql-resolvers&quot;;

const client = new ApolloClient({
  uri: &quot;https://api.spacex.land/graphql/&quot;,
  cache: new InMemoryCache({
    typePolicies: {
      Mission: {
        fields: {
          sponsors: {
            read() {
              return [
                ...faker.random
                  .words(
                    faker.datatype.number({
                      min: 1,
                      max: 5,
                    }),
                  )
                  .split(&quot; &quot;),
              ];
            },
          },
        },
      },
    },
  }),
  typeDefs,
  resolvers, // here you go
});
</code></pre>
<h3>Use mocked query in the app</h3>
<p>Let&#39;s create the publications component that displays mocked data.</p>
<p>components \ publications \ Publications.jsx</p>
<pre><code class="language-javascript">import { useQuery } from &quot;@apollo/client&quot;;
// 1. here is imported the publications query
import PUBLICATIONS_QUERY from &quot;./publications.gql.js&quot;;

export const Publications = () =&gt; {
  // 2. here the query is used
  const { loading, error, data } = useQuery(PUBLICATIONS_QUERY);

  if (loading) return null;
  if (error) return `Error! ${error}`;
  if (!data?.publications?.length) {
    return &quot;No publications found&quot;;
  }

  const { publications } = data;

  return (
    &lt;&gt;
      &lt;h1&gt;Publications&lt;/h1&gt;
      &lt;ol&gt;
        {publications?.map((publication) =&gt; (
          &lt;li key={publication.name}&gt;
            &lt;a href={publication.url}&gt;{publication.name}&lt;/a&gt;
          &lt;/li&gt;
        ))}
      &lt;/ol&gt;
    &lt;/&gt;
  );
};
</code></pre>
<p>(1.) I imported the publications query in this place, and here (2.) I used in it the useQuery hook.</p>
<p>As you can see from the component and useQuery hook perspective, it&#39;s not important if the quarry that is used is fake or real. It&#39;s transparent and works in the same way.</p>
<p>components \ publications \ publications.gql.js:</p>
<pre><code class="language-javascript">import { gql } from &quot;@apollo/client&quot;;

export default gql`
  {
    publications @client {
      name
      url
    }
  }
`;
</code></pre>
<p>Directive @client allows defining not only fields like in the previous example but also queries and mutations.</p>
<p>components \ publications \ index.js:</p>
<pre><code class="language-javascript">export { Publications } from &quot;./Publications&quot;;
</code></pre>
<p>components \ index.js:</p>
<pre><code class="language-javascript">export { Publications } from &quot;./publications&quot;; // added this import
export { Missions } from &quot;./missions&quot;;
</code></pre>
<p>Add the publication component in the App.js:</p>
<pre><code class="language-javascript">import &#39;./App.css&#39;;
import {Missions, Publications} from &quot;./components&quot;; // added import

function App() {
  return &lt;main className=&quot;container&quot;&gt;
    &lt;Missions/&gt;
    &lt;Publications/&gt; !&lt;-- added component --&gt;
  &lt;/main&gt;
}

export default App;
</code></pre>
<hr>
<h2>Ho to mock a graphql mutation</h2>
<p>The last example I want to show is how to mock a graphql mutation. Let&#39;s implement a simple form that allows users to submit a new publication. The form has two inputs: the title of the publication and its URL.</p>
<hr>
<h2>Add the PublicationForm component</h2>
<p>Create a file components \ PublicationForm \ PublicationForm.jsx</p>
<pre><code class="language-javascript">import { useCallback, useState } from &quot;react&quot;;

export const PublicationForm = () =&gt; {
  const [title, setTitle] = useState(&quot;&quot;);
  const [url, setUrl] = useState(&quot;&quot;);

  const submitForm = useCallback(
    (e) =&gt; {
      e.preventDefault();
      console.log(title, url);
    },
    [title, url],
  );
  return (
    &lt;form onSubmit={submitForm}&gt;
      &lt;legend&gt;Submit a new publication:&lt;/legend&gt;
      &lt;input
        type=&quot;text&quot;
        placeholder=&quot;Publication title&quot;
        value={title}
        onChange={(e) =&gt; setTitle(e.target.value)}
      /&gt;
      &lt;input
        type=&quot;text&quot;
        placeholder=&quot;Publication URL&quot;
        value={url}
        onChange={(e) =&gt; setUrl(e.target.value)}
      /&gt;
      &lt;button type=&quot;submit&quot;&gt;Submit&lt;/button&gt;
    &lt;/form&gt;
  );
};
</code></pre>
<p>So there are two fields in the form and the submit button. When a user clicks submit, the submitForm function is called. For now, it only logs to the console.</p>
<p>Create a file components \ publicationForm \ index.js</p>
<pre><code class="language-javascript">export { PublicationForm } from &quot;./PublicationForm&quot;;
</code></pre>
<p>Re-export component in components/index.js:</p>
<pre><code class="language-javascript">export { Publications } from &quot;./publications&quot;;
export { Missions } from &quot;./missions&quot;;
export { PublicationForm } from &quot;./publicationForm&quot;; // added here
</code></pre>
<p>Add the component to the render function of the app component:</p>
<pre><code class="language-javascript">import &quot;./App.css&quot;;
import { Missions, Publications, PublicationForm } from &quot;./components&quot;;

function App() {
  return (
    &lt;main className=&quot;container&quot;&gt;
      &lt;Missions /&gt;
      &lt;Publications /&gt;
      &lt;PublicationForm /&gt;
    &lt;/main&gt;
  );
}

export default App;
</code></pre>
<h2>Add the mutation to the schema</h2>
<p>Let&#39;s define a new mutation in our graphql schema:</p>
<pre><code class="language-javascript">type Mutation {
  addPublication(name: String!, url: String!): String
}
</code></pre>
<p>The final graphql-type-defs looks like this:</p>
<pre><code class="language-javascript">import { gql } from &quot;@apollo/client&quot;;

export default gql`
  extend type Mission {
    sponsors: [String]
  }

  type Query {
    publications: [Publication]
  }

  type Mutation {
    addPublication(name: String!, url: String!): String
  }

  type Publication {
    name: String!
    url: String!
  }
`;
</code></pre>
<h2>Define a resolver for the mutation</h2>
<p>Now, I am gonna add the addPublication mutation resolver to the graphql-resolvers.js file:</p>
<pre><code class="language-javascript">import { faker } from &quot;@faker-js/faker&quot;;

export default {
  Query: {
    // query resolvers
  },

  Mutation: {
    addPublication: (parent, args, context, info) =&gt; {
      console.log(parent, args, context, info);
      return &quot;Your publication has been submitted, thank you!&quot;;
    },
  },
};
</code></pre>
<p>I defined the mutation, and it returns a string. Of course, if you need more sophisticated testing of mocked mutation, you can add code here.</p>
<h2>How to use mocked mutation in the app</h2>
<p>Add components \ publicationForm \ addPublication.gql.js file:</p>
<pre><code class="language-javascript">import { gql } from &quot;@apollo/client&quot;;

export default gql`
  mutation addPublication($name: String!, $url: String!) {
    addPublication(name: $name, url: $url) @client
  }
`;
</code></pre>
<p>As you can see, here also I used the @client directive to define mock mutation</p>
<p>Update the publicationForm.jsx component code:</p>
<pre><code class="language-javascript">import { useCallback, useMemo, useState } from &quot;react&quot;;
import { useMutation } from &quot;@apollo/client&quot;;
import ADD_PUBLICATION_MUTATION from &quot;./addPublication.gql&quot;;

export const PublicationForm = () =&gt; {
  const [name, setName] = useState(&quot;&quot;);
  const [url, setUrl] = useState(&quot;&quot;);
  const [addPublication, { data, loading, error }] = useMutation(
    ADD_PUBLICATION_MUTATION,
  );

  const submitForm = useCallback(
    (e) =&gt; {
      e.preventDefault();
      addPublication({ variables: { name, url } });
    },
    [name, url, addPublication],
  );

  const results = useMemo(() =&gt; {
    return data?.addPublication;
  }, [data]);

  if (loading) return null;
  if (error) return `Error! ${error}`;

  return (
    &lt;form onSubmit={submitForm}&gt;
      &lt;legend&gt;Submit a new publication:&lt;/legend&gt;
      &lt;input
        type=&quot;text&quot;
        placeholder=&quot;Publication title&quot;
        value={name}
        onChange={(e) =&gt; setName(e.target.value)}
      /&gt;
      &lt;input
        type=&quot;text&quot;
        placeholder=&quot;Publication URL&quot;
        value={url}
        onChange={(e) =&gt; setUrl(e.target.value)}
      /&gt;
      &lt;button type=&quot;submit&quot;&gt;Submit&lt;/button&gt;
      &lt;div&gt;{results}&lt;/div&gt;
    &lt;/form&gt;
  );
};
</code></pre>
<p>Here I added the logic responsible for performing mutation. As for queries - it work the same with mocked mutation as with real ones.</p>
<hr>
<h2>How to use live data when it is ready</h2>
<p>Ok, so we have mocked some fields, queries, and mutations, and you may ask what you should do when the backend team implements all requested fields and operations in the API.</p>
<p>It&#39;s pretty simple. You should:</p>
<ol>
<li><p>1. remove @client annotations - when a particular field or operation is ready, just remove the @client directive from query/mutation</p>
</li>
<li><p>2. remove client resolvers - remove resolvers because they are not needed anymore when data is populated from API</p>
</li>
<li><p>3. remove client type definitions - same here, the schema should be implemented on the backend side, so it&#39;s no need anymore</p>
</li>
</ol>
<hr>
<h2>Summary</h2>
<p>In this article, I showed you how to mock GraphQL queries and mutations. Compared to a REST API, mocking GraphQL queries is much easier. The subsequent transition to real data only really involves a change in GraphQL queries and resolvers’ removal. In my opinion, mocking data in GraphQL is much easier than in REST, which is unquestionably beneficial for everyone.</p>
<p>When you want to mock some fields or operations on the client side using Apollo Client, please follow these steps:</p>
<ol>
<li><p>Create client-side GraphQL schema</p>
</li>
<li><p>Define custom resolvers/read function</p>
</li>
<li><p>Use @client directive in queries/mutations</p>
</li>
</ol>
<p>I hope you liked this article. Thanks for reading!</p>
<p>#WebDevelopment #FrontendDevelopment #BackendDevelopment #ProgrammingFundamentals #JavaScript #GraphQL #React #Apollo #REST #Tutorial #DeepDive #BestPractices #ProjectSetup #Intermediate #APISecurity</p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The full-stack guide to the GraphQL query]]></title>
            <description><![CDATA[<em>Last updated: 08/09/2022</em>]]></description>
            <link>https://frodigo.com/Blog/Archive/Frontend/Graphql/The+full-stack+guide+to+the+GraphQL+query</link>
            <guid isPermaLink="false">https://frodigo.com/Blog/Archive/Frontend/Graphql/The+full-stack+guide+to+the+GraphQL+query</guid>
            <pubDate>Thu, 08 Sep 2022 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p><em>Last updated: 08/09/2022</em></p>
<p>GraphQL helps prevent the overloading of data. Unlike Restful API, GraphQL allows specifying fields that will be received from the server. That will result in faster communication and less traffic on the network, which will reduce response time considerably. There are two basic types of operations in GraphQL: <strong>queries</strong> and <strong>mutations</strong>.</p>
<p>For reading data, you use query, while for modifying data, you use mutation. The operation is a simple string that allows the server to parse and respond to the data in both cases.</p>
<h2>What is a graphQL query?</h2>
<p>Each GraphQL API has a GraphQL schema. The schema describes how data looks and which operations are allowed. So if you have a cars object in your schema, you would query for it. The graphQL query would look like this:</p>
<pre><code class="language-javascript">query getCars {
  cars {
    brand,
    color
  }
}
</code></pre>
<p>In the above example, you can see a single graphQL query named <strong>getCars</strong>. In that GraphQL operation, there is the query defined that fetches two fields of <strong>cars</strong> type: <em>brand</em> and <em>color</em>. I will show you more details about GraphQL queries in this article, but first, let&#39;s see how it&#39;s possible to execute queries and consume any GraphQL API.</p>
<h2>Prerequisites</h2>
<p>In this article, I would like to show you two common things: using a GraphQL query on the client side and writing a basic query using NodeJS.</p>
<h3>GraphQL API</h3>
<p>For the first case, you don&#39;t need your GraphQL API. You can use some public GraphQL APIs to experiment.</p>
<p>A well-built API to start practicing with is the SpaceX API: <a href="https://api.spacex.land/graphql/">https://api.spacex.land/graphql/</a></p>
<h3>Graphql server</h3>
<p>In case when you want to create your GraphQL API(with queries), you need to create a GraphQL server. I am going to show you a straightforward example built using <strong>Node</strong> and <strong>ExpressJS</strong>.</p>
<h2>Where do you run a query in GraphQL?</h2>
<p>Let&#39;s start by consuming SpaceX API to learn fundamentals about graphQL queries. You have at least four options to play with that API:</p>
<ul>
<li><p>using GraphQL Playground</p>
</li>
<li><p>using GraphiQL Explorer</p>
</li>
<li><p>using Apollo Studio</p>
</li>
<li><p>using Chrome extensions like Altair GraphQL</p>
</li>
</ul>
<h3>GraphQL Playground &amp; GraphiQL Explorer</h3>
<p>Those are tools that allow you to play with every graphQL API. For example, when you put this URL in the browser: <a href="https://api.spacex.land/graphql/">https://api.spacex.land/graphql/</a> you will see GraphiQL explorer. There you can inspect schema, read the docs, run queries, mutations, and so on. GraphQL playground has pretty much the same set of features.</p>
<p>You can set up a playground on the project level or install it for macOS App.</p>
<p><strong>Note</strong>: GraphQL Playground and GraphiQL are joining forces. More info here: <a href="https://github.com/graphql/graphql-playground/issues/1143">https://github.com/graphql/graphql-playground/issues/1143</a></p>
<h3>Apollo Studio</h3>
<p>In case when you are creating your graphQL server using <a href="https://github.com/apollographql/apollo-server">https://github.com/apollographql/apollo-server</a>, you will be able to use Apollo Studio to inspect your API.</p>
<h3>Altair GraphQL Client</h3>
<p>Altair client is a Chrome extension that allows you to inspect GraphQL spec, query for all the data, send mutations, use subscriptions, etc.</p>
<p>Today I will use GraphiQL API because the SpaceX API provides it. Typically, you will use tools that are provided with the project/graphQL servers. If you develop your project, you are comfortable to choose own Playground, but this does not matter because soon, both tools will be merged.</p>
<p>Chrome extension is a good choice if you want to check sometimes fast and don&#39;t have a chance to use any Playground, but in general, you can use that extension and don&#39;t use playgrounds. All of those technics are good enough.</p>
<hr>
<h2>The query for the data</h2>
<p>Theory, theory, theory.... let&#39;s stop with that and get our hands dirty!</p>
<p>Let&#39;s try to get to all users!</p>
<pre><code class="language-json">{
  users {
    name
    rocket
  }
}
</code></pre>
<h3>How simple is that?</h3>
<p>In the above example, you can see a simple query. The <strong>&#39;users&#39;</strong> objects can be used to request data about all available users – &#39;users&#39; is an <strong>object</strong> in GraphQL terms. <strong>Objects</strong> hold data about an <strong>entity</strong>. Each object has fields. We used the name and rocket field in our case, but there are more fields for the users&#39; objects. Take a look at the docs:</p>
<pre><code class="language-json">id: uuid!
name: String
rocket: String
timestamp: timestampt!
twitter: String
</code></pre>
<p>Some fields cannot be empty (null), and an exclamation mark determines it at the end. That means that, for example, the id cannot be null or undefined. If will be, GraphQL will throw an error.</p>
<p>The results of the following query may look like this:</p>
<pre><code class="language-json">{
  &quot;data&quot;: {
    &quot;users&quot;: [
      {
        &quot;name&quot;: &quot;sherlock holmes&quot;,
        &quot;rocket&quot;: &quot;221B Baker Street&quot;
      },
      {
        &quot;name&quot;: &quot;sherlock holmes&quot;,
        &quot;rocket&quot;: &quot;221B Baker Street&quot;
      },
      {
        &quot;name&quot;: &quot;sherlock holmes&quot;,
        &quot;rocket&quot;: &quot;221B Baker Street&quot;
      },
      {
        &quot;name&quot;: &quot;sherlock holmes&quot;,
        &quot;rocket&quot;: &quot;221B Baker Street&quot;
      },
      {
        &quot;name&quot;: &quot;User_14211338&quot;,
        &quot;rocket&quot;: &quot;Space_101010101&quot;
      },
      {
        &quot;name&quot;: &quot;Elanthamil&quot;,
        &quot;rocket&quot;: &quot;Elanthamil&quot;
      }
    ]
  }
}
</code></pre>
<p>Congratulations! You made your first graphQL query! You are well on your way to becoming a GraphQL master, really!</p>
<hr>
<h2>Query with variables</h2>
<p>In the previous example, you have seen a straightforward query (anonymous operation). Now we are going to do something more complicated, like a query with variables. When you want to pass a variable to a graphQL query, you can pass them as parameters:</p>
<pre><code class="language-javascript">{
  users(limit: 10, order_by: {name: asc}) {
    name
    rocket
  }
}
</code></pre>
<p>That works in Playground, but it&#39;s not too valuable because the values of variables are hardcoded. Let&#39;s define a named query using (surprising) a <strong>query</strong> keyword:</p>
<pre><code class="language-javascript">query getUsers($limit: Int, $orderBy: [users_order_by!]) {
  users(limit: $limit, order_by: $orderBy) {
    name
    rocket
  }
}
</code></pre>
<p>As you can see here, we have passed two variables: <strong>$limit,</strong> which is a type of Int, and $orderBy, which is a type of [users_order_by!].</p>
<p>Types can be primitive like <strong>String</strong>, <strong>Int</strong>, <strong>Float</strong>, <strong>Boolean</strong>, and <strong>ID</strong>, and also you can declare custom types in the <a href="https://marcin-kwiatkowski.com/blog/graphql/introduction-to-the-apollo-local-state-and-reactive-variables">https://marcin-kwiatkowski.com/blog/graphql/introduction-to-the-apollo-local-state-and-reactive-variables</a> schema.</p>
<p>An exclamation mark means that this parameter cannot be empty. You have a special place called &quot;Query variables&quot; (at the bottom). It will help if you put a query variable there. Otherwise, the query will throw an error.</p>
<p>You can also specify a default variable in GraphQL. It works only for non-required arguments. Take a look:</p>
<pre><code class="language-javascript">query getUsers($limit: Int = 5, $orderBy: [users_order_by!]) {
  users(limit: $limit, order_by: $orderBy) {
    name
    rocket
  }
}
</code></pre>
<h2>Nested objects</h2>
<p>You can request nested fields in a query (based on graphQL schema):</p>
<pre><code class="language-javascript">query getLaunchesPast($limit: Int = 2) {
  launchesPast(limit: $limit) {
    mission_name
    launch_date_local
    launch_site {
      site_name_long
    }
    rocket {
      rocket_name
      first_stage {
        cores {
          flight
          core {
            reuse_count
            status
          }
        }
      }
    }
  }
}
</code></pre>
<p>The results may look like this:</p>
<pre><code class="language-json">{
  &quot;data&quot;: {
    &quot;launchesPast&quot;: [
      {
        &quot;mission_name&quot;: &quot;Starlink-15 (v1.0)&quot;,
        &quot;launch_date_local&quot;: &quot;2020-10-24T11:31:00-04:00&quot;,
        &quot;launch_site&quot;: {
          &quot;site_name_long&quot;: &quot;Cape Canaveral Air Force Station Space Launch Complex 40&quot;
        },
        &quot;rocket&quot;: {
          &quot;rocket_name&quot;: &quot;Falcon 9&quot;,
          &quot;first_stage&quot;: {
            &quot;cores&quot;: [
              {
                &quot;flight&quot;: 7,
                &quot;core&quot;: {
                  &quot;reuse_count&quot;: 6,
                  &quot;status&quot;: &quot;unknown&quot;
                }
              }
            ]
          }
        }
      },
      {
        &quot;mission_name&quot;: &quot;Sentinel-6 Michael Freilich&quot;,
        &quot;launch_date_local&quot;: &quot;2020-11-21T09:17:00-08:00&quot;,
        &quot;launch_site&quot;: {
          &quot;site_name_long&quot;: &quot;Vandenberg Air Force Base Space Launch Complex 4E&quot;
        },
        &quot;rocket&quot;: {
          &quot;rocket_name&quot;: &quot;Falcon 9&quot;,
          &quot;first_stage&quot;: {
            &quot;cores&quot;: [
              {
                &quot;flight&quot;: 1,
                &quot;core&quot;: {
                  &quot;reuse_count&quot;: 0,
                  &quot;status&quot;: null
                }
              }
            ]
          }
        }
      }
    ]
  }
}
</code></pre>
<h2>Directives</h2>
<p>Directives let you perform a conditional query for objects. There are two types of directives: include and skip. You can pass a bool variable to the query and then use this variable in the directive. Take a look at how it works:</p>
<pre><code class="language-javascript">query getLaunchesPast($limit: Int = 2, $withRockets: Boolean = false) {
  launchesPast(limit: $limit) {
    mission_name
    launch_date_local
    launch_site {
      site_name_long
    }
    rocket @include(if: $withRockets) {
      rocket_name
      first_stage {
        cores {
          flight
          core {
            reuse_count
            status
          }
        }
      }
    }
  }
}
</code></pre>
<p><strong>Note</strong>: Unfortunately, SpaceX API uses an older version of GraphQL tools, and those directives are not supported there.</p>
<p><strong>Note 2:</strong> GraphQL Server implementations may also add experimental features by defining completely new directives.</p>
<h2>How do GraphQL queries work?</h2>
<p>When you want to send a query from the front end, a single request comes to the graphQL server. GraphQL takes a query and looks at what you need, and then a resolver function gets all related data and returns a JSON object.</p>
<p>Theoretically, you can send a request as a simple HTTP request, but a good practice is to use one of the GraphQL clients to communicate with a server. Honestly, in real projects, you have to use the GraphQL client on the front end because it brings a lot of advantages like caching, mechanisms for sending queries, mutations, and so on.</p>
<hr>
<h2>How do I create a query in GraphQL?</h2>
<p>I showed you how to send a query. Now let&#39;s create our GraphQL server and define a query.</p>
<p>We are going to use NodeJS and Apollo Server to scaffold an app.</p>
<p>The app is a big word. It will be basic stuff. Let&#39;s create a query that returns Hello World. It is a fantastic idea, isn&#39;t it?</p>
<h3>Set-up a project</h3>
<p>Let&#39;s init a new project by <strong>npm init command.</strong> Then please install all necessary dependencies**:**</p>
<pre><code class="language-bash">npm install apollo-server graphql --save
</code></pre>
<h3>Create an index.js file</h3>
<p>Create an index.js file in the src directory(create the directory as well).</p>
<p>Put these imports at the beginning of the file:</p>
<pre><code class="language-javascript">const { ApolloServer, gql } = require(&quot;apollo-server&quot;);
</code></pre>
<h3>Create schema</h3>
<p>Let&#39;s create theschema. We have to make two types:</p>
<ol>
<li><p>The Query type with welcomeMessage query</p>
</li>
<li><p>The Message type is an object that represents our Message. It has only one field: text which cannot be empty.</p>
</li>
</ol>
<p>Put this code to the file:</p>
<pre><code class="language-javascript">const typeDefs = gql`
  type Query {
    welcomeMessage: Message!
  }

  type Message {
    text: String!
  }
`;
10;
</code></pre>
<h3>Create resolver</h3>
<p>It&#39;s time to create a resolver function for the welcomeMessage query. In real projects, resolvers communicate with databases or other data and collect data. In our trivial example, we want to return a hardcoded string.</p>
<p>Here you go:</p>
<pre><code class="language-javascript">const resolvers = {
  Query: {
    welcomeMessage: () =&gt; {
      return {
        text: &quot;Hello World&quot;,
      };
    },
  },
};
</code></pre>
<h3>Initialize server</h3>
<p>The last thing is to create a server instance:</p>
<pre><code class="language-javascript">const server = new ApolloServer({
  typeDefs,
  resolvers,
});

server.listen().then(({ url }) =&gt; {
  console.log(`🚀 Server ready at ${url}`);
});
</code></pre>
<p>You can run that server by typing <em>node ./src/index.js</em> in the console.</p>
<p>Now you can see something like this:</p>
<hr>
<h2>Summary</h2>
<p>I hope you enjoyed our quick tour through GraphQL queries. Let me summarize:</p>
<ul>
<li><p>GraphQL has two basic types of operations: queries and mutations</p>
</li>
<li><p>queries read data. Mutations write data</p>
</li>
<li><p>there are a few ways to consume or inspecting GraphQL API, like playgrounds and Browser extensions</p>
</li>
<li><p>graphQL query can be anonymous or can have a name</p>
</li>
<li><p>you can pass variables to a query</p>
</li>
<li><p>query variable can have a default value</p>
</li>
<li><p>each returned field can have a nested object with other data</p>
</li>
<li><p>There are predefined directives like @include and @skip, and also you can write your directives on the server side.</p>
</li>
<li><p>To create your graphQL server, you can use Node and Apollo servers.</p>
</li>
<li><p>Of course, you can even use PHP to write an apollo server, but who wants to write code in PHP?</p>
</li>
</ul>
<p>That essential information should help you to understand the basics of graphQL queries.</p>
<p>#WebDevelopment #BackendDevelopment #API #JavaScript #NodeJS #GraphQL #Apollo #REST #ExpressJS #ConceptExplanation #Tutorial #DeepDive #Intermediate</p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[2 ways of handling GraphQL errors in Apollo Client]]></title>
            <description><![CDATA[If you use Graphql Apollo Client with React, there are two ways (more precisely speaking) – two levels of handling errors:]]></description>
            <link>https://frodigo.com/Blog/Archive/Frontend/Graphql/2+ways+of+handling+GraphQL+errors+in+Apollo+Client</link>
            <guid isPermaLink="false">https://frodigo.com/Blog/Archive/Frontend/Graphql/2+ways+of+handling+GraphQL+errors+in+Apollo+Client</guid>
            <pubDate>Fri, 02 Sep 2022 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>If you use Graphql Apollo Client with React, there are two ways (more precisely speaking) – two levels of handling errors:</p>
<ul>
<li><p>operation level</p>
</li>
<li><p>application level</p>
</li>
</ul>
<h2>Operation-level errors handling</h2>
<p>In this case, you have access to the <strong>data</strong>, <strong>loading</strong>, and <strong>error</strong> fields, and you can use an error object, which can be used to show a conditional error message.</p>
<pre><code class="language-javascript">const { loading, error, data } = useQuery(YOUR_QUERY);

if (error) return &lt;p&gt;Error :(&lt;/p&gt;;
</code></pre>
<p>Of course, you can create a component responsible for displaying errors in your app.</p>
<pre><code class="language-javascript">import React from &quot;react&quot;;
import PropTypes from &quot;prop-types&quot;;

import classes from &quot;./ErrorMessage.module.css&quot;;

const ErrorMessage = (props) =&gt; {
  const { error, ...rest } = props;

  const shouldDisplayError =
    error &amp;&amp; error.message ? (
      &lt;div className={classes.errorMessage} {...rest}&gt;
        {error.message}
      &lt;/div&gt;
    ) : null;
  return shouldDisplayError;
};

ErrorMessage.propTypes = {
  error: PropTypes.shape({
    message: PropTypes.string.isRequired,
  }),
};

export default ErrorMessage;
</code></pre>
<p>This component is really straightforward. It receives an error as a prop and displays that error to the user.</p>
<h2>Application-level error handling</h2>
<p>Another approach is handling errors in Application-level. It allows you to create more complex logic.</p>
<p>Application-error handling lets you do whatever you want with errors. For example, you can log those errors to the console in <strong>development</strong> mode or use external tracking error tools like Sentry on <strong>production</strong>.</p>
<p>You can use this mechanism to display messages to the user as well. Let’s imagine that you have Messages Context in your app or you have a custom hook, and there you keep the whole logic for adding/removing/displaying messages.</p>
<p>If you use application-level error handling, you can pass error messages to your messages/notification manager and do whatever you want with them.</p>
<p>There are two types of errors.</p>
<ol>
<li><p>GraphQL errors (like in a previous example)</p>
</li>
<li><p>Network error (for example, if the app lost internet connection)</p>
</li>
</ol>
<h3>GraphQL errors</h3>
<p>There are three types of GraphQL errors:</p>
<ul>
<li><p><strong>syntax error</strong> - for example, when you made a mistake in a query or mutation</p>
</li>
<li><p><strong>resolver error</strong> - for example, when the GraphQL server was not able to resolve a query field</p>
</li>
<li><p><strong>validation error</strong> - for example, when provided data didn&#39;t pass validation on the server side.</p>
</li>
</ul>
<p>Note that when there is a resolver error, the GraphQL server returns partial data, but if there is a syntax or validation error, the server doesn&#39;t return data at all.</p>
<p>In the first case, the server responds with a <strong>200</strong> status code, otherwise returns a <strong>4xx</strong> status code (for syntax and validation errors)</p>
<h3>Network errors</h3>
<p>Network errors occur when there are communication problems with the GraphQL server. In this case, the server usually responds with a <strong>4xx</strong> or <strong>5xx</strong> response status code and no data.</p>
<h3>Error policies</h3>
<p>By default, the <a href="Introduction%20to%20the%20Apollo%20local%20state%20and%20reactive%20variables.md">Introduction%20to%20the%20Apollo%20local%20state%20and%20reactive%20variables.md</a> server returns partial data when there is a resolver error, but you can change this behavior by changing the error policy. There are three error policies:</p>
<ul>
<li><p><strong>none</strong> - the default one - if there are errors the <code>graphQLErrorsthe</code> field is populated and the <code>data</code> field is set to undefined (even if the server returns some data in response)</p>
</li>
<li><p><strong>all</strong> - both fields <code>data</code> and <code>graphQLErrors</code> are populated</p>
</li>
<li><p><strong>ignore</strong> - <code>graphQLErrors</code>field is ignored and not populated</p>
</li>
</ul>
<h4>How to specify error policy</h4>
<p>You can specify error policy globally on or query/mutation level.</p>
<h5>Global error policy</h5>
<p>You can set an error policy for queries and mutations using the <code>defaultOptions</code> object in the ApolloClient constructor. The example below shows an error policy all set for queries and an ignore policy for mutations.</p>
<pre><code class="language-javascript">import { ApolloClient, InMemoryCache } from &quot;@apollo/client&quot;;

const client = new ApolloClient({
  cache: new InMemoryCache(),
  uri: &quot;http://localhost:3000/&quot;,
  defaultOptions: {
    query: {
      errorPolicy: &quot;all&quot;,
    },
    mutate: {
      errorPolicy: &quot;ignore&quot;,
    },
  },
});
</code></pre>
<h4>Operation error policy</h4>
<p>To specify error policy on the operation level, you have to pass the <code>errorPolicy</code> field in options object like this:</p>
<pre><code class="language-javascript">const { loading, error, data } = useQuery(YOUR_QUERY, {
  errorPolicy: &quot;ignore&quot;,
});
</code></pre>
<h3>Implement application-level error handling</h3>
<p>To implement application-level error handling, we need to use a functionality called <strong>ApolloLink</strong>.</p>
<p>The Apollo Link library helps you customize the data flow between Apollo Client and your <strong>GraphQL</strong> server. You can define your client&#39;s network behavior as a chain of <strong>link</strong> objects that execute in a <strong>sequence</strong>.</p>
<p>Each link should represent either a self-contained modification to a GraphQL operation or a side effect (such as logging).</p>
<p>Take a look at a sample implementation of application-level error handling.</p>
<p>First, import the <strong>onError</strong> function.</p>
<pre><code class="language-javascript">import { onError } from &quot;@apollo/client/link/error&quot;;
</code></pre>
<p>Second, create the <strong>errorLink</strong>:</p>
<pre><code class="language-javascript">const errorLink = onError(({ graphQLErrors, networkError }) =&gt; {
  if (graphQLErrors) {
    console.log(graphQLErrors);
  }

  if (networkError) {
    // handle network error
    console.log(networkError);
  }
});
</code></pre>
<p>Third, use <strong>HttpLink</strong>, and from helper method to combine a single link that can be used in the Apollo client.</p>
<pre><code class="language-javascript">import { ApolloClient, InMemoryCache, ApolloProvider, from, HttpLink } from &#39;@apollo/client&#39;;

...

const httpLink = new HttpLink({ uri: &#39;https://&lt;API_URL&gt;&#39; })

const appLink = from([
    errorLink, httpLink
])

const client = new ApolloClient({
    link: appLink,
    cache: new InMemoryCache(),

});
</code></pre>
<hr>
<h2>Summary</h2>
<p>There are two types of errors that you can handle:</p>
<ol>
<li><p>network errors</p>
</li>
<li><p>GraphQL error</p>
</li>
</ol>
<p>There are three error policies (all, ignore, and none), and you can specify an error policy globally or on the operation level.</p>
<p>Moreover, there are two levels where you can handle those errors:</p>
<ol>
<li><p>application level</p>
</li>
<li><p>component (query/mutation) level</p>
</li>
</ol>
<p>Thanks to application-level error handling, you can use JavaScript error tracking tools on production and log errors to the console in local environments. Besides, you can use this mechanism to handle and display errors in your application.</p>
<p>#WebDevelopment #FrontendDevelopment #BackendDevelopment #JavaScript #React #GraphQL #ApolloClient #ConceptExplanation #Tutorial #BestPractices #Intermediate #ErrorHandling #APIIntegration</p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Magento graphql overview and how to get started in 2022]]></title>
            <description><![CDATA[<em>Last updated at 14/04/2022</em>]]></description>
            <link>https://frodigo.com/Blog/Archive/Frontend/Magento/Magento+graphql+overview+and+how+to+get+started+in+2022</link>
            <guid isPermaLink="false">https://frodigo.com/Blog/Archive/Frontend/Magento/Magento+graphql+overview+and+how+to+get+started+in+2022</guid>
            <pubDate>Thu, 14 Apr 2022 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p><em>Last updated at 14/04/2022</em></p>
<p><strong>GraphQL</strong> has become one of the most popular APIs for building web applications. It gives many benefits for frontend development:</p>
<ul>
<li><p>query language and the possibility to get precisely that data from the backend that you need</p>
</li>
<li><p>one endpoint for everything</p>
</li>
<li><p>self-documented API operations</p>
</li>
<li><p>validation out-of-the-box</p>
</li>
</ul>
<p><strong>Magento 2</strong> has supported GraphQL for a while, and in the case of creating a headless storefront, GraphQL API is a perfect choice. I want to show you the benefits of using GraphQL API and how to work with it, and at the end, I will show you some example queries that fetch some basic eCommerce stuff like categories, products, cms pages, and carts.</p>
<hr>
<h2>Magento 2 API: What&#39;s new?</h2>
<p>The main problem with GraphQL in Magento when it was introduced that developers met was the lack of supported Magento features. Magento itself has a massive amount of fantastic features, and it&#39;s essential to have as many of them as possible supported in API. Of course, it&#39;s an option to use REST API in some places instead of GraphQL, but it is not a perfect solution.</p>
<p>Anyway, Adobe and Magento community have been working hard to improve and develop Magento GQL API. <strong>They have recently added many new supported features related to a customer, my account, and b2b functionalities.</strong></p>
<p>On the other hand, module vendors started providing custom GraphQL queries and notations as a part of the custom GraphQL module. In case when an extension does not has GraphQL support, implementing GraphQL operations is still an option because Magento 2 API is extensible.</p>
<hr>
<h2>GraphQL compared to REST API</h2>
<p>I won&#39;t elaborate on the difference between GraphQL API and REST API (or even with soap API) because there are many great articles about that on the web. Still, I want to focus on GraphQL benefits from a frontend perspective. Read the below paragraphs to see more details.</p>
<h3>Query language and Graphql schema</h3>
<p>When you want to fetch some data from GraphQL API, you basically need to specify what data you need. To do so, you inspect the GraphQL schema and choose what you will query about.</p>
<p>For example, in Magento, there is a <strong>categoryList</strong> GraphQL query that returns an array containing information about the category. There are a lot of fields, and depending on the situation and your needs, you select whatever you want, for example, UID of category and name:</p>
<pre><code class="language-javascript">query getCategories(
    $filters: CategoryFilterInput,
  ) {
    categoryList(filters: $filters) {
      uid
      name
  }
}

// variables passed to the query:

{
  &quot;filters&quot;: {
    &quot;url_path&quot;: {
      &quot;eq&quot;: &quot;gear/bags&quot;
    }
  }
}

// results:
{
  &quot;data&quot;: {
    &quot;categoryList&quot;: [
      {
        &quot;uid&quot;: &quot;NA==&quot;,
        &quot;name&quot;: &quot;Bags&quot;
      }
    ]
  }
}
</code></pre>
<p>Here you can see the schema definition for the categoryList query:</p>
<p>Of course, there are more fields, but they are out of the screenshot, sorry 😆</p>
<hr>
<h3>One endpoint</h3>
<p>The next advantage of GraphQL is one endpoint for all requests. In REST API, each request has a different URL, and in addition, each URL can have multiple behaviors depending on the <strong>HTTP</strong> method like <strong>POST</strong>, <strong>GET</strong>, <strong>PUT</strong>, and so on.</p>
<p>GraphQL haters can say that it&#39;s not a problem. I agree it&#39;s not a problem, but let&#39;s explain that to frontend developers 😂</p>
<p>Using GraphQL is relatively easier for frontend developers than using REST API because everything is in one place, in one endpoint and even calling API is simpler because you have to change only the payload you send to API.</p>
<h3>Multiple resources at the same time</h3>
<p>In REST API, when you want to fetch multiple resources, for example, categories and products, you need to send requests to various endpoints. This complicates implementation and decreases performance because each millisecond count and each HTTP request increase communication time with APIs in the Core Web Vitals world.</p>
<p>Using GraphQL, you can fetch multiple resources in one query, so it&#39;s not a problem to fetch products and categories simultaneously, and it&#39;s not even a problem to fetch a cart as well.</p>
<h3>Validation</h3>
<p>GQL provides schema validation OOTB, so if you send a wrong request, you miss some variables, and you will see meaningful errors. For example, if you query for a field that doesn&#39;t exist, <a href="https://marcin-kwiatkowski.com/blog/graphql/2-ways-of-handling-graphql-errors-in-apollo-client">https://marcin-kwiatkowski.com/blog/graphql/2-ways-of-handling-graphql-errors-in-apollo-client</a> will inform you that the field you requested does not exist.</p>
<h3>No more over-fetching</h3>
<p>REST API typically returns a wall of data, and in most cases, 98% of data returned from REST API is redundant on the front. This dramatically increases the size of HTTP calls and decreases performance.</p>
<p>Well designed application that consumes GraphQL API fetches only that data that is needed in UI, so you get what you need, and the size of API calls is smaller</p>
<hr>
<h2>How to connect with Magento2 GraphQL API</h2>
<p>To connect with Magento 2 GraphQL API, you must send a request to the GraphQL endpoint. Typically is <strong><magento_base_url>/graphql.</strong></p>
<p>You can use some of the GraphQL playgrounds or browsers extensions to test GraphQL API and send some queries and mutations. I use <a href="https://chrome.google.com/webstore/detail/altair-graphql-client/flnheeellpciglgpaodhkhmapeljopja?hl=en">https://chrome.google.com/webstore/detail/altair-graphql-client/flnheeellpciglgpaodhkhmapeljopja?hl=en</a> chrome extension</p>
<p>Extensions like that are compelling and allow you to send graphql queries, mutations, set variables, HTTP headers, and read schema documentation so testing each GraphQL API is easy.</p>
<hr>
<h2>Magento GraphQL endpoint HTTP headers</h2>
<p>There is one crucial thing about sending GraphQL requests: the possibility of changing the response by adding specific HTTP headers.</p>
<h3>Magento 2 GraphQL API and Multistore</h3>
<p>When you have configured Multistore in the Magento backend, you can fetch data for different store views by passing the <strong>Store</strong> HTTP header.</p>
<p>Imagine that you have two store views: default and german, and those store views have set up different languages: English for the default store view and german for the german store. If you wanted to send a query for categories, you would have to use a graphQL query like this:</p>
<pre><code class="language-javascript">{
  categoryList {
      uid
      name
      url_key
      children {
        uid
        name
        url_key
      }
  }
}
</code></pre>
<p>The possible response can look like this:</p>
<pre><code class="language-json">{
  &quot;data&quot;: {
    &quot;categoryList&quot;: [
      {
        &quot;uid&quot;: &quot;Mg==&quot;,
        &quot;name&quot;: &quot;Default Category&quot;,
        &quot;url_key&quot;: null,
        &quot;children&quot;: [
          {
            &quot;uid&quot;: &quot;Mzg=&quot;,
            &quot;name&quot;: &quot;What&#39;s New&quot;,
            &quot;url_key&quot;: &quot;what-is-new&quot;
          },
          {
            &quot;uid&quot;: &quot;MjA=&quot;,
            &quot;name&quot;: &quot;Women&quot;,
            &quot;url_key&quot;: &quot;women&quot;
          },
          {
            &quot;uid&quot;: &quot;MTE=&quot;,
            &quot;name&quot;: &quot;Men&quot;,
            &quot;url_key&quot;: &quot;men&quot;
          },
          {
            &quot;uid&quot;: &quot;Mw==&quot;,
            &quot;name&quot;: &quot;Gear&quot;,
            &quot;url_key&quot;: &quot;gear&quot;
          },
          {
            &quot;uid&quot;: &quot;OQ==&quot;,
            &quot;name&quot;: &quot;Training&quot;,
            &quot;url_key&quot;: &quot;training&quot;
          },
          {
            &quot;uid&quot;: &quot;Mzc=&quot;,
            &quot;name&quot;: &quot;Sale&quot;,
            &quot;url_key&quot;: &quot;sale&quot;
          }
        ]
      }
    ]
  }
}
</code></pre>
<p>Assuming you have translated category names for the german store, you have just to set Store header equals &#39;german&#39; to fetch categories for the german store. You would see categories for a german store below.</p>
<pre><code class="language-json">{
  &quot;data&quot;: {
    &quot;categoryList&quot;: [
      {
        &quot;uid&quot;: &quot;Mg==&quot;,
        &quot;name&quot;: &quot;Default Category&quot;,
        &quot;url_key&quot;: null,
        &quot;children&quot;: [
          {
            &quot;uid&quot;: &quot;Mzg=&quot;,
            &quot;name&quot;: &quot;Was gibt&#39;s Neues&quot;,
            &quot;url_key&quot;: &quot;what-is-new&quot;
          },
          {
            &quot;uid&quot;: &quot;MjA=&quot;,
            &quot;name&quot;: &quot;Frau&quot;,
            &quot;url_key&quot;: &quot;women&quot;
          },
          {
            &quot;uid&quot;: &quot;MTE=&quot;,
            &quot;name&quot;: &quot;Männer&quot;,
            &quot;url_key&quot;: &quot;men&quot;
          },
          {
            &quot;uid&quot;: &quot;Mw==&quot;,
            &quot;name&quot;: &quot;Ausrüstung&quot;,
            &quot;url_key&quot;: &quot;gear&quot;
          },
          {
            &quot;uid&quot;: &quot;OQ==&quot;,
            &quot;name&quot;: &quot;Ausbildung&quot;,
            &quot;url_key&quot;: &quot;training&quot;
          },
          {
            &quot;uid&quot;: &quot;Mzc=&quot;,
            &quot;name&quot;: &quot;Sale&quot;,
            &quot;url_key&quot;: &quot;sale&quot;
          }
        ]
      }
    ]
  }
}
</code></pre>
<p>Remember that the query is the same so passing a Store header to requests allows you to fetch data for different stores. Magento multistore is mighty to have different categories, products, prices, languages, and cms content for different store views.</p>
<h3>Graphql queries in Magento and currency</h3>
<p>Price and currency are an essential part of each eCommerce platform because the product has prices, brands typically operate in different markets and different countries, and allow to buy products in other currencies. Luckily, GraphQL in Magento will enable you to fetch different prices for different stores with different currencies.</p>
<hr>
<h4>Fetch available currencies data from Magento 2</h4>
<p>You can set available currencies for each store view in the Magento backend panel. To do so, log in as an administrator and go to <strong>Stores -&gt; Configuration -&gt; General -&gt; Currency Setup.</strong> There you can see <strong>Allowed Currencies</strong> multi-select. When you uncheck the &quot;<strong>Use system value</strong>&quot; checkbox, you will be able to select available currencies for the selected store view.</p>
<p>The next step is to fetch those currencies to display the currencies switcher on the storefront.</p>
<p>To do that, use the currency graphql query:</p>
<pre><code class="language-json">{
  currency {
        available_currency_codes
    }
}
</code></pre>
<p>In my demo store it returns these results:</p>
<pre><code class="language-json">{
  &quot;data&quot;: {
    &quot;currency&quot;: {
      &quot;available_currency_codes&quot;: [&quot;EUR&quot;, &quot;PLN&quot;, &quot;USD&quot;]
    }
  }
}
</code></pre>
<p>When you need to fetch available currencies for other store views, you must again set the <strong>Store</strong> HTTP header (as in the example with categories that I have shown you earlier).</p>
<hr>
<h4>Fetch correct prices for the selected currency</h4>
<p>Based on available currencies data, it&#39;s easy to implement a currency switcher on a storefront and add the possibility for customers to select a currency. When customers choose a different currency than the default one, we have to send that information to Magento.</p>
<p>There is the <strong>Content-Currency</strong> HTTP header, and if it&#39;s passed to the GraphQL endpoint, Magento will return prices in the specified currency. For example, let&#39;s fetch some products from Magento:</p>
<pre><code class="language-javascript">query getProductsForCategory(
    $filter: ProductAttributeFilterInput,
  $productsPageSize: Int = 10,
   $currentProductsPage: Int = 1
  ) {
    products(filter: $filter, pageSize: $productsPageSize, currentPage: $currentProductsPage) {
      items {
        uid
        name
        price_range {
          maximum_price {
            final_price {
              currency
              value
            }
            regular_price {
              currency
              value
            }
          }
          minimum_price {
            final_price {
              currency
              value
            }
            regular_price {
              currency
              value
            }
          }
        }
      }
  }
}
</code></pre>
<p>Let&#39;s fetch just one product for one of the categories. I want to use the Bags category that has <strong>UID</strong> equals <strong>NA==</strong>. Variables that I pass to query look like this:</p>
<pre><code class="language-json">// variables
{
  &quot;filter&quot;: {
    &quot;category_uid&quot;: {
      &quot;eq&quot;: &quot;NA==&quot;
    }
  },
  &quot;productsPageSize&quot;: 1
}
</code></pre>
<p>For that query and variables, Magento returns this data for me:</p>
<pre><code class="language-json">{
  &quot;data&quot;: {
    &quot;products&quot;: {
      &quot;items&quot;: [
        {
          &quot;uid&quot;: &quot;MTQ=&quot;,
          &quot;name&quot;: &quot;Push It Messenger Bag&quot;,
          &quot;price_range&quot;: {
            &quot;maximum_price&quot;: {
              &quot;final_price&quot;: {
                &quot;currency&quot;: &quot;USD&quot;,
                &quot;value&quot;: 45
              },
              &quot;regular_price&quot;: {
                &quot;currency&quot;: &quot;USD&quot;,
                &quot;value&quot;: 45
              }
            },
            &quot;minimum_price&quot;: {
              &quot;final_price&quot;: {
                &quot;currency&quot;: &quot;USD&quot;,
                &quot;value&quot;: 45
              },
              &quot;regular_price&quot;: {
                &quot;currency&quot;: &quot;USD&quot;,
                &quot;value&quot;: 45
              }
            }
          }
        }
      ]
    }
  }
}
</code></pre>
<p>As you can see, the returned currency is <strong>USD</strong> (because it is the default one), and prices are in USD. Now I set the <strong>Content-Currency</strong> HTTP header to <strong>EUR, a</strong>nd Magento will return prices in EUR. Take a look at the below code:</p>
<pre><code class="language-json">{
  &quot;data&quot;: {
    &quot;products&quot;: {
      &quot;items&quot;: [
        {
          &quot;uid&quot;: &quot;MTQ=&quot;,
          &quot;name&quot;: &quot;Push It Messenger Bag&quot;,
          &quot;price_range&quot;: {
            &quot;maximum_price&quot;: {
              &quot;final_price&quot;: {
                &quot;currency&quot;: &quot;EUR&quot;,
                &quot;value&quot;: 31.8
              },
              &quot;regular_price&quot;: {
                &quot;currency&quot;: &quot;EUR&quot;,
                &quot;value&quot;: 31.8
              }
            },
            &quot;minimum_price&quot;: {
              &quot;final_price&quot;: {
                &quot;currency&quot;: &quot;EUR&quot;,
                &quot;value&quot;: 31.8
              },
              &quot;regular_price&quot;: {
                &quot;currency&quot;: &quot;EUR&quot;,
                &quot;value&quot;: 31.8
              }
            }
          }
        }
      ]
    }
  }
}
</code></pre>
<h3>Authorization with Magento 2 GraphQL</h3>
<p>Another crucial thing relates to HTTP headers is Authorization. When you want to fetch content available only for logged-in users, like customer data, you must set the <strong>Authorization HTTP header</strong>. Then Magento will return that data. Otherwise, graphQL queries without that header will return validation notice that the customer isn&#39;t authorized to fetch data specified in a request.</p>
<hr>
<h2>How to optimize Magento 2 GraphQL endpoint</h2>
<p>Magento haters say that Magento GQL API is slow, but I must disagree with that. Maybe it is not the fastest on the market, but there are several options to optimize each request.</p>
<p>To optimize Graphql endpoints, I recommend using Varnish and changing the HTTP method for graphql queries from POST to GET. Then you can cache several queries and reduce the time of response twice or even more!</p>
<hr>
<h3>Varnish caching example</h3>
<p>Here you have the query that fetches 20 products from Magento without Varnish caching:</p>
<p>Response time is 311ms when response time with Varnish is only 96ms for the same query!</p>
<hr>
<h2>Conclusion</h2>
<p>Magento GQL is an API that allows you to fetch data from Magento using queries and save information in Magento using mutations. If you would experiment with Magento 2 GraphQL, you have to set up the Magento store or use one of the public available Magento demos, then use one of the GraphQL Clients or Chrome extensions like Altair GraphQL client. You can use Postman as well.</p>
<p>Magento 2 has excellent support in GraphQL API for its features and advantages compared to REST architecture. Hence, it&#39;s a perfect choice when you want to fetch required data in your headless storefront.</p>
<h3>GraphQL and headless</h3>
<p>Nowadays, the most popular headless storefronts and boilerplates like Vue Storefront and PWA Studio use GraphQL API to communicate with Magento. Besides, Extensions providers for Magento 2 have started adding support for GraphQL in their extensions. That all can mean that headless storefronts, composable commerce, and all pretty new things will constantly be developing near future!</p>
<p>#WebDevelopment #BackendDevelopment #FrontendDevelopment #eCommerce #JavaScript #GraphQL #Magento #REST #Apollo #ConceptExplanation #Comparison #BestPractices #TechnicalOverview #Intermediate #API #PerformanceOptimization #Scalability</p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[React Context API: It is Not as Difficult as You Think]]></title>
            <description><![CDATA[<em>Last update at 03/02/2022</em>]]></description>
            <link>https://frodigo.com/Blog/Archive/Frontend/React/React+Context+API+-+It+Is+Not+as+Difficult+as+You+Think</link>
            <guid isPermaLink="false">https://frodigo.com/Blog/Archive/Frontend/React/React+Context+API+-+It+Is+Not+as+Difficult+as+You+Think</guid>
            <pubDate>Thu, 03 Feb 2022 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p><em>Last update at 03/02/2022</em></p>
<h2>A Guide to React Context and useContext() hook</h2>
<p>React Context gives data about the components regardless of the level of the component tree. The Context allows managing global data, such as global theme services, user preference, notifications, or more.</p>
<p>On the other hand, you can have Contexts for each more significant part of your app. In case you create an eCommerce react app, you can have Context for cart, the Context for account pages, checkout, and so on.</p>
<p>Context provides data and methods for child components, so the cart needs data about products in the cart. It offers methods to manipulate a cart like adding items, removing, applying discount codes, etc.</p>
<p>I like React because we can find many different solutions for different issues. We have a couple of varying form libraries, lots of CSS libraries, and we have several libraries specifically geared towards state data problems in React. It can be learned by experience in using the library in your project. Sometimes we may install and use libraries that we do not need.</p>
<p>The same is with the state management library. You can use external ones, or you can use context API. It&#39;s up to you! It would help if you thought three times before installing any state management library because Context solves issues well in typical cases.</p>
<hr>
<h2>Why Context API?</h2>
<p>React enables us to assemble reusable applications with components to repurpose the applications we create. So the React application contains several elements. When an application is rolled out, these components are often significant and unmaintained, which is why we divide them into small parts. This is a fascinating concept in React — you can have lots of components and have an efficient and concise application without having a significant component. When breaking down smaller components for maintenance purposes, they might need a little more data to work correctly.</p>
<h2>Use of provider pattern in React</h2>
<p>You probably know about the prop drill. As part of developing an application, you will probably find yourself attempting to drill down layers of components. We have to send props across different levels of components to make them available.</p>
<p>To summarize, prop drilling is a situation when you have to pass down props to a child component, and then to another child, and another.</p>
<h3>Context provides a way to avoid props drilling</h3>
<p>Take a look at the example:</p>
<pre><code class="language-javascript">import React, { useState } from &quot;react&quot;;

export const Account = () =&gt; {
  const [user, setUser] = useState({ name: &quot;Marcin&quot;, country: &quot;Poland&quot; });

  &lt;AccountDetails user={user} setUser={setUser} /&gt;;
};

export const EditAccount = (props) =&gt; {
  const { user, setUser } = props;

  return (
    &lt;form&gt;
      &lt;input
        type=&quot;text&quot;
        value={user.name}
        onChange={(e) =&gt;
          setUser({
            ...user,
            name: e.target.value,
          })
        }
      /&gt;

      &lt;input
        type=&quot;text&quot;
        value={user.country}
        onChange={(e) =&gt;
          setUser({
            ...user,
            country: e.target.value,
          })
        }
      /&gt;
    &lt;/form&gt;
  );
};

export const AccountDetails = (props) =&gt; {
  const { user, setUser } = props;

  return (
    &lt;&gt;
      &lt;h1&gt;{user.name}&lt;/h1&gt;
      &lt;p&gt;Country: {user.country}&lt;/p&gt;

      &lt;EditAccount user={user} setUser={setUser}&gt;&lt;/EditAccount&gt;
    &lt;/&gt;
  );
};
</code></pre>
<p>The Account component passes props user and SetUser to the account details component, and AccountDetailsComponent passes them to the EditAccount component.</p>
<p>Prop drilling, also called threading, is a good and helpful pattern, besides in some cases, you want to avoid using it. Let&#39;s consider when you have a notification system in your app. There are two available methods:</p>
<ol>
<li><p>addMessage</p>
</li>
<li><p>removeMessage</p>
</li>
</ol>
<p>You want to have the opportunity to use those methods in any component that you want.</p>
<h3>Parent component -&gt; consuming components communication</h3>
<p>To fix the problems with prop drilling, the global object must be accessible in the react tree directly by the components in the tree. React Context is a useful mechanism that allows you to pass data through each child component without prop drilling. React Context can help you a lot.</p>
<p><strong>Note: using Context in some components makes them depend on that Context. Please remember that when you are architecting your app.</strong></p>
<h2>Creating Context</h2>
<p>To create Context, you have to use react createContext method:</p>
<pre><code class="language-javascript">export const MessagesContext = createContext();
</code></pre>
<h2>Providing data to a Context</h2>
<p>Any Context object has its Provider component that allows passing data to the Context.</p>
<p>To pass data to the Provider component, you need to use value prop:</p>
<pre><code class="language-javascript">&lt;MyContext.Provider value={&lt;value here&gt;}&gt;
</code></pre>
<p>Take a look at the real example:</p>
<pre><code class="language-javascript">import React, { createContext, useReducer } from &quot;react&quot;;

import {
  MessagesReducer,
  messagesInitialState,
  addMessage as addMessageAction,
  removeMessage as removeMessageAction,
} from &quot;../../reducers/Messages&quot;;

export const MessagesContext = createContext();

export const MessagesProvider = ({ children }) =&gt; {
  const [{ messages }, dispatch] = useReducer(
    MessagesReducer,
    messagesInitialState,
  );

  const removeMessage = (message) =&gt; dispatch(removeMessageAction(message));

  const addMessage = (message) =&gt; dispatch(addMessageAction(message));

  return (
    &lt;MessagesContext.Provider
      value={{
        messages,

        addMessage,

        removeMessage,
      }}
    &gt;
      {children}
    &lt;/MessagesContext.Provider&gt;
  );
};
</code></pre>
<p>That Context uses the useReducer hook for state management and returns Provider with messages array, and two methods: addMessage and remove message.</p>
<hr>
<h2>Injecting Context to an App</h2>
<p>To inject your Context into an App, you need to wrap your app by the Provider component. Typically index.js or app.js file is the best place to do this:</p>
<pre><code class="language-javascript">import { MessagesProvider } from &quot;./contexts/Messages&quot;;

const App = () =&gt; {
  return (
    &lt;MessagesProvider&gt;
              
      &lt;Router&gt;
                    
        &lt;&gt;
                          
          &lt;PageHeader /&gt;
                          
          &lt;Messages /&gt;
                          
          &lt;Switch&gt;
                                
            &lt;Route path=&quot;/category/:categoryUrlKey&quot; component={CategoryRoute} /&gt;
                                
            &lt;Route path=&quot;/product/:productUrlKey&quot; component={ProductRoute} /&gt;
                            
          &lt;/Switch&gt;
                      
        &lt;/&gt;
                
      &lt;/Router&gt;
          
    &lt;/MessagesProvider&gt;
  );
};
</code></pre>
<h2>Using React Context in components with useContext hook</h2>
<p>React hooks enable us in functional components to store the state data. React has some hooks that are included. UseState, UseCallback, UseEffects and others. The other thing which we want to discuss in detail is useContext hook. UseContext hooks allow you to connect and consume contexts. This hook uses an argument that contains the Context of your choice. Then, you can access the context value.</p>
<p>The last thing I want to show you is using Context in child components.</p>
<p>We exported two things from the file where we declared Context:</p>
<ol>
<li><p>MessagesContext</p>
</li>
<li><p>MessagesProvider</p>
</li>
</ol>
<p>We used MessagesProvider to wrap the App component. At the same time, MessageContext references our Context, and we have to pass it as an argument to the useContext hook in the component when we want to use that Context.</p>
<pre><code class="language-javascript">import { MessagesContext } from &#39;../../contexts/Messages&#39;;
const Messages = () =&gt; {

    const { messages, removeMessage, addMessage } = useContext(MessagesContext);
    (...)
};
</code></pre>
<p>Now we can use all Context data in our component.</p>
<hr>
<h2>Summary</h2>
<p>React Context API provides another way to pass data to multiple components and be more precise: to all the children of Context components. The context object provides data and methods to a consumer component, and a consumer reads context value.</p>
<p>Alternatively, without react&#39;s Context, you can pass props down manually to each react component, but in this case, you will meet a &quot;props drilling&quot; problem. React&#39;s context API allows to avoid prop drilling, but on the other hand – it adds dependency between react components and Context.</p>
<p>Moreover, testing a react component that consumes context value is a bit more complicated, but it is, of course, double, and there are a few patterns to do that. Anyway, using Context is an option for state management in react projects instead of installing external state management libraries.</p>
<p>Complete set up of context API includes:</p>
<ol>
<li><p>Create Context by using react createContext method</p>
</li>
<li><p>Create context provider by using the Provider react component</p>
</li>
<li><p>Inject context provider to the component tree</p>
</li>
<li><p>Consume Context in the following components by using the useContext hook</p>
</li>
</ol>
<p>#WebDevelopment #FrontendDevelopment #ProgrammingFundamentals #JavaScript #React #Hooks #ConceptExplanation #Tutorial #BestPractices #Intermediate #StateManagement</p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Magento 2 product types]]></title>
            <description><![CDATA[<em>Published at 29/12/2021</em>]]></description>
            <link>https://frodigo.com/Blog/Archive/Frontend/Magento/Magento+2+product+types</link>
            <guid isPermaLink="false">https://frodigo.com/Blog/Archive/Frontend/Magento/Magento+2+product+types</guid>
            <pubDate>Wed, 29 Dec 2021 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p><em>Published at 29/12/2021</em></p>
<h2>Magento 2 product types</h2>
<p>There are six types of products in Magento:</p>
<ul>
<li><p>simple products</p>
</li>
<li><p>grouped products</p>
</li>
<li><p>bundle products</p>
</li>
<li><p>configurable products</p>
</li>
<li><p>virtual products</p>
</li>
<li><p>downloadable Products</p>
</li>
</ul>
<hr>
<h3>Simple products</h3>
<p>Simple Product is the most frequently used product type by store owners. Every such Product is considered a single item that is not subject to customization.</p>
<p>For instance, a book. Every Simple Product is a physical item and has an SKU (Store Keeping Unit) number.</p>
<p>Every single product has a set of attributes that you can set up in the admin panel. A simple product is a standalone product that can be sold separately or as a part of grouped Product, Configurable Product, or bundle product.</p>
<hr>
<h3>Grouped products</h3>
<p>Magento Grouped Product is a collection of Simple Products that can be bought together. For instance, someone buying a table may also be interested in complementary chairs.</p>
<p>You can create Grouped Products including a table and chairs and offer a discount. Your customer will be able to pay less when buying the set.</p>
<p>Products that are part of the grouped Product can be purchased together or separately. Take a look at the example grouped Product: Set of Sprite Yoga Straps:</p>
<p>As you can see, the Set of Sprite Yoga Straps grouped Product is a set of three simple products:</p>
<ul>
<li><p>Sprite Yoga Strap 6 foot</p>
</li>
<li><p>Sprite Yoga Strap 8 foot</p>
</li>
<li><p>Sprite Yoga Strap 10 foot</p>
</li>
</ul>
<hr>
<h3>Bundle products</h3>
<p>Bundle Product is a collection of similar products that differ in some detail. For instance, if a customer is looking for headphones, you can offer him five types of those that come from different manufacturers and are listed at different prices.</p>
<p>Your customer will surely appreciate that he&#39;s being presented with various options to choose from.</p>
<p>Another example is bundle product from default Magento 2 demo: Sprite yoga companion kit. When a customer goes to the product catalog and then to the Sprite yoga companion kit&#39;s product page, there is a &quot;Customize and add to cart&quot; button.</p>
<p>Then a customer can choose the type of ball and strap. Change the quantity of all simple products included in bundled product.</p>
<p>Grouped and bundle products in Magento 2 are beneficial types of products, and thanks to them, you can increase the sale of your products.</p>
<hr>
<h3>Configurable products</h3>
<p>Magento Configurable Product is a product type that allows you to post products that offer additional options to consider before purchasing.</p>
<p>Configurable Products in Magento are collections of Simple Products. A Configurable Product isn&#39;t an actual item in your store and doesn&#39;t come on its own.</p>
<p>A Configurable Product is a kind of container for Simple Products, allowing the customer to modify them to their liking.</p>
<p>To generate product variations for a configurable product, Magento 2 creates simple products. Each variation is a simple product that is not visible in the product catalog individually.</p>
<p>An excellent example of a Configurable Product is a shirt, where you can select its size and color.</p>
<p>Thanks to Magento&#39;s Configurable Products mechanism, the customer can customize some of the Product&#39;s elements.</p>
<hr>
<h3>Downloadable Products</h3>
<p>A downloadable product is a product that the customer can (surprise) download right after purchasing.</p>
<p>An e-book may serve as an example in this category. The customer expects that after he pays, he&#39;ll be able to download the Product to his hard drive.</p>
<p>An example of an excellent downloadable product is a Beginner&#39;s Yoga video from Magento 2 demo.</p>
<p>When customers buy a downloadable product, downloadable files will be visible on the &quot;My downloadable products&quot; page in a Customer account.</p>
<hr>
<h3>Virtual products</h3>
<p>Magento Virtual Product is the one that can&#39;t be downloaded or shipped. Virtual Product is mostly additional service related to the particular Product.</p>
<p>For instance, when buying a PC, you can purchase an extra warranty extension - this is what a Virtual Product is.</p>
<p>On the other hand, a virtual product is simple without weight. When customers buy a virtual product and go to checkout - there is no shipping step.</p>
<p>Simple and virtual products are pretty similar, but it&#39;s clear that digital Product does not have weight.</p>
<hr>
<h2>Other product types in Magento 2</h2>
<h3>Up sells, related, and cross-sell products</h3>
<p>Those listed above are products that are associated with other standalone products.</p>
<p>On the product page, you can see related and up-sell products. Related products are products similar to the main Product, and up-sell is identical as well, but they have higher prices.</p>
<p>Cross-sell products appear in the cart. For example, if you buy shoes, socks can be a cross-sell product.</p>
<h3>Custom options</h3>
<p>Product in Magento can have customizable options that allow offering a selection of options with a variety of text, selection, and date input types.</p>
<p>For example, you can sell licenses for your online course, and there can be three types of licenses. Basic, Standard, and Pro. Each one can have a different price.</p>
<h2>Support for product types in Magento headless solutions</h2>
<p>There are two main headless storefronts available for Magento 2: PWA Studio and Vue Storefront.</p>
<p>Let&#39;s look at how support for different Magento products looks in those headless storefronts.</p>
<table>
<thead>
<tr>
<th>Product type</th>
<th>Magento Monolith</th>
<th>PWA Studio</th>
<th>Vue Storefront - Magento 2 integration [beta]</th>
</tr>
</thead>
<tbody><tr>
<td>Simple</td>
<td>yes</td>
<td>yes</td>
<td>yes</td>
</tr>
<tr>
<td>Grouped</td>
<td>yes</td>
<td>no</td>
<td>yes</td>
</tr>
<tr>
<td>Configurable</td>
<td>yes</td>
<td>yes</td>
<td>yes</td>
</tr>
<tr>
<td>Bundle</td>
<td>yes</td>
<td>no</td>
<td>yes</td>
</tr>
<tr>
<td>Downloadable</td>
<td>yes</td>
<td>no</td>
<td>partial</td>
</tr>
<tr>
<td>Virtual</td>
<td>yes</td>
<td>no</td>
<td>partial</td>
</tr>
</tbody></table>
<p>Magento Luma is a benchmark for headless, and I hope that headless approaches will shortly support all Magento product types. Anyway, if you don&#39;t need support for virtual and downloadable products, VSF can be an option for you even now.</p>
<hr>
<h2>Conclusion</h2>
<p>Magento 2 provides pretty extensive options when it comes to creating products. This will help you switch things up and diversify the product range offered at your Magento 2 online store to meet the customers&#39; expectations.
Headless solutions are behind Magento Monolith in terms of support for product types, but it&#39;s only a matter of time when full support will be in place.</p>
<p>#WebDevelopment #E-commerce #BackendDevelopment #FrontendDevelopment #PHP #Magento2 #PWAStudio #VueStorefront #Headless #ConceptExplanation #TerminologyDefinitions #Comparison #Intermediate #DigitalTransformation</p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to get started with routing in React apps with React Router]]></title>
            <description><![CDATA[<em>Published at 2021-12-23</em>]]></description>
            <link>https://frodigo.com/Blog/Archive/Frontend/React/How+to+get+started+with+routing+in+React+apps+with+React+Router</link>
            <guid isPermaLink="false">https://frodigo.com/Blog/Archive/Frontend/React/How+to+get+started+with+routing+in+React+apps+with+React+Router</guid>
            <pubDate>Thu, 23 Dec 2021 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p><em>Published at 2021-12-23</em></p>
<h2>What is React Router?</h2>
<p>React itself is focused on building user interfaces, and it lacks a fully integrated routing solution. React Router is React&#39;s most popular routing library. It allows you to define paths in the same declarative style as most other libraries. A router allows your application to navigate by changing its browser&#39;s URL or browsing history while staying in synch with other elements.</p>
<h2>Understanding routes</h2>
<p>The matching logic to a component is delegated to the path-to-regexp library. With this behavior, you set which component should be displayed for a specific URL path.</p>
<p>It means that if you have, for example, an &quot;about me&quot; page and the path of this page is &quot;/about-me,&quot; you need to assign a component that Reacts will render for that specific path.</p>
<h2>What does React Router DOM do?</h2>
<p>React Router DOM allows you to implement dynamic routing in web apps. React RouterDOM supports component-based routing according to the app&#39;s requirements and the framework. In contrast, the traditional routing architecture provides routing services in a configuration outside of a currently active app. React Router is the best solution for creating React Applications that run in the browser. React router DOM is the quickest way to create routing in React.</p>
<hr>
<h2>Let&#39;s dive in</h2>
<p>This tutorial is split out among multiple areas. Our first task is to a create React app and install React Router using npm. Now we&#39;ll get down to some basic features of the React Router. Each concept and system for constructing these routes will be discussed along the course.</p>
<p>The full code for the project is published at this GitHub repository. This tutorial presents concepts of using React routing, the basics of React, hooks, and testing.</p>
<h3>Prerequisites</h3>
<p>I tested the code in Node 14.17.3. I set up the project using <strong>Create React App.</strong> You will also need a basic knowledge of JavaScript, HTML &amp; CSS add React to understand what is going on here, but if you need to learn React Router, you are familiar with those things.</p>
<p>By The Way: HTML means HyperText Markup Language, so it&#39;s not a <a href="https://time.com/12410/11-of-americans-think-html-is-an-std/">https://time.com/12410/11-of-americans-think-html-is-an-std/</a>.</p>
<h3>Scaffold the project</h3>
<p>As I mentioned before, you&#39;ll require the Node installed on your computer for this tutorial. Then you can follow <a href="https://create-react-app.dev/docs/getting-started/">https://create-react-app.dev/docs/getting-started/</a> to set up React project using Create React App.</p>
<p>Changes after this: step: <a href="https://github.com/Frodigo/react-router-tutorial/commit/dd4e2230153228220a0ed75136c74dc556aad657">https://github.com/Frodigo/react-router-tutorial/commit/dd4e2230153228220a0ed75136c74dc556aad657</a></p>
<h3>Setting up React Router</h3>
<p>Now you can install React Router by using npm or yarn. Let&#39;s use npm</p>
<pre><code class="language-bash">npm install react-router-dom@6
</code></pre>
<p>Changes after this: step: <a href="https://github.com/Frodigo/react-router-tutorial/commit/89ac1a9a16ec0fac0c1317635b72cf4f7cd72dcf">https://github.com/Frodigo/react-router-tutorial/commit/89ac1a9a16ec0fac0c1317635b72cf4f7cd72dcf</a></p>
<h3>Cleanin&#39; Out My Closet</h3>
<p>Before we go deeper, let&#39;s clean some code that CRA generated for us.</p>
<p>Replace index.js with this content:</p>
<pre><code class="language-javascript">import React from &quot;react&quot;;
import ReactDOM from &quot;react-dom&quot;;
import App from &quot;./App&quot;;

ReactDOM.render(&lt;App /&gt;, document.getElementById(&quot;root&quot;));
</code></pre>
<p>Remove these files: reportWebVitals.js, index.css, logo.svg, App.css, and App.test.js -who needs tests??? But wait, we can add our own tests later, don&#39;t worry, I am just trolling you.</p>
<p>Replace App.js with this content:</p>
<pre><code class="language-javascript">import React from &quot;react&quot;;

export const App = () =&gt; {
  return (
    &lt;&gt;
      &lt;h1&gt;Hello, hello, hello&lt;/h1&gt;
    &lt;/&gt;
  );
};

export default App;
</code></pre>
<p>Change title and description in public/index.html and remove unnecessary comments. The final result should look like this:</p>
<pre><code class="language-html">&lt;!doctype html&gt;
&lt;html lang=&quot;en&quot;&gt;
  &lt;head&gt;
    &lt;meta charset=&quot;utf-8&quot; /&gt;
    &lt;link rel=&quot;icon&quot; href=&quot;%PUBLIC_URL%/favicon.ico&quot; /&gt;
    &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1&quot; /&gt;
    &lt;meta name=&quot;theme-color&quot; content=&quot;#000000&quot; /&gt;
    &lt;meta name=&quot;description&quot; content=&quot;React router tutorial&quot; /&gt;
    &lt;link rel=&quot;apple-touch-icon&quot; href=&quot;%PUBLIC_URL%/logo192.png&quot; /&gt;

    &lt;link rel=&quot;manifest&quot; href=&quot;%PUBLIC_URL%/manifest.json&quot; /&gt;
    &lt;title&gt;React Router example&lt;/title&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;noscript&gt;You need to enable JavaScript to run this app.&lt;/noscript&gt;
    &lt;div id=&quot;root&quot;&gt;&lt;/div&gt;
  &lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p>Changes after this: step: <a href="https://github.com/Frodigo/react-router-tutorial/commit/82839415d55ad15c2411fec640a57a6c0bb8bcd7">https://github.com/Frodigo/react-router-tutorial/commit/82839415d55ad15c2411fec640a57a6c0bb8bcd7</a></p>
<hr>
<h3>It&#39;s not time for styling</h3>
<p>Yeah, so let&#39;s use React Bootstrap, which provides some components and styles to focus on writing React code. It is a good move, isn&#39;t it?</p>
<pre><code class="language-bash">npm install react-bootstrap bootstrap@5.1.3
</code></pre>
<p>Import bootstrap styles in the index.js. Just add this line after other imports:</p>
<pre><code class="language-javascript">import &quot;bootstrap/dist/css/bootstrap.min.css&quot;;
</code></pre>
<p>OK, that all was easy. Let do something less trivial than importing things from npm.</p>
<p>Changes after this: step: <a href="https://github.com/Frodigo/react-router-tutorial/commit/96843a7dd88cb1175790e527b86e5a25cc4e0fb0">https://github.com/Frodigo/react-router-tutorial/commit/96843a7dd88cb1175790e527b86e5a25cc4e0fb0</a></p>
<hr>
<h3>Add Router component</h3>
<p>To get React Router working in your App, you need to add a Router. Basically, it means that you need to wrap your app with a top-level router that makes all other React Router components and hooks work. A router is stateful, and it creates history with the initial location and subscribes to the URL.</p>
<p>React Router can subscribe to the URL changes thanks to the History object. Each user action that changes URL is kept in History Stack.</p>
<p>There are three types of those actions: <strong>PUSH</strong>, <strong>POP,</strong> and <strong>REPLACE</strong>.</p>
<ul>
<li><p>PUSH - a new entry is added to the history stack</p>
</li>
<li><p>POP - it happens when a user click Browser&#39;s back or forward buttons</p>
</li>
<li><p>REPLACE - Replace action is similar to PUSH, but it replaces the current entry in the history stack instead of adding a new one</p>
</li>
</ul>
<p>A location is an object built on top of a <strong>window. location</strong> object. In this object, you can find information about URL, and in general, it represents where a user is at the time.</p>
<p>There are three types of Routers in react-router-dom:</p>
<ul>
<li><p><strong>BrowserRouter -</strong> recommended for running React Router in a Web browser</p>
</li>
<li><p><strong>HashRouter -</strong> is used for apps where the URL should not be sent to a server for some reason. It&#39;s not recommended to use the Hash router unless you absolutely have to.</p>
</li>
<li><p><strong>MemoryRouter -</strong> the common case of using MemoryRouter is testing. It stores all information in an array.</p>
</li>
</ul>
<p>OK, so let&#39;s wrap our app by BrowserRouter.</p>
<p><em>index.js:</em></p>
<pre><code class="language-javascript">import React from &quot;react&quot;;
import ReactDOM from &quot;react-dom&quot;;
import { BrowserRouter } from &quot;react-router-dom&quot;;
import App from &quot;./App&quot;;
import &quot;bootstrap/dist/css/bootstrap.min.css&quot;;

ReactDOM.render(
  &lt;BrowserRouter&gt;
    &lt;App /&gt;
  &lt;/BrowserRouter&gt;,
  document.getElementById(&quot;root&quot;),
);
</code></pre>
<p>Changes after this: step: <a href="https://github.com/Frodigo/react-router-tutorial/commit/cdd0fbca793766e8b85ae01ed4ead1d795de1a68">https://github.com/Frodigo/react-router-tutorial/commit/cdd0fbca793766e8b85ae01ed4ead1d795de1a68</a></p>
<hr>
<h3>Add Navigation and links</h3>
<p>Update app component (App.js) file with this content:</p>
<pre><code class="language-javascript">import React from &quot;react&quot;;
import { NavLink } from &quot;react-router-dom&quot;;
import { Navbar, Container, Nav } from &quot;react-bootstrap&quot;;

export const App = () =&gt; {
  return (
    &lt;&gt;
      &lt;Navbar bg=&quot;light&quot; expand=&quot;lg&quot;&gt;
        &lt;Container&gt;
          &lt;Navbar.Brand&gt;
            &lt;NavLink
              to=&quot;/&quot;
              style={{ textDecoration: &quot;none&quot;, color: &quot;inherit&quot; }}
            &gt;
              Your account
            &lt;/NavLink&gt;
          &lt;/Navbar.Brand&gt;
          &lt;Navbar.Toggle aria-controls=&quot;basic-navbar-nav&quot; /&gt;
          &lt;Navbar.Collapse id=&quot;basic-navbar-nav&quot;&gt;
            &lt;Nav className=&quot;me-auto&quot;&gt;
              &lt;NavLink to=&quot;/address&quot; className=&quot;nav-link&quot;&gt;
                Address book
              &lt;/NavLink&gt;
              &lt;NavLink to=&quot;/orders&quot; className=&quot;nav-link&quot;&gt;
                Orders
              &lt;/NavLink&gt;
            &lt;/Nav&gt;
          &lt;/Navbar.Collapse&gt;
        &lt;/Container&gt;
      &lt;/Navbar&gt;
      &lt;Container className=&quot;mt-3&quot;&gt;
        &lt;h1&gt;Hello, hello, hello&lt;/h1&gt;
      &lt;/Container&gt;
    &lt;/&gt;
  );
};

export default App;
</code></pre>
<p>We imported a NavLink component here from react-router-dom:</p>
<pre><code class="language-javascript">import { NavLink } from &quot;react-router-dom&quot;;
</code></pre>
<p>A <strong>NavLink</strong> component is a special type of the Link component with an additional feature. It can have an &quot;active&quot; class where the URL is the same as &quot;to property.</p>
<p>A Link React component renders <code>&lt;a&gt;</code> tag with real href property. The difference about the real <code>&lt;a&gt;</code> tag is that React Router will handle navigation to specific locations when you use Link (and NavLink).</p>
<p>So in our app, even we haven&#39;t declared any routes yet, the URL is changed without page reloading thanks to React Router.</p>
<p>Besides, we imported there some Bootstrap stuff:</p>
<pre><code class="language-javascript">import { Navbar, Container, Nav } from &quot;react-bootstrap&quot;;
</code></pre>
<p>Summarizing, we added three links: <strong>HomePage</strong>: /, <strong>Address book</strong>: /address, and <strong>Orders</strong>: /orders.</p>
<p>Changes after this: step: <a href="https://github.com/Frodigo/react-router-tutorial/commit/0492a826f8047a48161ccb87e535b9edc280cea6">https://github.com/Frodigo/react-router-tutorial/commit/0492a826f8047a48161ccb87e535b9edc280cea6</a></p>
<hr>
<h3>Add the first route</h3>
<p>React Router is a declarative routing framework that means you configure Routes to use standard React components.</p>
<p>At the end of this step, you now have a react application to find navigation links showing the components for each route.</p>
<p>Let&#39;s implement the first route. To do so, we need just to import Route and Routes components in the app and use them in this way:</p>
<pre><code class="language-javascript">import { Routes, BrowserRouter, Route} from &quot;react-router-dom&quot;;

(...)

&lt;BrowserRouter&gt;
    &lt;Routes&gt;
        &lt;Route path=&quot;/&quot; element={&lt;App /&gt;} /&gt;
    &lt;/Routes&gt;
&lt;/BrowserRouter&gt;
</code></pre>
<p>So we have a route that handles the &quot;/&quot; path and renders the <strong>App,</strong> a React component. Basically, nothing has changed in our app so far. Let&#39;s implement other routes.</p>
<p>Changes after this step: <a href="https://github.com/Frodigo/react-router-tutorial/commit/3157af6cfe8db2f5354a0fbde9029b1073658844">https://github.com/Frodigo/react-router-tutorial/commit/3157af6cfe8db2f5354a0fbde9029b1073658844</a></p>
<hr>
<h3>Add first nested routes and outlet</h3>
<p>React Router uses nested routes to provide the most detailed routing details inside child components. Those routes group your routing information directly into components to render other components.</p>
<p>By the finish of this step, you will have various ways of providing info. This is a little additional code, but the routes keep the child&#39;s parents in line. Not every project uses a nested route: some prefer an explicit list.</p>
<p>Nested routes allow you to build a complex system of routing. Each route defines a portion of the URL through segments, and a single URL can match multiple routes. Take a look:</p>
<p>Here is our main route:</p>
<pre><code class="language-bash">/
</code></pre>
<p>Here is the route for the address book:</p>
<pre><code class="language-bash">/address
</code></pre>
<p>And here is the route for address details</p>
<pre><code class="language-bash">/address/:addressId
</code></pre>
<p>So the route is built by three routes: / + address/ + :/addressId</p>
<p>Let&#39;s implement that scenario. Please, replace <code>**&lt;Route path=&quot;/&quot; element={&lt;App /&gt;} /&gt;**</code> by:</p>
<pre><code class="language-javascript">&lt;Route path=&quot;/&quot; element={&lt;App /&gt;}&gt;
  &lt;Route path=&quot;address&quot; element={&lt;AddressBook /&gt;}&gt;
    &lt;Route path=&quot;:addressId&quot; element={&lt;AddressDetails /&gt;} /&gt;
  &lt;/Route&gt;
&lt;/Route&gt;
</code></pre>
<p>Hero you go!</p>
<p>Of course, we need to define two new components: AddressBook, and AddressDetails</p>
<p>src/routes/AddressBook/addressBook.js:</p>
<pre><code class="language-javascript">import React from &quot;react&quot;;

export const addressBook = () =&gt; {
  return &lt;p&gt;Address book will be here&lt;/p&gt;;
};

export default addressBook;
</code></pre>
<p>src/routes/AddressBook/index.js</p>
<pre><code class="language-javascript">export { default } from &quot;./addressBook&quot;;
</code></pre>
<p>Do the same for address details (and do not forget about importing these routes in index.js!)</p>
<p>That should work, but wait. If you go now for the address page, you will see that the address route is rendered, but it looks pretty the same as the index route, but we except that there will be a paragraph: <strong>Address book will be here.</strong></p>
<p>To render the content of any child, you need to use the <strong>Outlet</strong> component that renders the next match in a set of matches.</p>
<p>Please import the Outlet React component to the App component:</p>
<pre><code class="language-javascript">import { NavLink, Outlet } from &quot;react-router-dom&quot;;
</code></pre>
<p>and add it below the <code>&lt;h1&gt;</code></p>
<pre><code class="language-javascript">&lt;Container className=&quot;mt-3&quot;&gt;
  &lt;h1&gt;Hello, hello, hello&lt;/h1&gt;
  &lt;Outlet /&gt;
&lt;/Container&gt;
</code></pre>
<p>Now the paragraph from the address book component is in place.</p>
<p>Changes after this: step: <a href="https://github.com/Frodigo/react-router-tutorial/commit/4fcee983b94da41991244463b6092fbfd80255ad">https://github.com/Frodigo/react-router-tutorial/commit/4fcee983b94da41991244463b6092fbfd80255ad</a></p>
<hr>
<h3>Add Index routes</h3>
<p>Let&#39;s go ahead and add some content to the address book. First, add some addresses:</p>
<pre><code class="language-javascript">const addresses = [
  {
    id: 1,
    addressName: &quot;Polna 1, Wrocław&quot;,
  },
  {
    id: 2,
    addressName: &quot;Wrocławska 2, Warszawa&quot;,
  },
];
</code></pre>
<p>Then, render navigation with addresses:</p>
<pre><code class="language-javascript">const navLinks = addresses.map((address) =&gt; {
  return (
    &lt;ListGroupItem key={address.id}&gt;
      &lt;NavLink to={`/address/${address.id}`} key={address.id}&gt;
        {address.addressName}
      &lt;/NavLink&gt;
    &lt;/ListGroupItem&gt;
  );
});

const shouldDisplayNav =
  navLinks &amp;&amp; navLinks.length ? (
    &lt;ListGroup&gt;{navLinks}&lt;/ListGroup&gt;
  ) : (
    &lt;p&gt;There are no addresses.&lt;/p&gt;
  );
</code></pre>
<p>Return all stuff with a nice layout:</p>
<pre><code class="language-javascript">return addresses ? (
  &lt;Row&gt;
    &lt;Col sm=&quot;3&quot;&gt;{shouldDisplayNav}&lt;/Col&gt;
    &lt;Col sm=&quot;9&quot;&gt;
      &lt;Outlet /&gt;
    &lt;/Col&gt;
  &lt;/Row&gt;
) : (
  &lt;Row&gt;
    &lt;p&gt;There are no addresses.&lt;/p&gt;
  &lt;/Row&gt;
);
</code></pre>
<p>Do not forget about imports:</p>
<pre><code class="language-javascript">import { NavLink, Outlet } from &quot;react-router-dom&quot;;
import { ListGroupItem, ListGroup, Col, Row } from &quot;react-bootstrap&quot;;
</code></pre>
<p>On the right of navigation, there is a space for address details, but initially then is empty space. When you click on an address in navigation, you can see address details.</p>
<p>There is a way to add some improvements! Let&#39;s add a paragraph that says: &quot;Please select an address.&quot; To do so that you can use another pretty cool feature of React Router called: <strong>Index route</strong></p>
<p>Add this code to index.js to AddressBook route component:</p>
<pre><code class="language-javascript">&lt;Route index element={&lt;p&gt;Select an address.&lt;/p&gt;} /&gt;
</code></pre>
<p>Now, when you go to the address book, you can see &quot;Select an address&quot; text by default.</p>
<p>Let&#39;s do something similar for the home route:</p>
<pre><code class="language-javascript">&lt;Route
  index
  element={
    &lt;&gt;
      &lt;h2&gt;Welcome in your account.&lt;/h2&gt;
      &lt;p&gt;Please use the navigation above to see Address book or your orders.&lt;/p&gt;
    &lt;/&gt;
  }
/&gt;
</code></pre>
<p>Remove this code from App.js</p>
<pre><code class="language-javascript">&lt;h1&gt;Hello, hello, hello&lt;/h1&gt;
</code></pre>
<p>Changes after this: step: <a href="https://github.com/Frodigo/react-router-tutorial/commit/cb81a97541fb8de9601f8774cfb267967ea6cd38">https://github.com/Frodigo/react-router-tutorial/commit/cb81a97541fb8de9601f8774cfb267967ea6cd38</a></p>
<hr>
<h3>Use URL params</h3>
<p>We have already defined a route for Address Details that receives addressId param:</p>
<pre><code class="language-javascript">&lt;Route path=&quot;:addressId&quot; element={&lt;AddressDetails /&gt;} /&gt;
</code></pre>
<p>When you click on addresses, the URL is changing:</p>
<pre><code class="language-bash">http://localhost:3000/address/1
http://localhost:3000/address/2
</code></pre>
<p>&quot;1&quot; and &quot;2&quot; in this case are addresses ID. The question is: how do we handle those params in the AddressDetails React component?</p>
<h4>useParams hook</h4>
<p>React Router provides a useParams hook that allows you to handle URL params. Take a look:</p>
<pre><code class="language-javascript">import React from &quot;react&quot;;
import { useParams } from &quot;react-router-dom&quot;;

export const AddressDetails = () =&gt; {
  const { addressId } = useParams();
  return &lt;p&gt;Address details for {addressId} will be here&lt;/p&gt;;
};

export default AddressDetails;
</code></pre>
<p>Now, addressId is handled by the AddressDetails component.</p>
<p>Changes after this step: <a href="https://github.com/Frodigo/react-router-tutorial/commit/f2f833a41e994bd1dac366648d44739d98a38a82">https://github.com/Frodigo/react-router-tutorial/commit/f2f833a41e994bd1dac366648d44739d98a38a82</a></p>
<h3>Use search params</h3>
<p>React Routes provides a <strong>useSearchParams</strong> hook that allows you to read and modify a <strong>query</strong> part of a URL (q=). Let&#39;s use it to add some filtering to the App.</p>
<p>First import useSearchParams hook in the AddressBook React component ad get searchParams, and setSearchParams from it:</p>
<pre><code class="language-javascript">import { useSearchParams } from &quot;react-router-dom&quot;;

// below in the compoonent body:

const [searchParams, setSearchParams] = useSearchParams();
</code></pre>
<p>Second, add a search form. To do so, add this code at the beginning of the return function:</p>
<pre><code class="language-javascript">&lt;Col sm=&quot;12&quot;&gt;
  &lt;nav&gt;
    &lt;InputGroup size=&quot;sm&quot; className=&quot;mb-3&quot;&gt;
      &lt;InputGroup.Text id=&quot;address-search&quot;&gt;
        Search for an address
      &lt;/InputGroup.Text&gt;
      &lt;FormControl
        aria-label=&quot;Search for an address&quot;
        aria-describedby=&quot;address-search&quot;
        value={searchParams.get(&quot;filter&quot;) || &quot;&quot;}
        onChange={(event) =&gt; {
          const filter = event.target.value;
          if (filter) {
            setSearchParams({ filter });
          } else {
            setSearchParams({});
          }
        }}
      /&gt;
    &lt;/InputGroup&gt;
  &lt;/nav&gt;
&lt;/Col&gt;
</code></pre>
<p>A function bound on the onCahnge event sets the current input value to the URL query param.</p>
<p>Third, let&#39;s read the query param and filter addresses by it:</p>
<pre><code class="language-javascript">const navLinks = addresses
  .filter((address) =&gt; {
    const filter = searchParams.get(&quot;filter&quot;);
    if (!filter) return true;

    let name = address.addressName.toLowerCase();

    return name.startsWith(filter.toLowerCase());
  })
  .map((address) =&gt; {
    return (
      &lt;ListGroupItem key={address.id}&gt;
        &lt;NavLink to={`/address/${address.id}`} key={address.id}&gt;
          {address.addressName}
        &lt;/NavLink&gt;
      &lt;/ListGroupItem&gt;
    );
  });
</code></pre>
<p>In the previous step, we named a param by word: filter, and now we can read that value by using this: <strong>searchParams.get(&#39;filter&#39;);</strong></p>
<p>Changes after this step: <a href="https://github.com/Frodigo/react-router-tutorial/commit/50954adea6242fbc4d3ebaef05979050206134cd">https://github.com/Frodigo/react-router-tutorial/commit/50954adea6242fbc4d3ebaef05979050206134cd</a></p>
<hr>
<h3>Handle no matching route</h3>
<p>The last thing I want to show you is the no-match route. It&#39;s a case when the user goes to a route that does not exist, for example,/blablabla</p>
<p>To handle that, add this route component definition at the end of your route components definitions:</p>
<pre><code class="language-javascript">&lt;Route
  path=&quot;*&quot;
  element={
    &lt;main&gt;
      &lt;p style={{ padding: &quot;30px&quot;, textAlign: &quot;center&quot; }}&gt;
        There&#39;s nothing here!
      &lt;/p&gt;
    &lt;/main&gt;
  }
/&gt;
</code></pre>
<p>That code handles all routers not handled by other defined routes components. On the other hand, if no routes match, those elements will be rendered. Of course, you can use the react component as well.</p>
<p>Changes after this step: <a href="https://github.com/Frodigo/react-router-tutorial/commit/0f3e6588b1bc5cb6e6f6e298a527a8d856247e2b">https://github.com/Frodigo/react-router-tutorial/commit/0f3e6588b1bc5cb6e6f6e298a527a8d856247e2b</a></p>
<h3>An additional thing: protected Routes</h3>
<p>A protected route is used to ensure only logged-in users can use some places on your site. Typically we create e a secure route component for someone in the system to use /admin when they attempt to connect. However, some aspects of React Router must first be covered.</p>
<p>Basically, you can create a special React component that will check if a user can go to a protected route or not.</p>
<hr>
<h3>Working Demo</h3>
<p>Here you can see the demo of the application we developed with react-router:</p>
<p><a href="https://react-router-tutorial-omega.vercel.app/">https://react-router-tutorial-omega.vercel.app/</a></p>
<hr>
<h3>Source code</h3>
<p>Here you can find the source code for this tutorial: <a href="https://github.com/Frodigo/react-router-tutorial">https://github.com/Frodigo/react-router-tutorial</a></p>
<p>Here are the commits for each step:</p>
<ol>
<li><p><a href="https://github.com/Frodigo/react-router-tutorial/commit/dd4e2230153228220a0ed75136c74dc556aad657">https://github.com/Frodigo/react-router-tutorial/commit/dd4e2230153228220a0ed75136c74dc556aad657</a></p>
</li>
<li><p><a href="https://github.com/Frodigo/react-router-tutorial/commit/89ac1a9a16ec0fac0c1317635b72cf4f7cd72dcf">https://github.com/Frodigo/react-router-tutorial/commit/89ac1a9a16ec0fac0c1317635b72cf4f7cd72dcf</a></p>
</li>
<li><p><a href="https://github.com/Frodigo/react-router-tutorial/commit/82839415d55ad15c2411fec640a57a6c0bb8bcd7">https://github.com/Frodigo/react-router-tutorial/commit/82839415d55ad15c2411fec640a57a6c0bb8bcd7</a></p>
</li>
<li><p><a href="https://github.com/Frodigo/react-router-tutorial/commit/96843a7dd88cb1175790e527b86e5a25cc4e0fb0">https://github.com/Frodigo/react-router-tutorial/commit/96843a7dd88cb1175790e527b86e5a25cc4e0fb0</a></p>
</li>
<li><p><a href="https://github.com/Frodigo/react-router-tutorial/commit/cdd0fbca793766e8b85ae01ed4ead1d795de1a68">https://github.com/Frodigo/react-router-tutorial/commit/cdd0fbca793766e8b85ae01ed4ead1d795de1a68</a></p>
</li>
<li><p><a href="https://github.com/Frodigo/react-router-tutorial/commit/0492a826f8047a48161ccb87e535b9edc280cea6">https://github.com/Frodigo/react-router-tutorial/commit/0492a826f8047a48161ccb87e535b9edc280cea6</a></p>
</li>
<li><p><a href="https://github.com/Frodigo/react-router-tutorial/commit/3157af6cfe8db2f5354a0fbde9029b1073658844">https://github.com/Frodigo/react-router-tutorial/commit/3157af6cfe8db2f5354a0fbde9029b1073658844</a></p>
</li>
<li><p><a href="https://github.com/Frodigo/react-router-tutorial/commit/4fcee983b94da41991244463b6092fbfd80255ad">https://github.com/Frodigo/react-router-tutorial/commit/4fcee983b94da41991244463b6092fbfd80255ad</a></p>
</li>
<li><p><a href="https://github.com/Frodigo/react-router-tutorial/commit/cb81a97541fb8de9601f8774cfb267967ea6cd38">https://github.com/Frodigo/react-router-tutorial/commit/cb81a97541fb8de9601f8774cfb267967ea6cd38</a></p>
</li>
<li><p><a href="https://github.com/Frodigo/react-router-tutorial/commit/f2f833a41e994bd1dac366648d44739d98a38a82">https://github.com/Frodigo/react-router-tutorial/commit/f2f833a41e994bd1dac366648d44739d98a38a82</a></p>
</li>
<li><p><a href="https://github.com/Frodigo/react-router-tutorial/commit/50954adea6242fbc4d3ebaef05979050206134cd">https://github.com/Frodigo/react-router-tutorial/commit/50954adea6242fbc4d3ebaef05979050206134cd</a></p>
</li>
<li><p><a href="https://github.com/Frodigo/react-router-tutorial/commit/0f3e6588b1bc5cb6e6f6e298a527a8d856247e2b">https://github.com/Frodigo/react-router-tutorial/commit/0f3e6588b1bc5cb6e6f6e298a527a8d856247e2b</a></p>
</li>
</ol>
<hr>
<h2>Summary</h2>
<p>React Router lets you handle all the routes in a React application. You can use it for a web app or even for React native app.</p>
<p><strong>The router</strong> is one of the main React Router components, and for web apps, there is a BrowserRouter react component, a router implementation that uses HTML5 History API.</p>
<p>React Router provides other essential components are Routes, Route, Link, and NavLink.</p>
<p>Besides, there are a few hooks like useParams, useSearchParams, and useNavigate.</p>
<p>So react-router package provides just components and just hooks, and those all together allow you to create complex routing systems easily.</p>
<p>#WebDevelopment #FrontendDevelopment #JavaScript #HTML #React #ReactRouter #Bootstrap #GitHub #Tutorial #DeepDive #ProjectSetup #Intermediate #APISecurity #Performance</p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[What is react redux, and what development of redux apps looks in 2021]]></title>
            <description><![CDATA[<em>last updated at 02/11/2021</em>]]></description>
            <link>https://frodigo.com/Blog/Archive/Frontend/React/What+is+react+redux,+and+what+development+of+redux+apps+looks+in+2021</link>
            <guid isPermaLink="false">https://frodigo.com/Blog/Archive/Frontend/React/What+is+react+redux,+and+what+development+of+redux+apps+looks+in+2021</guid>
            <pubDate>Tue, 02 Nov 2021 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p><em>last updated at 02/11/2021</em></p>
<p>I want to invite you to the journey throughout react-redux. You will see:</p>
<ul>
<li><p>What choice of approach to state management in react apps is important?</p>
</li>
<li><p>What is redux, and why should you use it or not?</p>
</li>
<li><p>What are Flux architecture and event sourcing?</p>
</li>
<li><p>What is the architecture of the typical redux app?</p>
</li>
<li><p>How to manage side effects in the redux app?</p>
</li>
<li><p>What are alternatives for redux-thunk middleware?</p>
</li>
<li><p>What hooks react-redux offer?</p>
</li>
<li><p>What is the best approach for testing redux apps?</p>
</li>
</ul>
<hr>
<h2>What is state management?</h2>
<p>State management is designed to enable the exchange and use of data between different sectors. It creates a concrete data structure that represents the state of your app for you to read.</p>
<p>Most frameworks, including React, have a way for components to control the state. It&#39;s also helpful for applications with few components, but state management across components can quickly become a task.</p>
<p>That is why you need state management tools to make it easier to maintain the state. It is clear that state management becomes difficult as the app is complicated, so you require a state-management tool like Redux.</p>
<p>Redux is a predictable state container for JavaScript apps and offers a centralized way to manage the state. Using a framework like React, we can tell certain parts of code only to show specific values of the state.</p>
<p>State property updates will be automatically uploaded to the front end (if rendered there). In all, state references a condition of something at a given moment, such as whether a modal is opened or not, which data should be visible, which view is active, and so on.</p>
<hr>
<h2>Why use redux?</h2>
<p>Redux is a pattern and library for managing and updating application states using events called actions that accomplish this task by defining actions that are dispatched to reducers. All actions in an app can have a type and a payload.</p>
<p>Reducer accepts action and changes state depending on recipient Action Type and Payload. Reducers are pure functions and therefore are predictable.</p>
<p>A simple function returns the same output for the same data. In addition to communication with our application, we can subscribe to data updates. It is a simple yet elegant solution to facilitate state management for small and large applications.</p>
<hr>
<h2>When to use Redux</h2>
<p>The Redux API allows you to maintain your Apps state and keep it more predictable. The idea of adding code can seem like a bit of a chore and make complicated things look a little bit overwhelming, but that can depend on the decision-making from the architecture. If you&#39;re still unsure of who needs them, you do not. The usual scenario starts when your company&#39;s application expands to the level where maintaining the app state becomes a hassle. You are seeking to make managing the user&#39;s app straightforward. These benefits come with tradeoffs and constraints, but all of these tradeoffs come with limitations and tradeoffs, including the addition of boilerplate code.</p>
<hr>
<h2>Why do I need to use React-Redux?</h2>
<p>React Redux is the official Redux Interface Library for React. If you use Redux with any UI framework, you will generally use a library with its own internal &quot;binding&quot; library to put Redux together with a UI framework. Redux is a standalone library used with any UI layer or framework, including React Angular Vue Ember and Vanilla JS.</p>
<hr>
<h2>Redux architecture</h2>
<p>Let&#39;s look at the react redux architecture and what you can find in a typical redux app.</p>
<h3>Flux</h3>
<p>React redux implements the Flux architecture (proposed by Facebook). The base rule of that architecture is that data flows in a single direction, and also redux state can be changed only in one place.</p>
<p>The other concept of Flux is that writing data is separated from reading data. It&#39;s based on the C<strong>ommand Query Responsibility Segregation (CQRS)</strong> design pattern.</p>
<p>That separation is good because typically, in apps, reading data is more frequent than writing, so it&#39;s good to separate them and scale, develop and maintain separately. Of course, sometimes using CQRS does not make sense, but it depends on projects requirements (as always)</p>
<p>In the Flux architecture, there are three main areas:</p>
<ul>
<li><p><strong>view</strong> - that what you see and place where you can dispatch actions that can change data in a store</p>
</li>
<li><p><strong>dispatcher -</strong> receives actions and pass them to stores</p>
</li>
<li><p><strong>store</strong> - stores information</p>
</li>
</ul>
<p>So Redux implements flux architecture in its way, and keep in mind that other tools also implement Flux but in a slightly different way. Take a look at <a href="https://mobx.js.org/README.html">https://mobx.js.org/README.html</a>, for example.</p>
<hr>
<h3>Event sourcing &amp; time traveling</h3>
<p>Another idea (pattern, architecture) that react redux implements is event sourcing. That pattern is more known on the backend side. Event sourcing on the frontend in redux apps means that the state reading by the view is not the source of data.</p>
<p>The redux state and data are created by events (actions in react redux architecture – more information below). Typically you have a default store state, and you change that state by dispatching actions.</p>
<p>Thanks to that, you can build a state of what you want by triggering particular actions. Let&#39;s imagine a tester finding a bug in an app and reproducing that bug in your local environment. You can need to reproduce actions history.</p>
<p>Moreover, you can travel back and forth in time by performing and withdrawing specific actions and seeing the unique state for each action.</p>
<p>Redux has a global store and brings your component&#39;s states to one central area. The prominent place where we store the state is called a store.</p>
<p>That also provides an additional way for examining how a particular period an app&#39;s state changes. The store is immutable, which means it can&#39;t be directly modified. It can be cloned and replaced in place with updated properties. This cloning gives us a view of our app useful for debugging purposes, particularly in the Context of a specific app&#39;s state change.</p>
<h3>Redux Reducers</h3>
<p>A reducer is only a pure function that takes two arguments and returns the current state in your app.</p>
<p>The first argument is the current state, and the second is an action that will modify that state. The Reducer function returns a new state based on provided current state and action.</p>
<p>Take a look at the example reducer:</p>
<pre><code class="language-javascript">export function CarsReducer(state, action) {
  switch (action.type) {
    case &quot;ADD_CAR&quot;: {
      return {
        messages: [...state.cars, action.car],
      };
    }

    case &quot;REMOVE_CAR&quot;: {
      const indexToToRemove = state.cars.indexOf(action.car);

      if (indexToToRemove &gt;= 0) {
        return {
          cars: [...state.cars.splice(indexToToRemove, indexToToRemove)],
        };
      }

      return state;
    }

    default: {
      return state;
    }
  }
}
</code></pre>
<p>Thar <strong>CarsReducer</strong> can handle two actions: <em>ADD_CAR</em> and <em>REMOVE_CAR.</em> Let&#39;s take a look at what actions are in Redux.</p>
<h3>Redux Actions</h3>
<p>Actions in redux are JavaScript objects containing two features called type and payload. These actions will be used as arguments through the API method that calls Redux dispatch when an item needs dispatch. To &quot;call&quot; this action, we have to use a reducer in our store to update our state. We then use this function to create a new store state to preview how the app looks after actions are sent on.</p>
<p>Take a look at the example action creator:</p>
<pre><code class="language-javascript">const addCar = (car) =&gt; {
  return {
    type: &quot;ADD_CAR&quot;,
    car,
  };
};
</code></pre>
<p>Another convention is to send a car object as a field of the payload:</p>
<pre><code class="language-javascript">const addCar = (car) =&gt; {
  return {
    type: &quot;ADD_CAR&quot;,
    payload: {
      car,
    },
  };
};
</code></pre>
<p>Both versions are good. They are only conventions. I prefer the first one.</p>
<p>Those functions are called action creators because they return a plain JavaScript object (action object) that you can dispatch to the store.</p>
<hr>
<h2>Redux &amp; performance</h2>
<p>In terms of performance in React, re-renders are something that impacts performance the most. When you use the React way to handle state, for example, hooks or even class components with private state, re render is necessary when the state is changed.</p>
<p>When you are integrating redux with react, the state is keeping outside react. That means the react&#39; components read data from the redux store, and in some cases, re-renders can be unnecessary. This Is like magic, but redux can you prevent redundant re-renders OOTB.</p>
<p>Comparing redux performance with React Contexts, there is a vast difference. When you have a significant Context with big data objects, and there is a change in this Context, all Context&#39;s subscribers (basically children) will be re-rendered.</p>
<p>Of course, you can reduce redundant re-renders by using the useCallback or useMemo hooks, but you need to take care of it by yourself and write additional code.</p>
<p>When you use Redux and dispatch some action using the <strong>useDispatch</strong> hook, only components that read data changed data will re render.</p>
<p>That makes a difference. Of course, this does not mean that React contexts are wrong, and redux is excellent. You can manage the state efficiently both ways, and the performance typically depends on implementation (and quality).</p>
<p>That means the redux can help you with performance, and you should consider this when you design state management in your app.</p>
<h3>Collaborative UI</h3>
<p>Another thing in terms of performance is apps that work in real-time. For example, Google docs. There you can edit documents with other users in real-time. In a case like that, Redux&#39;s architecture based on events is better because it&#39;s easier to send only events between users than sending all document data and handling change.</p>
<p>So using redux or not depends on the project type.</p>
<p>You may also ask if Redux can have a destructive impact on the performance of a react app? Sometimes - yes, but it&#39;s typically because not of redux, but because of the wrong design of an application.</p>
<p>React redux state is outside React, so when you design and develop a redux app, you need to decide which data should be a part of the redux store and a private state of components. Good design of state management can help to avoid performance issues.</p>
<hr>
<h2>Side effects</h2>
<p>Some actions can have side effects. For example, when you add the product to a cart, you need to display a notification. On the other hand, you can also need to work with async data. I mean, send a request for data before redux action or after.</p>
<p>You can manage side effects in a react component, but this is not a good idea because user action, or generally speaking an operation, is divided into two parts</p>
<ol>
<li><p>pure - redux action - handled by reducer</p>
</li>
<li><p>impure - side effect - controlled by a react component</p>
</li>
</ol>
<p>A better solution is adding one common type of place in a react app when you can handle operations. That place is called: redux middleware.</p>
<hr>
<h2>Redux middleware</h2>
<p>Redux middleware is a building block that allows you to encapsulate sending redux actions and manage side effects, async data in one place.</p>
<p>There are three types of redux middleware:</p>
<ol>
<li><p>Thunks (<a href="https://github.com/reduxjs/redux-thunk">https://github.com/reduxjs/redux-thunk</a>)</p>
</li>
<li><p>Sagas (<a href="https://redux-saga.js.org/">https://redux-saga.js.org/</a>)</p>
</li>
<li><p>Observables (<a href="https://redux-observable.js.org/">https://redux-observable.js.org/</a>)</p>
</li>
</ol>
<p>If you are developing redux apps, you should choose one of the middleware, and then Redux and middleware are responsible for the state management, and components don&#39;t need to handle side effects. Hence, they are more readable and more superficial because they only display data.</p>
<p>Let&#39;s go dive into each redux middleware type.</p>
<h3>Redux Thunk</h3>
<p>Redux-thunks are the simplest to understand, so let&#39;s start from them! Thunks is a function that can dispatch any redux action directly to a redux store.</p>
<p>Moreover, you can dispatch many actions in one thunk and add additional logic of dispatching so that you can dispatch action conditionally or in a loop. Also, a thunk can call other thunks, so whatever you need, all of this is in one place.</p>
<p>The significant advantage of thunks is that components don&#39;t know anything about the thunk that it calls. All dependencies and logic are inside thunks, and components are only called thunk. All magic is inside.</p>
<p>Take a look at the example thunk:</p>
<pre><code class="language-javascript">// redux action
function setUser(userData) {
  return {
    type: &quot;SET_USER&quot;,
    userData,
  };
}

// redux action
function setError(errorMessage) {
  return {
    type: &quot;SET_ERROR&quot;,
    errorMessage,
  };
}

// The thunk
function fetchUserData(userId) {
  return function (dispatch) {
    return fetch(`https://my-api.com/user/${userId}`).then(
      (data) =&gt; dispatch(setUser(data)),
      (error) =&gt; dispatch(setError(`Something went wrong... ${error}`)),
    );
  };
}
</code></pre>
<p>You can see two redux actions in the code above: setUser, setError, and the thunk named fetchUserData. That thunk receives userId as a parameter and calls <a href="https://marcin-kwiatkowski.com/blog/graphql/2-ways-of-handling-graphql-errors-in-apollo-client">https://marcin-kwiatkowski.com/blog/graphql/2-ways-of-handling-graphql-errors-in-apollo-client</a> using fetch and then dispatch setUser action, or setError hook if the API returns error.</p>
<p>Take a look at another example with more logic:</p>
<pre><code class="language-javascript">function fetchUserB2Bdata() {
  return function (dispatch, getState) {
    const user = getState().user;

    if (!user) {
      return dispatch(setError(&quot;User is not authenticated.&quot;));
    }

    if (!user.isB2Buser) {
      return dispatch(setError(&quot;User is not a B2B user.&quot;));
    }

    return fetch(`https://my-api.com/user/b2b/${user.id}`).then(
      (data) =&gt; dispatch(setUserB2Bdata(data)),
      (error) =&gt; dispatch(setError(`Something went wrong... ${error}`)),
    );
  };
}
</code></pre>
<p>As you can see, there is a getState function that allows you to get the current state and do what you need depending on it.</p>
<p>Those examples are elementary, and I wanted to show you (what I am writing about for a while ) the advantages of using thunks: handle async data, encapsulate actions with side effects, and async data.</p>
<h3>Sagas (redux-saga)</h3>
<p>Let&#39;s take a look at the second type of redux middleware. On the homepage of redux-saga, you can read that it&#39;s &quot;An intuitive Redux side effect manager.&quot; Let&#39;s check it&#39;s the truth! :)</p>
<p>Here is the example piece of code that uses redux-saga:</p>
<pre><code class="language-javascript">import { call, put, takeEvery } from &quot;redux-saga/effects&quot;;
import Api from &quot;...&quot;;

// Worker saga will be fired on REQUEST_USER actions
function* fetchUserData(action) {
  try {
    const userData = yield call(Api.fetchUser, action.payload.userId);
    yield put({ type: &quot;SET_USER&quot;, userData });
  } catch (error) {
    yield put({ type: &quot;SET_ERROR&quot;, errorMessage: error });
  }
}

// Starts fetchUser on each dispatched REQUEST_USER action
function* mySaga() {
  yield takeEvery(&quot;REQUEST_USER&quot;, fetchUserData);
}
</code></pre>
<p>It is intuitive, isn&#39;t it?</p>
<p>I hope you will agree with me that at first glance, it looks more complicated than redux-thunk. Let&#39;s explain it a little bit.</p>
<p>The concept of redux-saga is similar to the saga design pattern. Each saga is an independent logic that is fired when some specific actions are dispatched.</p>
<p>In the example above, there is a <strong>fetchUserData</strong> saga that is fired on every <strong>REQEST_USER</strong> redux action.</p>
<p>The <strong>takeEvery</strong> function is a redux-saga effect. There is also the <strong>takeLatest</strong> effect which is fired on the latest specified redux action.</p>
<p>There are more effects, so you need time to understand redux-saga API. Moreover, redux-saga uses JavaScript generators that ate powerfully but also required more attention to understand them.</p>
<h3>Epics (redux-observable)</h3>
<p>The last type of redux middleware which you should consider when you design a redux app is redux-observable. It&#39;s based on <a href="https://github.com/ReactiveX/RxJS">https://github.com/ReactiveX/RxJS</a>.</p>
<p>In this case, redux-actions are implemented as RxJS streams, so you have a stream of actions and a stream of state. Using redux-observable, you can create <strong>Epics</strong> that describes connections between actions. You can think about epics like sagas from redux-saga.</p>
<p>For now, I don&#39;t want to go deeper with epics (and sagas as well), but you can expect separate articles about them in the future.</p>
<p>Summarizing – sagas and epics have a significant entry threshold, so you and your team should think twice before you decide to use them. It does not mean that they are wrong. Everything depends on a project&#39;s requirements and team credentials.</p>
<p>However, I will not lie if I say that redux-thunk is a good choice in 90% of cases.</p>
<hr>
<h2>Modern redux with hooks</h2>
<p>The React world can be divided into two eras: before Hooks were introduced and after Hooks were introduced.</p>
<p>Before, there was a connect function provided by the react redux, and you were able to map state and dispatch to props of components. Also, a connected component could give props from a parent component, so you needed to merge props from components and redux.</p>
<p>Writing with this method added even more redux boilerplate to your code to add child component that has access to the redux store to the component tree.</p>
<p>Luckily we now have hooks, and things look more straightforward.</p>
<h3>Hooks</h3>
<p>React&#39;s new &quot;hooks&quot; API gives functions the ability to use local component states, execute side effects, and so on. React also lets us write custom hooks enabling us to extract reusable hooks to add our behavior for React&#39;s built-in hooks. React-Redux has its standard hook APIs that allow React components to subscribe to Redux stores, read state, and dispatch actions.</p>
<h4>useSelector</h4>
<p>The useSelector hook allows you to read a state from the redux store. Each call to useSelector() creates an individual subscription to the Redux store.</p>
<p>Take a look at the example of a react component that uses of the useSelector:</p>
<pre><code class="language-javascript">import React from &quot;react&quot;;
import { useSelector } from &quot;react-redux&quot;;

export const UserDetails = () =&gt; {
  const user = useSelector((state) =&gt; state.user);
  return &lt;h1&gt;Hello, {user.name}&lt;/h1&gt;;
};
</code></pre>
<h4>useDispatch</h4>
<p>The useDispatch hook returns a reference to the dispatch function from the Redux store. Thanks to that, you can dispatch actions in a react component as needed.</p>
<pre><code class="language-javascript">import React from &quot;react&quot;;
import { useDispatch } from &quot;react-redux&quot;;

export const LogOut = ({ value }) =&gt; {
  const dispatch = useDispatch();

  return &lt;button onClick={() =&gt; dispatch({ type: &quot;LOG_OUT&quot; })}&gt;Log out&lt;/button&gt;;
};
</code></pre>
<p>As you can see, using Redux with hooks is pretty simple, and keep in mind that it&#39;s recommended approach (instead of using connect function).</p>
<hr>
<h2>Redux code boilerplate</h2>
<p>Now let&#39;s focus on something which is a considerable disadvantage of redux - code boilerplate. When you create a new app, you set up redux and are ready to go... you need to write a lot of code: reducers, action creators. It&#39;s instead not enjoyable, more frustrating, but there is a solution for that!</p>
<h3>Redux toolkit</h3>
<p>Redux toolkit is an Opinionated set of tools that can help create redux code and focus on the core logic your app needs. Thanks to that toolkit, you can write less code and do more work at the same time. Redux toolkit can help with store setup, creating reducers, immutable update logic, and more.</p>
<p>Take a look at the examples here: <a href="https://redux-toolkit.js.org/tutorials/quick-start">https://redux-toolkit.js.org/tutorials/quick-start</a>.</p>
<h2>Redux DevTools</h2>
<p>Redux DevTools is a browser extension that allows you to see the status of your Redux store at any given time. It is how we can time travel by our app - as different changes take place.</p>
<p>If the app starts to send more actions, the software will check the user&#39;s interaction on all of its features using Redux DevTools. This is useful for debugging as your app grows, so having a running log of how your redux store state is updated will give you valuable insight into how your app ends up looking how it does.</p>
<p>What I like the most is inspecting actions, the current state at the moment, and time traveling. It&#39;s highly recommended to use <a href="https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd?hl=en">https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd?hl=en</a> to debug redux apps.</p>
<hr>
<h2>Testing redux</h2>
<p>Redux is simply a library that ensures we follow a particular pattern for updating our global state. Should we unit test stores, reducers, actions, and so on?</p>
<p>In my opinion, we shouldn&#39;t. Redux is an implementation detail, and it should be hidden in tests even when I think about redux middleware. From the perspective of a test, you don&#39;t have to use thunks, sagas, or epics. What is vital is that the state should be changed.</p>
<p>So I recommend testing the redux more integrally than the unit.</p>
<p>For example, instead of testing actions and reducers, test component that reads data and triggers actions. Test it from the user&#39;s perspective and assert that results are that as they should be.</p>
<h2>React Redux Tutorial for Beginners: The Complete Guide</h2>
<p>The most simplified Redux tutorial I want when I&#39;m figuring it out. Includes the Redux Toolbox and Redux hooks! There are many redux tutorials, but many of them are outdated, so I plan to write a new one that is up to date with approaches and techniques that we have already known in 2021. Stay tuned!</p>
<hr>
<h2>Conclusion</h2>
<p>In the end, I want to say that Redux is a complex way to manage the state in react apps and still is an option in 2021 for the state management engine for React Apps. Hooks provided by the library are effortless to use and powerful. Thanks to the redux toolkit, there is also a way to avoid a large amount of redux boilerplate code.</p>
<p>In some cases, Redux is a good choice, and also even if you choose Redux, you need to select other things: which middleware should I use?</p>
<p>Redux-thunk, redux-saga, or redux-observable?</p>
<p>Those middlewares can help manage side effects and asynchronous logic in different ways. Also, the level of complexity of those is different (thunks are most straightforward).</p>
<h3>Do I need redux?</h3>
<p>Is another question about architecture? Do you need redux and event sourcing? Maybe alternatives as <a href="https://mobx.js.org/README.html">https://mobx.js.org/README.html</a>, <a href="https://react-query.tanstack.com/">https://react-query.tanstack.com/</a> or <a href="https://swr.vercel.app/">https://swr.vercel.app/</a> will be better for `you?</p>
<p>Moreover, Facebook is actively working on something new for state management in react apps. Check out <a href="https://recoiljs.org/">https://recoiljs.org/</a>.</p>
<h3>Do I migrate from redux?</h3>
<p>The final question is, when I have the redux app, should I migrate to something else?</p>
<p>I was fascinated with how the redux became popular a few years ago, and I&#39;m just as fascinated with how suddenly its popularity has declined.</p>
<p>When you think about migrating from redux, you need to have good reasons. Migrating because the community declines it is not a reason.</p>
<p>If, after a few years, it turned out that the choice of redux was not the best, it only emphasizes the importance of design and choosing the exemplary architecture for a specific project.</p>
<p>So, if you have reasons and money, please migrate from redux, but migrate if you want to migrate. Please give it up.</p>
<p>#WebDevelopment #Frontend #React #StateManagement #JavaScript #Redux #ReduxThunk #ReduxSaga #ReduxObservable #ConceptExplanation #DeepDive #BestPractices #Intermediate #Performance #Architecture</p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[What is JSX in React, and is it worth making friends with it]]></title>
            <description><![CDATA[<em>Last updated at 07/10/2021</em>]]></description>
            <link>https://frodigo.com/Blog/Archive/Frontend/React/What+is+JSX+in+React,+and+is+it+worth+making+friends+with+it</link>
            <guid isPermaLink="false">https://frodigo.com/Blog/Archive/Frontend/React/What+is+JSX+in+React,+and+is+it+worth+making+friends+with+it</guid>
            <pubDate>Thu, 07 Oct 2021 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p><em>Last updated at 07/10/2021</em></p>
<p>JSX is an XML/HTML-like syntax from React that extends <strong>ECMAScript</strong>. JSX allows you to write HTML code mixed with JavaScript. Take a look at the following example:</p>
<pre><code class="language-javascript">export const App = () =&gt; {
  return (
    &lt;main id=&quot;root&quot;&gt;
      &lt;h1&gt;Hello Folks!&lt;/h1&gt;
      &lt;p&gt;
        Random question: Is &lt;strong&gt;FC Barcelona&lt;/strong&gt; win any match in the
        Champions League this season?
      &lt;/p&gt;
    &lt;/main&gt;
  );
};

export default App;
</code></pre>
<p>Each JSX code is converted into the React.createElement function, which the web browser knows. Take a look at the converted code I showed you in a previous snippet:</p>
<pre><code class="language-javascript">export const App = () =&gt; {
  return React.createElement(
    &quot;main&quot;,
    { id: &quot;root&quot; },
    React.createElement(&quot;h1&quot;, null, &quot;Hello Folks!&quot;),
    React.createElement(
      &quot;p&quot;,
      null,
      &quot;Random question: Is &quot;,
      React.createElement(&quot;strong&quot;, null, &quot;FC Barcelona&quot;),
      &quot; win any match in the Champions League this season?&quot;,
    ),
  );
};

export default App;
</code></pre>
<p>The react createElement function creates and returns a new React element. Here is the function signature:</p>
<pre><code class="language-javascript">React.createElement(type, [props], [...children]);
</code></pre>
<p>The type argument can be an HTML element or React element (or even React fragment). The second argument is the props object which contains HTML tag attributes or props of a React element. The last argument is children, and so this is another jsx element transformed to React <strong>createElement</strong> function.</p>
<p>In the above code, you can see there are nested elements. All jsx tags are transported to react createElement functions, and regular javaScript can understand them.</p>
<p>So if you ask what is jsx in React, what is jsx meaning, I may answer you that is a special syntax that allows mixing plain JavaScript and HTML elements to speed up development. Then you can ask...</p>
<hr>
<h2>Is JSX necessary for React?</h2>
<p>It&#39;s not so if you don&#39;t like JSX syntax, or you want to write JavaScript code without configuring babel to transpioling JSX syntax to actual javascript code, you can write React apps without JSX.</p>
<p>Pro-tip for you then: if you want to avoid writing React.createElement too much, you can try something like this:</p>
<pre><code class="language-javascript">const e = React.createElement;

ReactDOM.render(
  e(&#39;div&#39;, null, &#39;Are you sure you don&#39;t want to use JSX?&#39;),
  document.getElementById(&#39;root&#39;)
);
</code></pre>
<h2>React JSX Explained with Examples</h2>
<p>React JSX is React&#39;s core concept. If you have a good understanding of it properly, then you can write React code efficiently. Let&#39;s see common use cases of JSX in React.</p>
<h2>How do I add JavaScript code in JSX?</h2>
<p>To add JSX-style code, you need to use curly brackets like this:</p>
<pre><code class="language-javascript">export const App = () =&gt; {
  const welcomeMessage = &quot;Hello folks!&quot;;

  return (
    &lt;main id=&quot;root&quot;&gt;
      &lt;h1&gt;{welcomeMessage}&lt;/h1&gt;
    &lt;/main&gt;
  );
};

export default App;
</code></pre>
<p>JSX doesn&#39;t allow to render objects directly as a JSX tag, so this react code is not valid, and the error occurs when using JSX Expression when rendering an object.</p>
<pre><code class="language-javascript">export const App = () =&gt; {
  const welcomeMessage = {
    text: &quot;Hello folks!&quot;,
  };

  return (
    &lt;main id=&quot;root&quot;&gt;
      &lt;h1&gt;{welcomeMessage}&lt;/h1&gt;
    &lt;/main&gt;
  );
};

export default App;
</code></pre>
<p>But you can write arrays in <strong>JSX Expressions</strong> because arrays are converted to strings while rendering. So this react&#39; component is valid:</p>
<pre><code class="language-javascript">export const App = () =&gt; {
  const welcomeMessage = [&quot;Hello&quot;, &quot; &quot;, &quot;folks&quot;, &quot;!&quot;];

  return (
    &lt;main id=&quot;root&quot;&gt;
      &lt;h1&gt;{welcomeMessage}&lt;/h1&gt;
    &lt;/main&gt;
  );
};

export default App;
</code></pre>
<hr>
<h2>Attributes</h2>
<p>JSX allows you to use native HTML attributes and your own custom <strong>attributes</strong> as well. For native HTML attributes, JSX uses camel case convention instead of the normal HTML naming convention. Besides, please note that, for example, a <strong>class</strong> word is a reserved keyword and <strong>for</strong> either. For those keywords, JSX uses a <strong>className</strong> for a class and a <strong>htmlFor</strong> for a reserved keyword (for).</p>
<h2>Custom HTML attributes in JSX</h2>
<p>For custom HTML attributes, you must use a data prefix. It looks pretty the same as regular HTML.</p>
<pre><code class="language-javascript">export const App = () =&gt; {
  const welcomeMessage = [&quot;Hello&quot;, &quot; &quot;, &quot;folks&quot;, &quot;!&quot;];
  const isCustom = true;

  return (
    &lt;main id=&quot;root&quot;&gt;
      &lt;h1 data-isCustom={isCustom}&gt;{welcomeMessage}&lt;/h1&gt;
    &lt;/main&gt;
  );
};

export default App;
</code></pre>
<p>As you can see in that react component on <code>&lt;h1&gt;</code> <strong>HTML</strong> tag, I used the data-isCustom attribute and curly braces to pass value to the attribute. Moreover, you can pass values to attributes of HTML tags using string literals like this:</p>
<pre><code class="language-javascript">export const App = () =&gt; {
  const welcomeMessage = [&quot;Hello&quot;, &quot; &quot;, &quot;folks&quot;, &quot;!&quot;];

  return (
    &lt;main id=&quot;root&quot;&gt;
      &lt;h1 data-mode=&quot;default&quot;&gt;{welcomeMessage}&lt;/h1&gt;
    &lt;/main&gt;
  );
};

export default App;
</code></pre>
<hr>
<h2>JavaScript Expressions</h2>
<p>JavaScript expression may be used inside JSX. What is not surprising, I suppose, when you want to use a JavaScript expression, you need to wrap it by curly braces {}</p>
<h3>If statements</h3>
<p>You cannot use <strong>if-else</strong> statements in JSX. Instead, you can use conditional or JavaScript ternary expressions. Take a look at the simple React component with JSX if statement:</p>
<pre><code class="language-javascript">export const App = () =&gt; {
  const welcomeMessage = [&quot;Hello&quot;, &quot; &quot;, &quot;folks&quot;, &quot;!&quot;];
  const shouldDisplay = true;

  return (
    &lt;main id=&quot;root&quot;&gt;
      {shouldDisplay &amp;&amp; &lt;h1 data-mode=&quot;default&quot;&gt;{welcomeMessage}&lt;/h1&gt;}
    &lt;/main&gt;
  );
};

export default App;
</code></pre>
<p>For if/else you can use something like this:</p>
<pre><code class="language-javascript">{
  shouldDisplay ? &lt;h1 data-mode=&quot;default&quot;&gt;{welcomeMessage}&lt;/h1&gt; : &quot;no way!&quot;;
}
</code></pre>
<p>I prefer to explode JavaScript expressions to <strong>const</strong> (and sometimes memorize them using React useMemo hook because of performance reasons). Take a look at the JSX code below:</p>
<pre><code class="language-javascript">export const App = (props) =&gt; {
  const { hasError } = props;
  const welcomeMessage = &quot;Hello folks!&quot;;
  const errorMessage = &quot;Something went wrong.&quot;;

  const shouldDisplayError = hasError ? (
    &lt;p&gt;{errorMessage}&lt;/p&gt;
  ) : (
    &lt;h1&gt;{welcomeMessage}&lt;/h1&gt;
  );

  return &lt;main id=&quot;root&quot;&gt;{shouldDisplayError}&lt;/main&gt;;
};

export default App;
</code></pre>
<h3>Looping</h3>
<p>When you want to render a JavaScript <strong>array</strong> using JSX, you can use something like this:</p>
<pre><code class="language-javascript">export const App = () =&gt; {
  const array = [&quot;a&quot;, &quot;b&quot;, &quot;c&quot;];

  return (
    &lt;main id=&quot;root&quot;&gt;
      {array.map((el) =&gt; (
        &lt;p key={el}&gt;This is {el}&lt;/p&gt;
      ))}
    &lt;/main&gt;
  );
};
export default App;
</code></pre>
<h3>Spread operator</h3>
<p>The spread operator is not supported as a child jsx expression, so you are not able to put something like this to a react component:</p>
<pre><code class="language-javascript"> {...[&#39;a&#39;,&#39;b&#39;,&#39;c&#39;]}
</code></pre>
<h2>Comments in JSX code</h2>
<p>If you want to insert a comment to the react component, you need to wrap its using curly braces.</p>
<pre><code class="language-javascript">{
  /** this is comment */
}
</code></pre>
<hr>
<h2>JSX Styling</h2>
<p>React encourages to use of inline styles. You can use JSX expressions to pass value to the style attribute to set inline styles. React will also automatically append px following the number of the specified property.</p>
<p>Note: inline styling is not the only way to style react elements. There are a few other approaches, but this is the topic for another article.</p>
<hr>
<h2>JSX: Conclusion</h2>
<p>When you work on the React application, you can use JSX to put markup into javascript code. That means that you have HTML tags and JavaScript expressions in the same file.</p>
<p>Each JSX tag is transformed to <strong>React createElement</strong> javascript function. Moreover, If you don&#39;t want to use JSX for any reason, you can write react code without JSX.</p>
<p>In my opinion, JSX has many advantages, and definitely, it&#39;s a friend of any React developer. I love it the most in JSX because it looks like JavaScript with more power (It&#39;s fair to say that JSX is built on top of JavaScript). I like that approach more than something you can find in <strong>Vue.js</strong> or <strong>Angular</strong>, where you need to learn specific framework functions and syntax.</p>
<p>#WebDevelopment #FrontendDevelopment #JavaScript #React #JSX #ConceptExplanation #Tutorial #BestPractices #Intermediate</p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Getting started with Magento PWA Studio targetables]]></title>
            <description><![CDATA[<em>Published at: 04/10/2021</em>]]></description>
            <link>https://frodigo.com/Blog/Archive/Frontend/Magento/Getting+started+with+Magento+PWA+Studio+targetables</link>
            <guid isPermaLink="false">https://frodigo.com/Blog/Archive/Frontend/Magento/Getting+started+with+Magento+PWA+Studio+targetables</guid>
            <pubDate>Mon, 04 Oct 2021 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p><em>Published at: 04/10/2021</em></p>
<p>As a Magento Frontend developer (If you are not deal with Magento, you can skip this paragraph), you have a few mechanisms to customize the look and feel of your shops, like Layout modifications, customizing styles, or creating mixins). These methods (if you can use their power) allows you to do whatever you want with your shop.</p>
<hr>
<h2>Welcome to Magento PWA Studio</h2>
<p>Magento PWA Studio was specially designed by the Adobe core team to help developers construct progressive web apps on Magento 2. This tool consists of ready-to-use, out-of-the-box solutions which are handy for headless storefronts development. The tool does have some benefits and weaknesses, including the value of the toolkits. In this article, I will show you one of the most powerful Magento PWA studio features: <strong>the extensibility framework.</strong></p>
<hr>
<h2>PWA Studio extensibility framework</h2>
<p>PWA Studio has a built-in extensibility framework for developers to extends the new storefront based on the Venia concept easily. Before introducing this solution, developers were forced to overwrite files using the webpack plugin. Overwriting core files causes unexpected errors and complicates the project. Each overwrites complicates everything and is harder. Thanks to PWA studio extensibility Framework you can reduce overwriters as much as possible.</p>
<h3>Targetables</h3>
<p>Regarding the <a href="https://marcin-kwiatkowski.com/blog/pwastudio/what-is-the-difference-between-pwa-studio-and-the-current-magento-frontend">https://marcin-kwiatkowski.com/blog/pwastudio/what-is-the-difference-between-pwa-studio-and-the-current-magento-frontend</a> PWA, you also have a powerful mechanism for adding customizations called: targetables that allow you to modify the JSX output of your PWA (React) App during build-time. Targetables are part of the <a href="https://magento.github.io/pwa-studio/pwa-buildpack">https://magento.github.io/pwa-studio/pwa-buildpack</a> module.</p>
<h3>Interceptor pattern</h3>
<p>PWA Studio extensibility framework uses the Interceptor pattern to allow changes in PWA studio storefronts during code build time. That pattern allows you to modify code in a way that Magento PWA studio does know anything about that change. It means that you can enable/disable your piece of code and this is transparent for PWA Studio. Any changes in code are not necessary.</p>
<p>Take a look at how it works when you create a new project based on PWA Studio and want to add some changes there:</p>
<ol>
<li>You scaffold a new project using pwa-buildpack CLI command-line instructions</li>
<li>You add a new module in the project directory, let&#39;s name it a @theme</li>
<li>You create an intercept file and put their instructions on which storefront parts you want to customize</li>
<li>You hook to specific targets that PWA Studio provides (I mean public API) to extend storefront by extensions/other modules</li>
<li>You use the public API provided by the pwa-buildpack module. For example, you use methods like <strong>insertAfterJSX</strong>, <strong>removeJSX</strong>, <strong>setJSXprops</strong>, and so on to achieve your goal</li>
<li>You can put all your extensions into one file, or have a few ones. Everything depends on you.</li>
<li>You run npm run build (or watch) and pwa-buildpack checks are there any interceptions exist and run those instructions.</li>
<li>If pwa-buildpack finds any error during build time, compilation fails, and errors are printed into the console.</li>
<li>Extensions are applied to the source code</li>
<li>Static code chunks contain modifications</li>
</ol>
<h3>Note about targetables in PWA studio extensions</h3>
<p>When you create your own extension you are able to add targets that other extensions can intercept. Because of security reasons PWA Studio restricts the scope of Targetable modifications in extensions. So you can add modifications only within the source code of extensions. That means in the case when you create and publish an npm package and someone wants to use your module, interceptors from the module won&#39;t work automatically. You can prepare a public function, and consumers will be able to use it in their local-intercept.js file and then your extension will affect a storefront.</p>
<hr>
<h2>It&#39;s time to get your hands dirty</h2>
<p>Enough of the theory. Let&#39;s see how it works in practice and create something using pwa-buildpack targetables!</p>
<p>Have you ever needed to add extra features to the Product Detail Page or wanted to customize it? I suppose the answer might be “yes” because the product page is probably the most frequently modified area in the online store.</p>
<p>In the following example, I would like to show you how to extend the product detail page into four steps:</p>
<ul>
<li><p>Get data from the API (define a <a href="/the-full-stack-guide-to-the-graphql-query">/the-full-stack-guide-to-the-graphql-query</a> query)</p>
</li>
<li><p>Add Unit tests</p>
</li>
<li><p>Create a component</p>
</li>
<li><p>Inject the component to the Product Detail page</p>
</li>
</ul>
<p>That process is repeatable for each customization you want to add to your store – however – in this article, we will work on a concrete case: how to add a short description to the product page.</p>
<hr>
<h2>Prerequisites</h2>
<ol>
<li><p><strong>Scaffolded PWA Studio app</strong> - if you don’t know how to set up PWA Studio on your local – check out my video tutorial:<a href="https://youtu.be/lsiul60vBfs">https://youtu.be/lsiul60vBfs</a></p>
</li>
<li><p><strong>Magento 2.4.2 installed locally</strong> - theoretically, you can use the public Magento instance with Venia sample data installed, but you cannot change anything in the admin panel. For example, you cannot change a product’s short description, so it will be hard to test the code.</p>
</li>
</ol>
<p>Let’s go ride code!</p>
<hr>
<h2>Define a GraphQL query</h2>
<p>The data comes from the Magento backend, and the only unknown thing is which GraphQL query we should use.</p>
<p>There is a products query:</p>
<pre><code class="language-javascript">products(
   search: String
   filter: ProductAttributeFilterInput
   pageSize: Int = 20
   currentPage: Int = 1
   sort: ProductAttributeSortInput
): Products
</code></pre>
<p>The products query searches for products that match the criteria specified in the search and filter attributes.</p>
<p>We definitely want to get one specific product and to achieve that I’m wanna use a product SKU as a filter. When you take a look at <strong>ProductAttributeFilterInput</strong> (of filter argument) you will see that there is an SKU field that we can use:</p>
<pre><code class="language-javascript">type ProductAttributeFilterInput {
    category_id: FilterEqualTypeInput
    category_uid: FilterEqualTypeInput
    description: FilterMatchTypeInput
    name: FilterMatchTypeInput
    price: FilterRangeTypeInput
    short_description: FilterMatchTypeInput
    sku: FilterEqualTypeInput
    url_key: FilterEqualTypeInput
}
</code></pre>
<hr>
<h3>Note for GraphQL beginners</h3>
<p>When you run your local PWA Studio instance, you will see an URL to the GraphQl playground. There you can find docs with all necessary information about GraphQL schema and possible information (like the information I Listed above)</p>
<p>Let’s define the query in <strong>src / @theme / components / ShortDescription / ShortDescription.gql.js.</strong></p>
<pre><code class="language-javascript">import gql from &#39;graphql-tag&#39;;

const GET_SHORT_DESCRIPTION_QUERY = gql`
    query shortDescriptionOfProduct($productSku: String!) {
        products(filter: { sku: { eq: $productSku } }) {
            items {
                uid
                short_description {
                    html
                }
            }
        }
    }


export default {
    queries: {
        getShortDescriptionQuery: GET_SHORT_DESCRIPTION_QUERY
    },
    mutations: {}
};
</code></pre>
<p>Of course, you can first use <strong>GraphQL Playground</strong> and write a query there, and when your query works as you want, you can just copy it to a source file in your project.</p>
<p>The query defined above takes one argument: product SKU, and it’s exported from the file as <strong>queries.getShortDescriptionQuery.</strong></p>
<h3>Define unit tests</h3>
<p>In our case, we just need to check two things:</p>
<ol>
<li><p>It is a short description visible when a product has it set up</p>
</li>
<li><p>It is a short description invisible when a product does not have a short description filled. (in this case, we assume that component will return null)</p>
</li>
</ol>
<p>Let’s create a file <strong>src / @theme / components / ShortDescription / __tests__ / ShortDescription.spec.js</strong> and add tests to it. Before we start, please add necessary imports:</p>
<pre><code class="language-javascript">import React from &quot;react&quot;;
import { render, getByText } from &quot;@testing-library/react&quot;;
import { useQuery } from &quot;@apollo/client&quot;;

import ShortDescription from &quot;../ShortDescription&quot;;

jest.mock(&quot;@apollo/client&quot;);
</code></pre>
<h3>Note about dependencies</h3>
<p>You can see I use <a href="https://testing-library.com/docs/react-testing-library/intro/">https://testing-library.com/docs/react-testing-library/intro/</a>, so you need to add this as a dependency with other necessary dependencies. Also, you have to add config for jest to run tests. All of those things you can find in <a href="https://github.com/Frodigo/tutorial-pwa-studio-short-description">https://github.com/Frodigo/tutorial-pwa-studio-short-description</a>. Describing that process is out of the scope of this tutorial, but please reach out to me on Magento Community Slack if you have any questions.</p>
<h3>Memo about mocking</h3>
<p>I mocked <strong>@apollo/client</strong> because I need to simulate communication with GraphQL API to test two scenarios I described earlier. Moreover, on the unit test level, we don’t want to test API.</p>
<p>I assumed that I would use the useQuery hook to fetch data from an API in my component.</p>
<h3>Test one: it renders component when the short description is filled in a product</h3>
<pre><code class="language-javascript">test(&quot;It renders component when the short description is filled in a product&quot;, () =&gt; {
  useQuery.mockReturnValue({
    data: {
      products: {
        items: [
          {
            uid: &quot;1&quot;,
            short_description: {
              html: &quot;&lt;p&gt;Lorem ipsum&lt;/p&gt;&quot;,
            },
          },
        ],
      },
    },
  });

  const { container } = render(&lt;ShortDescription productSku=&quot;abc&quot; /&gt;);

  expect(getByText(container, &quot;Lorem ipsum&quot;)).toBeDefined();
  expect(container).toMatchSnapshot();
});
</code></pre>
<p>I used <strong>useQuery.mockReturnValue</strong> function to define mocked returned value from API. Then I created an instance of ShortDescription component and passed “abc” value as the product SKU property (value is not essential here because returned data is mocked, so we need only to make sure that SKU is passed.</p>
<p>The next step is checking if Lorem ipsum text is rendered (defined). Take a look at the mock data - we returned the “lorem ipsum” paragraph there, so it should be rendered.</p>
<p>To check this, I used the <strong>getByText</strong> method of testing-library, which takes two arguments: container (tree, where we want to search, is passed value exists) and value (a value that we want to find in a specified container).</p>
<p>The function returns true if a value is defined in a container and false if not.</p>
<p>In the following line, we are just checking if rendered container matches the snapshot.</p>
<h3>Test two: it does not render when a product does not have the short description</h3>
<pre><code class="language-javascript">test(&quot;It does not render when a product does not have the short description&quot;, () =&gt; {
  useQuery.mockReturnValue({
    data: {
      products: {
        items: [
          {
            uid: &quot;1&quot;,
            short_description: {
              html: &quot;&quot;,
            },
          },
        ],
      },
    },
  });

  const { container } = render(&lt;ShortDescription productSku=&quot;abc&quot; /&gt;);

  expect(container.firstChild).toBeNull();
});
</code></pre>
<p>In the second case, there are two differences—the first one in another mocked data. The HTML field is empty.</p>
<p>The second one is to check that the rendered container is empty. I just check if container.firstChild is null.</p>
<hr>
<h2>Create the component</h2>
<p>We have already declared unity tests, and they, of course, fail because the component that we test is not defined yet. Let’s create it by adding a file <strong>src / @theme / components / ShortDescription / ShortDescription.js</strong> with following content:</p>
<pre><code class="language-javascript">import React from &quot;react&quot;;

import classes from &quot;./ShortDescription.css&quot;;
import productOperations from &quot;./ShortDescription.gql&quot;;

import { shape, string } from &quot;prop-types&quot;;

const ShortDescription = (props) =&gt; {
  const { productSku } = props;
  const { queries } = productOperations;
  const { getShortDescriptionQuery } = queries;

  return &lt;div className={classes.section}&gt;Short description will be here&lt;/div&gt;;
};

export default ShortDescription;

ShortDescription.propTypes = {
  classes: shape({
    root: string,
    section: string,
  }),
  productSku: string.isRequired,
};
</code></pre>
<p>From the beginning - we imported React because we want to create a React Component. Also, we imported a string checker from prop-types, and we use it to validate if productSku is passed:</p>
<pre><code class="language-javascript">ShortDescription.propTypes = {
  productSku: string.isRequired,
};
</code></pre>
<p>The productOperations object is imported from the already declared <strong>ShortDescription.gql.js</strong> file.</p>
<p>The classes object is the CSS module with classes. We imported it from src / @theme / components /ShortDescription / ShortDescription.css. Let’s create that file.</p>
<pre><code class="language-css">.root {
  margin: 15px 0;
}

.section {
  border-color: rgb(var(--venia-border));
  border-style: solid;
  border-width: 1px 0 1px;
  margin: 0 1.5rem;
  padding: 1.5rem 0;
}
</code></pre>
<p>Let’s back to the component file. You can see that we return static text:</p>
<pre><code class="language-javascript">return &lt;div className={classes.section}&gt;Short description will be here&lt;/div&gt;;
</code></pre>
<p>Let’s make it dynamic!</p>
<hr>
<h2>Querying for data</h2>
<p>The <strong>getShortDescriptionQuery</strong> is already imported, so now just import the useQuery hook from @apollo/client and use it to query data.</p>
<pre><code class="language-javascript">import { useQuery } from &#39;@apollo/client&#39;;
(..)
const ShortDescription = props =&gt; {
(...)
 const { data } = useQuery(getShortDescriptionQuery, {
          fetchPolicy: &#39;cache-and-network&#39;,
          variables: {
              productSku
          }
     });
(...)

};
</code></pre>
<p>Note: Because I want to keep this example simple, I do not handle errors here.</p>
<p>Now we can use data returned by the useQuery hook. For example: data.products.items[0].short_description.html, but this is not perfect.</p>
<h3>Save/memoize a short description</h3>
<p>I have an idea: create a function that checks is a short description is defined and returns it. Otherwise return null.</p>
<p>I want to use the useMemo hook, which will memoize that value for us between re-renders of the component.</p>
<pre><code class="language-javascript">const shortDescription = useMemo(() =&gt; {
  if (!data) return null;

  const { products } = data;

  if (
    products &amp;&amp;
    products.items &amp;&amp;
    products.items.length &amp;&amp;
    products.items[0].short_description &amp;&amp;
    products.items[0].short_description.html &amp;&amp;
    products.items[0].short_description.html.length
  ) {
    return products.items[0].short_description.html;
  }

  return null;
}, [data]);
</code></pre>
<p>The shortDescription field will be changed only when data (passed in dependency array - the second argument of the useMemo) is changed.</p>
<h3>Display short description</h3>
<p>Let’s display a short description. Do you remember our test cases? If a description exists, we have to show it. Otherwise, we should return null.</p>
<p>Perfect, because our shortDescripotion constant has, in fact, two states: null or short description value.</p>
<p>We can use it to check what should be rendered. We will create the shouldRenderShortDescription constant that will yield a div contains a short description or return null.</p>
<p>BTW: the short description in the Magento backend is a WYSIWYG field, so the value returned from API is HTML. Let’s use the RichText component to render it:</p>
<pre><code class="language-javascript">import RichText from &#39;@magento/venia-ui/lib/components/RichText&#39;;
(..)
const ShortDescription = props =&gt; {
(...)
const shouldRenderShortDescription = shortDescription ? &lt;div className={classes.root}&gt;
        &lt;div className={classes.section}&gt;
            &lt;RichText content={shortDescription} /&gt;
        &lt;/div&gt;
    &lt;/div&gt; : null;

    return shouldRenderShortDescription;

}
</code></pre>
<h3>Final component</h3>
<p>Here is the finished source code of the ShortDesxcription component:</p>
<pre><code class="language-javascript">import React, { useMemo } from &quot;react&quot;;
import { useQuery } from &quot;@apollo/client&quot;;
import RichText from &quot;@magento/venia-ui/lib/components/RichText&quot;;

import classes from &quot;./ShortDescription.css&quot;;
import productOperations from &quot;./ShortDescription.gql&quot;;

import { shape, string } from &quot;prop-types&quot;;

const ShortDescription = (props) =&gt; {
  const { productSku } = props;
  const { queries } = productOperations;
  const { getShortDescriptionQuery } = queries;

  const { data } = useQuery(getShortDescriptionQuery, {
    fetchPolicy: &quot;cache-and-network&quot;,
    variables: {
      productSku,
    },
  });

  const shortDescription = useMemo(() =&gt; {
    if (!data) return null;

    const { products } = data;

    if (
      products &amp;&amp;
      products.items &amp;&amp;
      products.items.length &amp;&amp;
      products.items[0].short_description &amp;&amp;
      products.items[0].short_description.html &amp;&amp;
      products.items[0].short_description.html.length
    ) {
      return products.items[0].short_description.html;
    }

    return null;
  }, [data]);

  const shouldRenderShortDescription = shortDescription ? (
    &lt;div className={classes.root}&gt;
      &lt;div className={classes.section}&gt;
        &lt;RichText content={shortDescription} /&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  ) : null;

  return shouldRenderShortDescription;
};

export default ShortDescription;

ShortDescription.propTypes = {
  classes: shape({
    root: string,
    section: string,
  }),
  productSku: string.isRequired,
};
</code></pre>
<p>The last thing related to the component is to create an index.js file that will export it.</p>
<p>Create a file <strong>src / @theme / components / ShortDescription / index.js</strong></p>
<pre><code class="language-javascript">export { default } from &quot;./ShortDescription&quot;;
</code></pre>
<hr>
<h2>Inject the component to the PDP</h2>
<p>We have good progress so far – the ShortDescription component is ready to use, unit tests have passed. Let’s inject the component to a product detail page using targetables.</p>
<h3>Add @theme dependency</h3>
<p>Because of some unknown reasons, it’s not possible to use relative paths in the <strong>local-intercept.js</strong> file. The workaround for that is to create a virtual dependencyMagento using yarn link funcionality.</p>
<p>Add a new field to the “dependencies” array in PWA Studio root package.json:</p>
<pre><code class="language-javascript">&quot;@marcinkwiatkowski/theme&quot;: &quot;link:src/@theme&quot;
</code></pre>
<p>Run yarn install command. When the install process is done, you should see the <strong>@marcinkwiatkowski/theme</strong> package in node_modules. When you make changes in the <strong>src/@theme</strong> directory, they will automatically apply in <strong>node_modules / @marcinkwiatkowski / theme</strong> directory.</p>
<h3>Adding interceptor</h3>
<p>Please replace local-intercept.js content with this one:</p>
<pre><code class="language-javascript">const { Targetables } = require(&quot;@magento/pwa-buildpack&quot;);

module.exports = (targets) =&gt; {
  const targetables = Targetables.using(targets);
};
</code></pre>
<p>Now, we are ready to use targetables.</p>
<h3>Target the productFullDetail component</h3>
<p>We want to add a short description between a title and quantity fields. After inspection, I figured out that the productFullDetail component is responsible for this area, so let’s handle it in the interceptor:</p>
<pre><code class="language-javascript">(...)
const targetables = Targetables.using(targets);

const ProductDetailComponent = targetables.reactComponent(
    &#39;@magento/venia-ui/lib/components/ProductFullDetail/productFullDetail&#39;
);
</code></pre>
<h3>Import the ShortDescription component</h3>
<p>Now we can add import to the productFullDetail component using targetables public API.</p>
<pre><code class="language-javascript">const ShortDescription = ProductDetailComponent.addImport(
  &quot;ShortDescription from &#39;@marcinkwiatkowski/theme/components/ShortDescription&#39;&quot;,
);
</code></pre>
<h3>Insert JSX</h3>
<p>The last thing is to insert JSX in a specific place. Let’s add it after the section with class &quot;title&quot;.</p>
<pre><code class="language-javascript">(...)
ProductDetailComponent.insertAfterJSX(&#39;&lt;section className={classes.title} /&gt;&#39;, `&lt;${ShortDescription} productSku={productDetails.sku} /&gt;`)
</code></pre>
<p>The exciting thing here is passing props. I passed productDetails.sku. To understand it, let’s imagine when that line is executed, the scope of data is the same as in intercepted component.</p>
<p>That means you can use all values which exist in the intercepted component. In this case, we are intercepting the productFullDetail component, and the productDetails object is available there.</p>
<hr>
<h2>Summary</h2>
<p>I showed you the process of extending PWA Studio using targetables. We just used only one targetables method called insertAfterJSX, but there are a few more methods available.</p>
<p>Check out this article if you want to read more about targetables and other PWA Studio’s extensibility features.</p>
<hr>
<h2>Source code</h2>
<p>I pushed the source code of this tutorial to my GitHub. You can find it here: <a href="https://github.com/Frodigo/tutorial-pwa-studio-short-description">https://github.com/Frodigo/tutorial-pwa-studio-short-description</a></p>
<p>There you can find all source files and scaffolded Magento PWA studio project. Just clone the code, install dependencies by running the yarn install command in the project directory and run the storefront-project by yarn run watch command.</p>
<p>#WebDevelopment #FrontendDevelopment #JavaScript #GraphQL #React #MagentoPWA #Apollo #Tutorial #DeepDive #ConceptExplanation #Intermediate #Testing #ComponentInjection #ExtensibilityFramework</p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to add support for Category Landing Pages to PWA Studio]]></title>
            <description><![CDATA[<em>Published at 15/04/2021</em>]]></description>
            <link>https://frodigo.com/Blog/Archive/Frontend/Magento/How+to+add+support+for+Category+Landing+Pages+to+PWA+Studio</link>
            <guid isPermaLink="false">https://frodigo.com/Blog/Archive/Frontend/Magento/How+to+add+support+for+Category+Landing+Pages+to+PWA+Studio</guid>
            <pubDate>Thu, 15 Apr 2021 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p><em>Published at 15/04/2021</em></p>
<p>A Category Landing Page is a special type of Category Page. In Magento, a Merchant can set unique content for a specific category. Take a look at this sample Magento Luma Category Landing Page.</p>
<h3>How does it work</h3>
<p>In the Magento category page configuration, there is a config field called Display Mode and there are three available options:</p>
<ul>
<li>Products only</li>
<li>Static block only</li>
<li>Static block and products</li>
</ul>
<p>If you select the ‘Static block only’ option and set up a static block for the category, you will see your static block on the frontend for the Category, and this is precisely what the Category Landing Page means.</p>
<hr>
<h2>Does PWA Studio support Category Landing Pages?</h2>
<p>No.</p>
<p>No.</p>
<p><strong>No.</strong></p>
<hr>
<h2>Is it possible to add support for CLP in PWA Studio?</h2>
<p>If you read my blog….</p>
<p><strong>Definitely YES.</strong></p>
<p><strong>Let’s do this!</strong></p>
<hr>
<h2>Install PWA Studio</h2>
<p>The very first thing that we need to do is create a new instance of PWA Studio</p>
<pre><code class="language-bash">yarn create @magento/pwa
cd &lt;directory where PWA Studio has been installed
yarn buildpack create-custom-origin ./
</code></pre>
<p>Also, we need to have our own Magento 2 instance. This time I am using Magento 2.4.2 with sample data installed. If you don’t have your own local Magento 2 installation, I recommend using <a href="https://github.com/markshust/docker-magento">https://github.com/markshust/docker-magento</a></p>
<p>People often ask me:</p>
<blockquote>
<p><em>What is the best way to install Magento in the local environment?</em></p>
</blockquote>
<p>Then I tell them:</p>
<p><strong>Use Mark’s Docker – it’s the best</strong></p>
<hr>
<h2>The Idea</h2>
<p>To achieve our goal, we are going to use the PWA Studio Extensibility framework. We will create an improvedCategoryContent component which will be a wrapper for CategoryContent PWA Studio’s component. We will add additional logic there and display Category Landing page content instead of an empty page.</p>
<hr>
<h2>Implementation</h2>
<h3>Create theme</h3>
<p>Before we start coding, let’s create a directory to keep all modifications related to the Category Landing Page.</p>
<p>First, create <strong>@theme/category</strong> folder in pwa_studio_root/src directory.</p>
<p>Second, let’s link this folder as a package in the package.json</p>
<pre><code class="language-json">dependencies&quot;: {
    &quot;@magento/pwa-buildpack&quot;: &quot;~9.0.0&quot;,
    &quot;@theme&quot;: &quot;link:src/@theme&quot;
},
</code></pre>
<h3>Add improvedCategoryContent component</h3>
<p>Create file <strong>src / @theme / category / components / ImprovedCategoryContent / ImprovedCategoryContent.js</strong> with the following content:</p>
<pre><code class="language-javascript">import React from &quot;react&quot;;
import PropTypes from &quot;prop-types&quot;;

import CategoryContent from &quot;@magento/venia-ui/lib/RootComponents/Category/categoryContent&quot;;
import LoadingIndicator from &quot;@magento/venia-ui/lib/components/LoadingIndicator/indicator&quot;;
import { useImprovedCategoryContent } from &quot;../../talons/useImprovedCategoryContent&quot;;
import CategoryLandingPage from &quot;../CategoryLandingPage&quot;;

/**
 * The ImprovedCategoryContent wraps CategoryContent @components and allows to display Category Landing Pages
 * @param {object} props
 * @param {number} props.categoryId - Category&#39;s ID
 * @param {object} props.classes - additional CSS classes that will be applied to the component
 * @param {object} props.data - Category data
 * @param {object} props.pageControl - Pagination data
 * @param {number} props.pageSize - Page size
 * @param {object} props.sortProps - Sort props
 */
const ImprovedCategoryContent = (props) =&gt; {
  const {
    categoryId,
    classes,
    data,
    pageControl,
    sortProps,
    pageSize,
    ...rest
  } = props;

  const { isLandingPage, isLoading, staticBlockId } =
    useImprovedCategoryContent({ categoryId });

  const categoryContent = isLandingPage ? (
    &lt;div&gt;
      &lt;CategoryLandingPage staticBlockId={staticBlockId} /&gt;
    &lt;/div&gt;
  ) : (
    &lt;CategoryContent
      categoryId={categoryId}
      classes={classes}
      data={data}
      pageControl={pageControl}
      sortProps={sortProps}
      pageSize={pageSize}
    /&gt;
  );

  const shouldDisplayContent = !isLoading ? (
    categoryContent
  ) : (
    &lt;LoadingIndicator /&gt;
  );

  return &lt;div {...rest}&gt;{shouldDisplayContent}&lt;/div&gt;;
};

ImprovedCategoryContent.propTypes = {
  categoryId: PropTypes.number.isRequired,
  classes: PropTypes.object,
  data: PropTypes.object,
  pageControl: PropTypes.object,
  pageSize: PropTypes.number,
  sortProps: PropTypes.array,
};

export default ImprovedCategoryContent;
</code></pre>
<p>The purpose of this component is to check if the category page is a category landing page. All logic related to checking that is in useImprovedCategoryContent hook that we will create in the next step.</p>
<p>If the category is a landing page, the CategoryLandingPage component is rendered. Otherwise, native PWA Studio’s CategoryContent will be rendered.</p>
<p>Next, create the <strong>src / @theme / category / components / ImprovedCategoryContent / index.js</strong> file with following content:</p>
<pre><code class="language-javascript">export { default } from &quot;./ImprovedCategoryContent&quot;;
</code></pre>
<p>Add <strong>GET_CATEGORY_LANDING_PAGE</strong> query Before implementing the hook, let’s create a <a href="/the-full-stack-guide-to-the-graphql-query">/the-full-stack-guide-to-the-graphql-query</a> that will get information about category display mode and a Static Block ID used for a category’s content.</p>
<p>Create a <strong>src/@theme/category/queries.gql.js</strong> file:</p>
<pre><code class="language-javascript">import gql from &quot;graphql-tag&quot;;

export const GET_CATEGORY_LANDING_PAGE = gql`
  query category($id: Int!) {
    category(id: $id) {
      id
      display_mode
      landing_page
    }
  }
`;
</code></pre>
<p>The query is really straightforward. One important thing here is the ID field in results. Thanks to that ID, Apollo Client can merge these query results with other queries by ID.</p>
<p>Records are merged by ID, and if you have three queries for a category with the same ID, results will be stored in Apollo cache and available without querying to the server. How powerful is that?</p>
<hr>
<h3>Add useImprovedCategoryContent hook</h3>
<p>Create a <strong>src / @theme / category / talons / useImprovedCategoryContent.js</strong> file:</p>
<pre><code class="language-javascript">import { useQuery } from &quot;@apollo/client&quot;;
import { GET_CATEGORY_LANDING_PAGE } from &quot;../queries.gql&quot;;

/**
 * Returns props necessary to render the ImprovedCategoryContent @component.
 *
 * @param {object} props
 * @param {number} props.categoryID
 *
 * @returns {string} result.error - error message returns if something went wrong
 * @returns {bool} result.isLandingPage - flag determinates is a Category is in Statick Blocks only mode.
 *                                        This is true when display mode eauals &#39;PAGE&#39;
 * @returns {bool} result.isLoading - flag determinates is data loading
 * @returns {number|null} result.staticBlockId - Static block ID set up for the Category Page or null.
 */
export const useImprovedCategoryContent = (props) =&gt; {
  const { categoryId } = props;

  const { data, loading } = useQuery(GET_CATEGORY_LANDING_PAGE, {
    fetchPolicy: &quot;cache-and-network&quot;,
    nextFetchPolicy: &quot;cache-first&quot;,
    variables: {
      id: categoryId,
    },
  });

  return {
    isLandingPage: data &amp;&amp; data.category.display_mode === &quot;PAGE&quot;,
    isLoading: loading,
    staticBlockId: data ? data.category.landing_page : null,
  };
};
</code></pre>
<p>Note: Because I want to keep this example simple, I do not handle errors here.</p>
<p>That hook receives one parameter - categoryId, and it gets data from the Magento backend using the already declared <strong>GET_CATEGORY_LANDING_PAGE</strong> query.</p>
<p>Hook returns three fields:</p>
<ul>
<li><p><strong>isLandingPage</strong> - this flag determines category is a landing page or not. Each category that has set up Display Mode equals Page is a landing page.</p>
</li>
<li><p><strong>isLoading</strong> - determines state of data loading</p>
</li>
<li><p><strong>staticBlockId</strong> - ID of static block set up for Category landing page.</p>
</li>
</ul>
<h3>Update local-intercept.js</h3>
<p>It’s time to inject our component into Storefront. Take a look at the code below. If you are not familiar with PWA Studio extensibility framework, check <a href="https://marcin-kwiatkowski.com/blog/pwastudio/how-to-extend-pwa-studio-with-new-features">https://marcin-kwiatkowski.com/blog/pwastudio/how-to-extend-pwa-studio-with-new-features</a>.</p>
<p>File <strong>src/local-intercept.js</strong>:</p>
<pre><code class="language-javascript">const { Targetables } = require(&quot;@magento/pwa-buildpack&quot;);

module.exports = (targets) =&gt; {
  const targetables = Targetables.using(targets);

  const CategoryRootComponent = targetables.reactComponent(
    &quot;@magento/venia-ui/lib/RootComponents/Category/category&quot;,
  );

  const ImprovedCategoryContent = CategoryRootComponent.addImport(
    &quot;ImprovedCategoryContent from &#39;@theme/category/components/ImprovedCategoryContent&#39;&quot;,
  );

  CategoryRootComponent.replaceJSX(
    &quot;&lt;CategoryContent /&gt;&quot;,
    `&lt;${ImprovedCategoryContent} /&gt;`,
  ).setJSXProps(`ImprovedCategoryContent`, {
    categoryId: &quot;{id}&quot;,
    classes: &quot;{classes}&quot;,
    data: &quot;{categoryData}&quot;,
    pageControl: &quot;{pageControl}&quot;,
    sortProps: &quot;{sortProps}&quot;,
    pageSize: &quot;{pageSize}&quot;,
  });
};
</code></pre>
<p>To insert the ImprovedCategoryContent component, we added import to the Category root component. We used the replaceJSX method to insert the component to the JSX (replace the native component with our new one). We passed all props from the native component to ours.</p>
<h3>Add CategoryLandingPage component</h3>
<p>First, create a file <strong>src / @theme / category / components / CategoryLandingPage / CategoryLandingPage.js</strong> with the following content:</p>
<pre><code class="language-javascript">import React from &quot;react&quot;;
import PropTypes from &quot;prop-types&quot;;
import PlainHtmlRenderer from &quot;@magento/venia-ui/lib/components/RichContent&quot;;
import LoadingIndicator from &quot;@magento/venia-ui/lib/components/LoadingIndicator/indicator&quot;;

import { useCategoryLandingPage } from &quot;../../talons/useCategoryLandingPage&quot;;
import classes from &quot;./CategoryLandingPage.module.css&quot;;

/**
 * The CategoryLandingPage @component displays CMS content for categories that have set up Display mode to Static block only.
 *
 * @param {object} props
 * @param {string} props.staticBlockId - Static block&#39;s ID that provides content for the Page
 */
const CategoryLandingPage = (props) =&gt; {
  const { staticBlockId, ...rest } = props;

  const { content, errorMessage, isLoading } = useCategoryLandingPage({
    staticBlockId,
  });

  const shouldDisplayContent = !isLoading ? (
    &lt;PlainHtmlRenderer html={content} /&gt;
  ) : (
    &lt;LoadingIndicator /&gt;
  );
  const shouldDisplayError = errorMessage ? &lt;p&gt;{errorMessage}&lt;/p&gt; : null;

  return (
    &lt;div className={classes.categoryLandingPage} {...rest}&gt;
      {shouldDisplayContent}
      {shouldDisplayError}
    &lt;/div&gt;
  );
};

CategoryLandingPage.propTypes = {
  staticBlockId: PropTypes.string.isRequired,
};

export default CategoryLandingPage;
</code></pre>
<p>Keep in mind the component is rendered if a category has display mode equals Page set up. The component receives staticBlockId prop and uses it to get content for a specific category.</p>
<p>Content is rendered using PlainHtmlRenderer. If something went wrong, an error message is rendered.</p>
<p>Second, create a file <strong>src / @theme / category / components / CategoryLandingPage / index.js</strong>:</p>
<pre><code class="language-javascript">export { default } from &#39;./CategoryLandingPage&#39;;
</code></pre>
<p>Lastly, create a CSS module <strong>src /@theme / category / components / CategoryLandingPage / CategoryLandingPage.module.css</strong></p>
<pre><code class="language-css">.categoryLandingPage {
  padding: 20px;
}
</code></pre>
<h3>Add useCategoryLandingPage hook</h3>
<p>The last thing needed to display the Category landing page’s content is the hook that collects content from Magento.</p>
<p>Create a file <strong>src / @theme / category / talons / useCategoryLandingPage.js</strong>:</p>
<pre><code class="language-javascript">import { useState, useEffect } from &#39;react&#39;;
import { useQuery } from &#39;@apollo/client&#39;;
import { GET_CMS_BLOCKS } from &#39;@magento/venia-ui/lib/components/CmsBlock/cmsBlock.js&#39;;

/**
 * Returns props necessary to render the CategoryLandingPage @component.
 *
 * @param {object} props
 * @param {number} props.staticBlockId - ID of a Static Block connected with the Category Landing Page
 *
 * @returns {string} result.errorMessage - error message returns if something went wrong
 * @returns {bool} result.isLoading - flag determinates is data loading
 * @returns {string} result.content - HTML content of Static Block connected to the Category Landing Page
 */
export const useCategoryLandingPage = props =&gt; {
    const {
        staticBlockId
    } = props;

    const [ content, setContent ] = useState(null);
    const [ errorMessage, setErrorMessage ] = useState(null);

    const { data, error, loading } = useQuery(GET_CMS_BLOCKS, {
        fetchPolicy: &#39;cache-and-network&#39;,
        nextFetchPolicy: &#39;cache-first&#39;,
        skip: !staticBlockId,
        variables: {
            identifiers: [ staticBlockId ]
        }
    });

    useEffect(() =&gt; {
        if (data &amp;&amp; data.cmsBlocks &amp;&amp; data.cmsBlocks.items) {
            setContent(data.cmsBlocks.items[0].content);
        }

        if (!staticBlockId || error || data &amp;&amp; data.cmsBlocks &amp;&amp; data.cmsBlocks.items.length === ) {
            setErrorMessage(&#39;Unable to get category page content. Please try again later.&#39;)
        }

    }, [data, staticBlockId, error])

    return {
        errorMessage,
        isLoading: loading,
        content
    }
}
</code></pre>
<p>The component returns three fields, and the most important for us is content, which contains the Category Landing page’s content!</p>
<hr>
<h2>Summary</h2>
<p>This time we added support for Category Landing Pages to PWA Studio Storefront. As you can see, the PWA Studio Extensibility framework is really powerful, and thanks to this we can easily extend <a href="https://marcin-kwiatkowski.com/blog/pwastudio/getting-started-with-magento-pwa-studio-targetables">https://marcin-kwiatkowski.com/blog/pwastudio/getting-started-with-magento-pwa-studio-targetables</a> with new features.</p>
<hr>
<h2>Source code</h2>
<p>The source code for this tutorial is available on my <a href="https://github.com/Frodigo/pwa-studio-category-landing-pages">https://github.com/Frodigo/pwa-studio-category-landing-pages</a></p>
<p>#WebDevelopment #FrontendDevelopment #BackendDevelopment #JavaScript #CSS #GraphQL #React #PWAStudio #Magento #Apollo #Tutorial #DeepDive #ProjectSetup #Intermediate #API #ComponentArchitecture</p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to extend PWA Studio with new features]]></title>
            <description><![CDATA[<em>Published at 25/03/2021</em>]]></description>
            <link>https://frodigo.com/Blog/Archive/Frontend/Magento/How+to+extend+PWA+Studio+with+new+features</link>
            <guid isPermaLink="false">https://frodigo.com/Blog/Archive/Frontend/Magento/How+to+extend+PWA+Studio+with+new+features</guid>
            <pubDate>Thu, 25 Mar 2021 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p><em>Published at 25/03/2021</em></p>
<p>This time I would like to show you how to extend <strong>PWA Studio</strong> with new features. After reading you will know the following:</p>
<ul>
<li><p>What is the <strong>PWA Studio’s extensibility framework</strong> and what tools/techniques it offers</p>
</li>
<li><p>How to extend the PWA Studio storefront</p>
</li>
<li><p>How to create a new PWA Studio module</p>
</li>
<li><p>How to customize PWA Studio storefront’s styles</p>
</li>
</ul>
<hr>
<h2>PWA Studio extensibility framework</h2>
<p>PWA Studio extensibility framework is a set of tools that helps developers to extend storefronts in a smart way.</p>
<p>What does that mean? For me extension is smarty if it adds something to the app without core code overwriting, for example:</p>
<ul>
<li><p>adding a new payment method without overwriting any checkout core files - this is smart</p>
</li>
<li><p>adding a short description to a product page without overwriting a bunch of components including the product root component - this is also cute</p>
</li>
<li><p>to overwrite the JS and CSS file of one of the components to change the look of it - this is totally thoughtless</p>
</li>
</ul>
<p>Overwriting core files causes unexpected errors and complicates a project. Each overwrite complicates everything more and more and whatever you want to do in the project with a huge amount of overwrites is hard to do.</p>
<p>if you had overwritten too much in your project you would have looked like the guy on the image.</p>
<p>Thanks to the PWA Studio extensibility framework you can minimize the amount of overwriting to a minimum.</p>
<p><strong>Note:</strong> We are talking about PWA Studio 9 introduced targetables. Thanks to this, extending PWA Studio with the new features is possible without overwriting core code. In previous versions of PWA Studio, it was not possible in general, and keep in mind that many of my articles and tutorials are deprecated now and I need to update them.</p>
<hr>
<h2>Targets</h2>
<p>One of the options to extend PWA Studio is to use Targets.</p>
<p><strong>How it works:</strong></p>
<ul>
<li><p>Using interceptor pattern to modify core behavior by modifying core code output during build time.</p>
</li>
<li><p>the point that you can intercept the normal logic is Targets</p>
</li>
<li><p>targets are variants of a JavaScript pattern called Tappable hook</p>
</li>
<li><p>targets share some functionality with NodeJS’s EventEmitter class</p>
</li>
</ul>
<hr>
<h2>TargetProviders</h2>
<p>The TargetProvider is an object that provides public API to create targets and intercepting targets from other extensions.</p>
<p>For example, you can use routes Targets to add a new route. In this case, you create an intercept.js file in use &quot;routes&quot; target to add a new route to your storefront.</p>
<pre><code class="language-javascript">// intercept.js
module.exports = targets =&gt; {
    const veniaTargets = targets.of(&#39;@magento/venia-ui&#39;);
    const routes = veniaTargets.routes;

    routes.tap(
      routesArray =&gt; {
         routesArray.push({
             name: &#39;New route&#39;,
             pattern: &#39;/new-route&#39;,
             path: &#39;@organization/module&#39;
         });
         return routesArray;
      })
}

// package.json
{
  &quot;pwa-studio&quot;: {
    &quot;targets&quot;: {
      &quot;intercept&quot;: &quot;intercept.js&quot;
    }
  }
}
</code></pre>
<hr>
<h2>Targetables</h2>
<p>Another method to customize PWA Studio is using targetables which are object which represents source files in your projects.</p>
<p>Thanks to targetables you can for example get a specific component and insert JSX wherever you want. Take a look at the example below:</p>
<pre><code class="language-javascript">const { Targetables } = require(&quot;@magento/pwa-buildpack&quot;);

module.exports = (targets) =&gt; {
  const targetables = Targetables.using(targets);

  const MainComponent = targetables.reactComponent(
    &quot;@magento/venia-ui/lib/components/Header/header.js&quot;,
  );

  MainComponent.insertAfterJSX(&quot;&lt;MegaMenu /&gt;&quot;, &quot;&lt;div&gt;My new section!!&lt;/div&gt;&quot;);
};
</code></pre>
<p>On the other hand, if you create your own PWA Studio extension you can use Targetqables to add specific targets for other modules.</p>
<hr>
<h2>How to customize PWA Studio</h2>
<p>Typically if you deal with PWA Studio you create a new Storefront or a new extension.</p>
<h3>Creating a new storefront</h3>
<p>The first option to extend PWA Studio with new features is by creating a new storefront. Venia concept can be your starting point, but it’s not obligatory. Typically you will start from the Venia concept because it has many excellent features already implemented. In this case, you start by scaffolding your project using the <code>yarn create @magento/pwa</code> command. Then you have the opportunity to add customizations.</p>
<p>Take a look at an example scenario when you want to add a new section to the product page. Let’s add lorem ipsum text before <em>Add to cart</em> button.</p>
<p>First, scaffold the PWA Studio project:</p>
<pre><code class="language-bash">yarn create @magento/pwa
cd &lt;project_dir&gt;
yarn run buildpack create-custom-origin
yarn run watch
</code></pre>
<p>Second, add this code to the <code>local-intercept.js</code> file:</p>
<pre><code class="language-javascript">const { Targetables } = require(&quot;@magento/pwa-buildpack&quot;);

module.exports = (targets) =&gt; {
  const targetables = Targetables.using(targets);

  const ProductFullDetailComponent = targetables.reactComponent(
    &quot;@magento/venia-ui/lib/components/ProductFullDetail/productFullDetail.js&quot;,
  );

  ProductFullDetailComponent.insertBeforeJSX(
    &#39;&lt;Button type=&quot;submit&quot; /&gt;&#39;,
    &quot;&lt;span&gt;Hello World! &lt;/span&gt;&quot;,
  );
};
</code></pre>
<p>This code inserts a span with Hello World! Before submit button of the add to cart form.</p>
<h4>Targetables public API</h4>
<p>In the example above, I used <code>insertBeforeJSX</code> method of<code>@magento / pwa-buildpack / lib / WebpackTools / targetables / TargetableReactComponent.js</code></p>
<p>There are a few more public methods available that help you with modifying the PWA Studio storefront:</p>
<ul>
<li><p><strong>insertAfterJSX</strong> - Inserting a JSX element _after_ `element`.</p>
</li>
<li><p><strong>addJSXClassName</strong> - Adding a CSS classname to a <a href="https://marcin-kwiatkowski.com/blog/what-is-jsx-and-is-it-worth-making-friends-with-it">https://marcin-kwiatkowski.com/blog/what-is-jsx-and-is-it-worth-making-friends-with-it</a> element.</p>
</li>
<li><p><strong>addReactLazyImport</strong> - Add a new named dynamic import of another React component</p>
</li>
<li><p><strong>appendJSX</strong> - Appending a JSX element to the children of `element`</p>
</li>
<li><p><strong>prependJSX</strong> - Prepending a JSX element to the children of `element`.</p>
</li>
<li><p><strong>removeJSX</strong> - Removing the JSX node matched by &#39;element&#39;.</p>
</li>
<li><p><strong>removeJSXProps</strong> - Removing JSX props from the element if they match one of the lists of names.</p>
</li>
<li><p><strong>replaceJSX</strong> - Replacing a JSX element with a different code.</p>
</li>
<li><p><strong>setJSXProps</strong> - Setting JSX props on a JSX element.</p>
</li>
<li><p><strong>surroundJSX</strong> - wrapping a JSX element in an outer element.</p>
</li>
</ul>
<p>As you can see, those JSX manipulations are powerful and let you do whatever you want.</p>
<h3>Creating a new extension</h3>
<p>Another way to customize PWA Studio is by creating a new extension. For example, if you had wanted to add integration with any headless CMS systems, you would have to create a new extension.</p>
<p>Everyone who wants to use your extension can install it and use it in his project. This also means that any extension can be extended in storefronts where it’s used.</p>
<p>As an extension creator, you can use Targetables in your intercept file to add specific Targets that are available to other extensions.</p>
<p>I recommend using <a href="https://github.com/larsroettig/create-pwa-studio-extension">https://github.com/larsroettig/create-pwa-studio-extension</a> to create new PWA Studio extensions.</p>
<p>You can create an extension using one command:</p>
<p><code>$ yarn create @larsroettig/pwa-extension</code></p>
<p>Then you need to link your extension in the package.json file of the PWA Studio project and you can work on it.</p>
<p>If you want to read more about creating extensions please check <a href="https://marcin-kwiatkowski.com/how-to-build-a-high-quality-pwa-studio-extension">https://marcin-kwiatkowski.com/how-to-build-a-high-quality-pwa-studio-extension</a></p>
<h3>Styling PWA Studio</h3>
<p>The common thing that frontend developers do is styling. In terms if you want to customize styles of the Storefront you need to add your custom styles somewhere.</p>
<p>Thanks to Targetables styling PWA Studio is much more simplified than before. Chris Brabender wrote a fascinating article about this.</p>
<p>You can find it here: <a href="https://dev.to/chrisbrabender/simplifying-styling-in-pwa-studio-1ki1">https://dev.to/chrisbrabender/simplifying-styling-in-pwa-studio-1ki1</a></p>
<hr>
<h2>Summary</h2>
<p>Since PWA Studio 9.0.0 developer experience is much better, there is a public API called Targetables that gives developers an easy way to customize PWA Studio Storefronts.</p>
<p>Thanks to that, you can manipulate JSX output and develop high-quality storefronts/extensions.</p>
<p>Besides that, there is the Targets API that allows, for example, to create a new storefront routes or new content renderers in 1 minute without any overwrites.</p>
<p>All of these goods make PWA Studio a great starting point for any new headless projects connected with the Magento backend.</p>
<hr>
<h2>Sources</h2>
<ul>
<li><p><a href="https://magento.github.io/pwa-studio/pwa-buildpack/extensibility-framework/">https://magento.github.io/pwa-studio/pwa-buildpack/extensibility-framework/</a></p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Interceptor_pattern">https://en.wikipedia.org/wiki/Interceptor_pattern</a></p>
</li>
<li><p><a href="https://github.com/webpack/tapable">https://github.com/webpack/tapable</a></p>
</li>
<li><p><a href="https://magento.github.io/pwa-studio/venia-ui/reference/targets/#routes--tapableasyncserieswaterfall">https://magento.github.io/pwa-studio/venia-ui/reference/targets/#routes--tapableasyncserieswaterfall</a></p>
</li>
</ul>
<p>#WebDevelopment #FrontendDevelopment #JavaScript #JSX #React #PWAStudio #Magento #Webpack #ConceptExplanation #Tutorial #BestPractices #Intermediate #Extensibility #CustomizationTechniques</p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Everything you need to know about CSS modules]]></title>
            <description><![CDATA[<em>Date: 15/02/2021</em>]]></description>
            <link>https://frodigo.com/Blog/Archive/Frontend/Everything+you+need+to+know+about+CSS+modules</link>
            <guid isPermaLink="false">https://frodigo.com/Blog/Archive/Frontend/Everything+you+need+to+know+about+CSS+modules</guid>
            <pubDate>Mon, 15 Feb 2021 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p><em>Date: 15/02/2021</em></p>
<h2>CSS is easy, right?</h2>
<p>I started my web developer journey as a frontend developer, and with time I also started to deal with backend employees. My colleagues from the backend team were laughing that CSS is just a language for just setting margins and colors.</p>
<p>Keep in mind: if you do something more than adding color to headings…</p>
<p><strong>CSS is not easy.</strong></p>
<p>Take a look at the list below. The biggest CSS dangers are described here:</p>
<ol>
<li><p>Conflicts between CSS rules</p>
</li>
<li><p>Overwrites</p>
</li>
<li><p>A big CSS stylesheet with unused rules</p>
</li>
<li><p>Side effects</p>
</li>
</ol>
<h2>CSS modules to the rescue</h2>
<p>Thanks to the fact that CSS modules are scoped locally, the problems described above disappear.</p>
<ul>
<li><p>no more conflicts</p>
</li>
<li><p>no side effects</p>
</li>
<li><p>no global scope</p>
</li>
</ul>
<h2>An example CSS module</h2>
<p>Take a look below at a straightforward example of a CSS module in <a href="https://marcin-kwiatkowski.com/blog">https://marcin-kwiatkowski.com/blog</a>:</p>
<h3>CSS module (this is just a CSS file)</h3>
<pre><code class="language-css">.section {
  padding: 15px;
}

.heading {
  font-size: 24px;
  font-weight: bold;
  margin: 15px 0;
}
</code></pre>
<h3>The sample component which uses these styles</h3>
<pre><code class="language-javascript">import React from &quot;react&quot;;
import classes from &quot;./sample-component.css&quot;;

const SampleComponent = () =&gt; {
  return (
    &lt;div className={classes.section}&gt;
      &lt;h3 className={classes.heading}&gt;This is the sample component title&lt;/h3&gt;
      &lt;p&gt;This is the sample component paragraph&lt;/p&gt;

      &lt;h4 className={classes.heading}&gt;This is the sample list:&lt;/h4&gt;
      &lt;ul&gt;
        &lt;li&gt;Sample item A&lt;/li&gt;
        &lt;li&gt;Sample item B&lt;/li&gt;
        &lt;li&gt;Sample item C&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/div&gt;
  );
};

export default SampleComponent;
</code></pre>
<p>On the third line, we imported a CSS module called sample-component.css as a classes object. Now we are able to use classes from the CSS module as object properties. Take a look at lines 6,7, and 10.</p>
<p>So this is exactly what CSS modules do—they let you use CSS classes as object properties in <a href="https://marcin-kwiatkowski.com/blog/what-is-jsx-and-is-it-worth-making-friends-with-it">https://marcin-kwiatkowski.com/blog/what-is-jsx-and-is-it-worth-making-friends-with-it</a> files.</p>
<h3>How does it work?</h3>
<p>Thanks to Webpack and the configuration of the loaders, the classes described in the CSS module, and applied in the React component, are rendered as unique CSS classes for each place where they are used.</p>
<ol>
<li><p>Here is the place where the section class is applied to the node.</p>
</li>
<li><p>This is a CSS rule which includes CSS properties.</p>
</li>
<li><p>Here you can see a unique class name applied to the node in the compiled app.</p>
</li>
<li><p>This is compiled into a unique CSS rule.</p>
</li>
</ol>
<hr>
<h3>Webpack configuration</h3>
<p>Take a look at the webpack configuration. In PWA Studio you can find them in @magento / pwa-buildpack / lib / WebpackTools / configureWebpack / getModuleRules.js file</p>
<pre><code class="language-javascript">getModuleRules.css = async ({ paths, hasFlag }) =&gt; ({
  test: /\.css$/,
  oneOf: [
    {
      test: [paths.src, ...hasFlag(&quot;cssModules&quot;)],
      use: [
        &quot;style-loader&quot;,
        {
          loader: &quot;css-loader&quot;,
          options: {
            localIdentName: &quot;[name]-[local]-[hash:base64:3]&quot;,
            modules: true,
          },
        },
      ],
    },
    {
      include: /node_modules/,
      use: [
        &quot;style-loader&quot;,
        {
          loader: &quot;css-loader&quot;,
          options: {
            modules: false,
          },
        },
      ],
    },
  ],
});
</code></pre>
<p>On line 11 you can see where the names of the compiled classes come from, and line 12 is where the CSS modules are enabled.</p>
<h2>Composition</h2>
<p>CSS modules let you compose rules from other rules:</p>
<pre><code class="language-css">.heading {
  font-size: 24px;
  font-weight: bold;
  margin: 15px 0;
}

.secondaryHeading {
  composes: heading;
}
</code></pre>
<p>It’s possible to compose multiple selectors:</p>
<pre><code class="language-css">.section {
  padding: 15px;
}

.header {
  background-color: lightgray;
}

.heading {
  font-size: 24px;
  font-weight: bold;
  margin: 15px 0;
}

.secondaryHeading {
  composes: heading;
}

.sampleHeader {
  composes: section header heading;
}
</code></pre>
<h2>Global CSS Rules</h2>
<p>Scoping styles locally is lovely, but sometimes you need to define some global CSS rules, for example for animations (keyframes). CSS modules let us create global rules using the :global() function.</p>
<pre><code class="language-css">// global-styles.css
:global(.global-class-name) {
  color: red;
}
</code></pre>
<p>When you import this CSS module into your app, you will be able to use a global CSS rule like this:</p>
<pre><code class="language-javascript">// import stylesheet
import &#39;./global-styles.css&#39;
...
// example of using global CSS rule
&lt;p className=&quot;global-class-name&quot;&gt;This is paragraph with global styles appiled&lt;/p&gt;
</code></pre>
<h3>Composing from global</h3>
<p>Sometimes it is necessary to compose from a global rule to a local one, which you can do like this:</p>
<pre><code class="language-css">.redHeading {
  composes: global-class-name from global;
}
</code></pre>
<h2>Naming conventions</h2>
<p>It’s recommended to use the camel case naming convention because using classes in JS, in this case, is easiest. When the name of the class looks. for example, like this: .my-sample-class, then you can apply this class to an element in the following way:</p>
<pre><code class="language-jsx">&lt;ul className={classes[&#39;my-sample-class&#39;]}&gt;
</code></pre>
<h2>Merging and overrides classes</h2>
<p>In PWA Studio you can pass classes as props to a component, and overwrite default component classes with classes from props. Take a look:</p>
<pre><code class="language-css">import { mergeClasses } from &#39;@magento/venia-ui/lib/classify&#39;;
import defaultClasses from &#39;@magento/venia-ui/lib/components/Main/main.css&#39;;

const Main = props =&gt; {
   const classes = mergeClasses(defaultClasses, props.classes);
}
</code></pre>
<p>If you pass classes as props, you can validate them using PropTypes:</p>
<pre><code class="language-javascript">Main.propTypes = {
  classes: shape({
    page: string,
    page_masked: string,
    root: string,
    root_masked: string,
  }),
};
</code></pre>
<hr>
<h2>Conclusion</h2>
<p>In my opinion, locally scoped CSS resolve many problems and is a really nice, modern approach to managing styles. Perhaps for many Frontend Developers, this is a controversial approach, and that’s OK because this is completely different from what is commonly used.</p>
<p>Maybe you have experience with CSS modules? Let me know in the comments!</p>
<p>Thanks for reading. All likes, shares, and comments are really appreciated.</p>
<h2>Github repository</h2>
<p>You can find examples of the code for this article on my Github.<a href="https://github.com/Frodigo/css-modules-examples-pwa-studio">https://github.com/Frodigo/css-modules-examples-pwa-studio</a></p>
<h2>Sources</h2>
<p><a href="https://magento.github.io/pwa-studio/technologies/basic-concepts/css-modules/">https://magento.github.io/pwa-studio/technologies/basic-concepts/css-modules/</a></p>
<p><a href="https://github.com/css-modules/css-modules">https://github.com/css-modules/css-modules</a></p>
<p><a href="https://webpack.js.org/loaders/css-loader/#modules">https://webpack.js.org/loaders/css-loader/#modules</a></p>
<p><a href="https://x-team.com/blog/css-modules-a-new-way-to-css/">https://x-team.com/blog/css-modules-a-new-way-to-css/</a></p>
<p><a href="https://glenmaddern.com/articles/css-modules">https://glenmaddern.com/articles/css-modules</a></p>
<p>#WebDevelopment #FrontendDevelopment #JavaScript #CSS #React #Webpack #PWAStudio #JSX #ConceptExplanation #Tutorial #BestPractices #Intermediate #ModularDesign</p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[What is the difference between PWA Studio and the current Magento frontend]]></title>
            <description><![CDATA[<em>Last updated 07/02/2021</em>]]></description>
            <link>https://frodigo.com/Blog/Archive/Frontend/Magento/What+is+the+difference+between+PWA+Studio+and+the+current+Magento+frontend</link>
            <guid isPermaLink="false">https://frodigo.com/Blog/Archive/Frontend/Magento/What+is+the+difference+between+PWA+Studio+and+the+current+Magento+frontend</guid>
            <pubDate>Sun, 07 Feb 2021 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p><em>Last updated 07/02/2021</em></p>
<p>This change will be a big challenge for frontend developers because working with <strong>PWA Studio</strong> is entirely different from working with the current Magento frontend. This change will be a big challenge for frontend developers because working with PWA Studio is entirely different from working with the current Magento frontend. The technology stack used is completely different. Therefore, different competencies and skills are required. In this article, I would like to show you the differences between the two and compare the current Magento frontend technology stack with that of PWA Studio.</p>
<hr>
<h2>Prerequisites</h2>
<p>The most crucial difference is that the PWA Studio is a typical React app, and you don&#39;t need special knowledge about Magento to get started with PWA Studio development.</p>
<p>Do you know <strong>React</strong>?</p>
<p>Do you know <strong>GraphQL and Apollo</strong>?</p>
<p><strong>If yes, You are ready to go!</strong></p>
<p>On the other hand – because it&#39;s a React App – the typical Magento Frontend Developer will have problems getting started.</p>
<p>You may ask, why?</p>
<p>In my opinion, Magento frontend devs are not familiar(no all!) with advanced JavaScript patterns and methodologies. They work with jQuery on daily basics, sometimes with <strong>KnockoutJS</strong>.</p>
<p>Understanding Magento JavaScript development is quite challenging and forces you to learn specific patterns that only Magento uses.</p>
<p>That is the main reason why there are not too many frontend Magento developers. Almost nobody wants to learn Magento-specific stuff (who wants to use KnockoutJS in 2021, BTW?)</p>
<p>I had a problem returning to the &quot;normal world&quot; after a few years with Magento frontend development and learn React.</p>
<p>Ok, this was only an introduction. Take a look at the differences between Magento-monolithic frontend and PWA Studio below.</p>
<hr>
<h2>Theme vs. Application</h2>
<p>The current Magento frontend is built based on themes. There are two primary themes (Blank and Luma) that you can inherit from. The Luma theme inherits from the Blank theme, and the Blank theme overrides files from core Magento modules.</p>
<p>Theme code is placed in the Magento structure, and it is a part of a project. Moreover, it&#39;s possible to create modules and manage frontend customization directly in module code.</p>
<p><strong>PWA Studio</strong> uses an entirely different approach. Have you heard about headless? First of all, this means that the storefront is separated from the backend. This is the right approach because the frontend developer environment is faster and more straightforward. A Magento instance can be set up in a different place entirely. In this case, the frontend team and the backend team are independent, and developing software is more comfortable for both sides.</p>
<p>The only important thing when it comes to using a headless storefront with the Magento backend is data such as products, customer data, shopping cart, etc. All this data is retrieved from Magento using GraphQLqueries (it is also possible to use the REST API to get data from the backend if The GraphQL API is not available for a particular functionality).</p>
<p>One of the most significant advantages of using GraphQL is that a frontend developer can quickly mock sample data, and switch to real data when the backend is done.</p>
<hr>
<h2>Inheritance vs. modularity</h2>
<p>When working with a Magento theme, frontend developers typically override templates, layouts, JS and CSS/Less files from the parent themes or modules. With multi-level inheritance and a large number of modules, a minor change can quickly become complicated.</p>
<p>The PWA Studio storefront is quite different and requires frontend developers to build a storefront from scratch using ready-made React components, or by making their own. PWA Studio is simply a collection of tools that are designed to facilitate your storefront development. Take a look below at the most essential parts of PWA Studio.</p>
<hr>
<h2>Peregrine</h2>
<p>This library gives developers a set of functions responsible for providing data to visual components in their application. This is mostly achieved through hooks and talons. You may be wondering what &#39;talons&#39; means. Talons are a specific type of hook, which are designed for particular components. For example, the useFooter talon is designed to be used with the footer component and provide it with copyright text.</p>
<pre><code class="language-javascript">import { useEffect } from &quot;react&quot;;
import { useQuery } from &quot;@apollo/react-hooks&quot;;

/**
 *
 * @param {*} props.query the footer data query
 */
export const useFooter = (props) =&gt; {
  const { query } = props;
  const { error, data } = useQuery(query);

  useEffect(() =&gt; {
    if (error) {
      console.log(&quot;Error fetching copyright data.&quot;);
    }
  }, [error]);

  return {
    copyrightText: data &amp;&amp; data.storeConfig &amp;&amp; data.storeConfig.copyright,
  };
};
</code></pre>
<hr>
<h2>Venia UI API</h2>
<p>his is a set of visual components that you can use when building a storefront. Within this set you will find a few base components, for example, Button, ButtonGroup, Logo.</p>
<h2>Venia Storefront (Concept)</h2>
<p>This is a storefront built using Peregrine and Venia UI. This is a storefront demo that presents the capabilities of PWA Studio. It can be the starting point of your application, but it doesn&#39;t have to be. Anyway it provides support for many Magento features and a few ways to extensibility.</p>
<hr>
<h2>Developer skill set comparison</h2>
<p>I wanted to give this section the title: &quot;Frontend Developer skill set comparison,&quot; but sometimes, when I think about Magento Frontend Developers, I get confused, because they need to be familiar with a few non-frontend technologies like <strong>PHP</strong> and <strong>XML.</strong> I believe that for more experienced frontend developers, the right career path will be working towards becoming a Full Stack Magento Developer.</p>
<p>But not everyone wants to work this way, and it is not surprising that not every frontend developer will want to learn PHP. Here, PWA Studio has an advantage because it uses typical frontend tools and technologies, which are more friendly for frontend developers. Take a look at the table below where you can find a comparison of the technologies and tools used in a typical Magento frontend and in PWA Studio.</p>
<table>
<thead>
<tr>
<th>Magento Luma</th>
<th>PWA Studio</th>
</tr>
</thead>
<tbody><tr>
<td>PHP</td>
<td>React</td>
</tr>
<tr>
<td>Javascript ES5</td>
<td>JavaScript ES6</td>
</tr>
<tr>
<td>Rest API</td>
<td>GraphQl</td>
</tr>
<tr>
<td>jQuery</td>
<td></td>
</tr>
<tr>
<td>Knockout</td>
<td></td>
</tr>
<tr>
<td>Ground</td>
<td>Webpack</td>
</tr>
<tr>
<td>CSS &amp; Less</td>
<td>CSS &amp; CSS modules</td>
</tr>
<tr>
<td>XML</td>
<td>JSON</td>
</tr>
<tr>
<td>Magento Layouts</td>
<td></td>
</tr>
<tr>
<td>Magento templates</td>
<td></td>
</tr>
<tr>
<td>Magento UI Library</td>
<td>Venia UI</td>
</tr>
<tr>
<td>Magento UI Components</td>
<td>Peregrine</td>
</tr>
<tr>
<td>Composer</td>
<td>Yarn</td>
</tr>
</tbody></table>
<hr>
<h3>JavaScript ES5 vs. JavaScript ES6</h3>
<p>In PWA Studio, the ES6 version of JS is standard, while Magento still uses ES5. I think that for every JavaScript developer, writing code in a newer standard is a meaningful advantage. It is true that you can also write in ES6 while working on the Magento frontend, if you take care of compilation to ES5 (I recommend using Snowdog Frontools for this). The problem is that this is a solution for new code only. You still need to remember that all of the core JavaScript code in Magento is written using the old standard, and you just have to live with it.</p>
<h3>Grunt vs. Webpack</h3>
<p>The Magento frontend uses Grunt as a task runner. A frontend developer can compile Less files and run unit tests using Grunt, but in fact, using Grunt is not necessary. For example, a developer can compile styles using a client-side compilation workflow. PWA Studio uses Webpack, and this tool is very different to Grunt. Webpack is a module bundler, and it is responsible for compiling the whole PWA Studio application.</p>
<h3>Less vs. CSS modules</h3>
<p>CSS modules are an interesting approach. The key feature is that all styles are used locally, i.e., within one component. This is an entirely different approach from the current Magento frontend, where styles are global.</p>
<h3>Knockout, jQuery, Magento UI components vs. React</h3>
<p>In my opinion, the Magento frontend&#39;s biggest problem is that there is a mix of technologies and this results in chaos. Famous UI Components – a result of the Magento Core Team&#39;s imagination – are a combination of JavaScript, PHP, and XML, and digging deeper, it turns out that Knockout is mixed with jQuery. It turns out that Knockout is mixed with jQuery.</p>
<p>In PWA Studio, React is used, and the best thing is that a Frontend developer doesn&#39;t need to know PHP, XML, or any other non-frontend tools and technologies.</p>
<h3>Magento templates vs. <a href="https://marcin-kwiatkowski.com/blog/what-is-jsx-and-is-it-worth-making-friends-with-it">https://marcin-kwiatkowski.com/blog/what-is-jsx-and-is-it-worth-making-friends-with-it</a></h3>
<p>Also, there is another type of template that is used by Magento UI Components. These templates are written as HTML files, and within them, you can find static HTML code mixed with KnockoutJS bindings.</p>
<p>Now, just as a comparison, take a look at an example piece of code for a PWA Studio view component:</p>
<hr>
<h2>Summary</h2>
<p>PWA Studio is different from the current Magento frontend in every area. The most significant advantage (for developers) is that PWA Studio is more developer-friendly. This is the future for Magento Frontend developers, and I am keeping my fingers crossed for this project. Also, keep in mind that there are alternatives to PWA Studio, for example: Vue Storefront. So maybe PWA technology is the future of e-commerce? What do you think? Feel free to post comments.</p>
<hr>
<h2>Sources</h2>
<ul>
<li><p><a href="https://magento.github.io/pwa-studio/technologies/theme-vs-storefront/">https://magento.github.io/pwa-studio/technologies/theme-vs-storefront/</a></p>
</li>
<li><p><a href="https://magento.github.io/pwa-studio/peregrine/">https://magento.github.io/pwa-studio/peregrine/</a></p>
</li>
<li><p><a href="https://github.com/magento/pwa-studio/tree/develop/packages/peregrine/lib/talons">https://github.com/magento/pwa-studio/tree/develop/packages/peregrine/lib/talons</a></p>
</li>
<li><p><a href="https://www.smashingmagazine.com/2020/04/react-hooks-api-guide/">https://www.smashingmagazine.com/2020/04/react-hooks-api-guide/</a></p>
</li>
<li><p><a href="https://magento.github.io/pwa-studio/venia-ui/reference/components/Button/">https://magento.github.io/pwa-studio/venia-ui/reference/components/Button/</a></p>
</li>
<li><p><a href="https://stackshare.io/stackups/grunt-vs-gulp-vs-webpack">https://stackshare.io/stackups/grunt-vs-gulp-vs-webpack</a></p>
</li>
<li><p><a href="https://github.com/css-modules/css-modules">https://github.com/css-modules/css-modules</a></p>
</li>
</ul>
<p>#WebDevelopment #FrontendDevelopment #Ecommerce #JavaScript #PHP #CSS #React #GraphQL #Apollo #KnockoutJS #jQuery #Webpack #Grunt #Magento #PWAStudio #Comparison #ConceptExplanation #BestPractices #Intermediate #Headless #ProgressiveWebApps</p>
]]></content:encoded>
        </item>
    </channel>
</rss>