<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Olavi Haapala</title>
    <description>The latest articles on Forem by Olavi Haapala (@olpeh).</description>
    <link>https://forem.com/olpeh</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F10689%2F02d2fdd9-72af-438e-b628-28a78b1d5d65.jpg</url>
      <title>Forem: Olavi Haapala</title>
      <link>https://forem.com/olpeh</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/olpeh"/>
    <language>en</language>
    <item>
      <title>Learn from Your Mistakes</title>
      <dc:creator>Olavi Haapala</dc:creator>
      <pubDate>Tue, 23 Mar 2021 18:59:00 +0000</pubDate>
      <link>https://forem.com/olpeh/learn-from-your-mistakes-41l0</link>
      <guid>https://forem.com/olpeh/learn-from-your-mistakes-41l0</guid>
      <description>&lt;p&gt;In this blog post, I will be sharing what I have learned from creating and maintaining a Sailfish OS app for tracking working hours. It’s simply called &lt;a href="https://wht.olpe.fi/"&gt;Working Hours Tracker&lt;/a&gt;. I started the project in 2014 and have been developing and maintaining it ever since. The most recent version of the app was released on February 20, 2021.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--PdgxTSpL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://olavihaapala.fi/images/23-learn/whtcover.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--PdgxTSpL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://olavihaapala.fi/images/23-learn/whtcover.png" alt="Working Hours Tracker App logo" width="540" height="270"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When I started the project back in 2014, I had quite little real world experience from software projects. I had been studying for a few years at that time, but we all know that most of the learning happens at work or when doing some side projects. My limited experience and knowledge led to some of the interesting mistakes made during the project, and I’ll cover some of those and what I learned from those.&lt;/p&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;You may not have heard about &lt;a href="https://sailfishos.org/"&gt;Sailfish OS&lt;/a&gt;, and that’s fine. It’s not very widely known. It’s a mobile operating system developed by a Finnish company called &lt;a href="https://jolla.com/about/"&gt;Jolla&lt;/a&gt;. They initially launched the OS together with their own devices, but later on they have been more focused on the development of the operating system and licensing it to different partners in addition to being close to bankruptcy twice.&lt;/p&gt;

&lt;p&gt;Personally, I love the Sailfish OS, but there are still a few blockers for me to be running it on my main phone. The available hardware that is officially supported is not at a good enough level for my needs, especially when it comes to the camera.&lt;/p&gt;

&lt;p&gt;Sailfish OS has still a small number of apps available in the store and a small number of community developers working on new apps. However, the small size of the community was one of the nice sides of my project. I think I was able to gain quite a lot of attention from the community because my app used to be the only time tracking application available in the Jolla Store. Check this tweet about how excited I was when the app reached 2k downloads in the Jolla Store:&lt;/p&gt;


&lt;blockquote class="ltag__twitter-tweet"&gt;

  &lt;div class="ltag__twitter-tweet__main"&gt;
    &lt;div class="ltag__twitter-tweet__header"&gt;
      &lt;img class="ltag__twitter-tweet__profile-image" src="https://res.cloudinary.com/practicaldev/image/fetch/s--8pi93x8t--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pbs.twimg.com/profile_images/791601013232992256/LoEtKJ5M_normal.jpg" alt="Olavi Haapala 🇫🇮 profile image"&gt;
      &lt;div class="ltag__twitter-tweet__full-name"&gt;
        Olavi Haapala 🇫🇮
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__username"&gt;
        @0lpeh
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__twitter-logo"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ir1kO05j--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-f95605061196010f91e64806688390eb1a4dbc9e913682e043eb8b1e06ca484f.svg" alt="twitter logo"&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__body"&gt;
      Working Hours Tracker getting closer to 2k downloads in Jolla store. Now at 1994 :) &lt;br&gt;&lt;a href="https://twitter.com/hashtag/jolla"&gt;#jolla&lt;/a&gt; &lt;a href="https://twitter.com/hashtag/sailfishos"&gt;#sailfishos&lt;/a&gt;
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__date"&gt;
      07:44 AM - 16 Apr 2016
    &lt;/div&gt;


    &lt;div class="ltag__twitter-tweet__actions"&gt;
      &lt;a href="https://twitter.com/intent/tweet?in_reply_to=721242967487463424" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fFnoeFxk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-reply-action-238fe0a37991706a6880ed13941c3efd6b371e4aefe288fe8e0db85250708bc4.svg" alt="Twitter reply action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/retweet?tweet_id=721242967487463424" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--k6dcrOn8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-retweet-action-632c83532a4e7de573c5c08dbb090ee18b348b13e2793175fea914827bc42046.svg" alt="Twitter retweet action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/like?tweet_id=721242967487463424" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SRQc9lOp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-like-action-1ea89f4b87c7d37465b0eb78d51fcb7fe6c03a089805d7ea014ba71365be5171.svg" alt="Twitter like action"&gt;
      &lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/blockquote&gt;


&lt;p&gt;In 2014, I was using a Jolla phone as my main driver and had the need for tracking my work hours in an easy way. I also wanted to try out developing mobile applications and this seemed like a great idea at that time. The simple features and UI is developed mostly for my initial needs, until I started receiving feature wishes and feedback from the users.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;In order to get started with the development and building the initial version, I was looking at some of the example applications and code snippets and took heavy inspiration from a few Sailfish OS applications that were openly available in GitHub.&lt;/p&gt;

&lt;p&gt;I built the whole application with a mindset of trying to build something without really knowing a lot about the platform or the best practices. I was happy when something seemed to work even though most of my solutions were not optimal. This was probably my first mistake, not figuring out how to properly design and implement a Sailfish OS application. But on the other hand, this was a great way to get something done and learn from your mistakes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2kczOCE5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://olavihaapala.fi/images/23-learn/devices.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2kczOCE5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://olavihaapala.fi/images/23-learn/devices.jpeg" alt="Working Hours Tracker running on multiple SailfishOS devices." width="880" height="495"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;p&gt;Here are a few selected lessons that I have learned while developing and maintaining this application for years. They are in no particular order and the list is not exhaustive.&lt;/p&gt;

&lt;h3&gt;
  
  
  Qt and QML
&lt;/h3&gt;

&lt;p&gt;Sailfish OS is based on Qt and the views are written in QML. Since I’m a web developer, I ended up doing most of my UI and features in QML, which is the equivalent of doing everything with JavaScript instead of doing the heavy lifting on the server side. In the Sailfish OS case that would mean Qt (C++) level. Correctly using C++ code that use signals to keep the UI up-to-date and to receive events, was something I only learned quite late in the project, when I took a closer look at the documentation and some more advanced example applications.&lt;/p&gt;

&lt;p&gt;The heavy use of QML for data processing and other stuff that should have been handled more effectively, lead in some cases to somewhat unresponsive UI. Mostly, it has been fine, but that might have been only because I haven’t been using the application with large amounts of data.&lt;/p&gt;

&lt;p&gt;Additionally, I ended up writing some of the data processing and grouping in JavaScript instead of doing that on the database level with SQL. This happened mostly because it happened slowly over time when adding new features and additionally, I might not have been aware of the powers and features of SQL.&lt;/p&gt;

&lt;h3&gt;
  
  
  Backwards Compatibility
&lt;/h3&gt;

&lt;p&gt;Backwards compatibility has been one the most annoying part of this project. I’ve had a goal to not break any of the earlier versions of the app, that someone might be upgrading from. This has led to some weird quirks and unnecessary migration checks that need to happen every time the application is started.&lt;/p&gt;

&lt;p&gt;Sometimes, an update I worked on ended up breaking the application for many users and I had to do some more database migrations in order to fix the database for those users who now had invalid data in the database due to an invalid migration script.&lt;/p&gt;

&lt;p&gt;In retrospective, I should probably not have cared so much about breaking the application for a few users at the cost of having to run unnecessary code for everyone every time the application is started. I think I could have earlier decided to release a v2 of the application that would not have been backwards compatible. Better database design from the start could have let to fewer migration needs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Database Design
&lt;/h3&gt;

&lt;p&gt;Good database design is important. This is probably one of the parts of the project that has made me want to forget about the backwards compatibility and start everything all over again and release it as v2.&lt;/p&gt;

&lt;p&gt;The database design basically did not exist at all. No foreign keys are used. Linking between hour markings and projects are done by looking for the project ID that is saved as a string on the hours row. Same goes for tasks inside projects and so on.&lt;/p&gt;

&lt;p&gt;And did I mention the format the 2014-version-me decided to save the hour markings in?&lt;/p&gt;

&lt;p&gt;The hours markings are saved in the following format (simplified):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Date as TEXT&lt;/li&gt;
&lt;li&gt;startTime as TEXT (HH:MM!)&lt;/li&gt;
&lt;li&gt;endTime as TEXT (HH:MM!)&lt;/li&gt;
&lt;li&gt;Duration as REAL&lt;/li&gt;
&lt;li&gt;Breakduration as REAL&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Why on earth did I want to save the startTime as a “HH:MM” string? You might realize what kind of issues this kind of crappy database design has led into. If I could go back in time I would change the database to at least save these as unix timestamps or datetimes.&lt;/p&gt;

&lt;p&gt;This in addition to custom home made code for handling durations and displaying those as “HH:MM” is also why I ended up having &lt;a href="https://github.com/olpeh/wht/issues/66"&gt;bugs like this reported by a user&lt;/a&gt;, displaying the time displayed as “150:60”.&lt;/p&gt;

&lt;h3&gt;
  
  
  Testing
&lt;/h3&gt;

&lt;p&gt;Automated testing of the application should have been thought of at an early stage of the project. Way too late in the project, after releasing a few extremely buggy versions to users, I tried adding some unit tests to the project, but failed to get them working and gave up. Right now there are no automated tests in this project.&lt;/p&gt;

&lt;p&gt;Additionally, as part of testing a version before releasing it, a plan or a script for the manual testing would have been useful. I usually only tested on a few different devices and only tested those features that I remembered to test. Quite often some features that I didn’t think were affected by the changes I was working on, got broken by a bugfix targeted at another feature.&lt;/p&gt;

&lt;h3&gt;
  
  
  Debugging
&lt;/h3&gt;

&lt;p&gt;Even if a good level of testing is done, your users will end up in some weird corner cases at some point of the time. You are lucky if you hear about these from some of the users. It means they care enough to contact you. One thing that was super useful in these scenarios was that I created a way for the users to easily send me the application logs as email to me.&lt;/p&gt;

&lt;h3&gt;
  
  
  Managing Releases and Release Notes
&lt;/h3&gt;

&lt;p&gt;Managing release notes and releases in different platforms etc. takes surprisingly much effort. This is something you might want to consider automating as much as possible.&lt;/p&gt;

&lt;p&gt;Current &lt;a href="https://github.com/olpeh/wht#releasing"&gt;release instructions for this project&lt;/a&gt; consist of a list of 15 steps to go through when releasing a new version, and the release notes need to be copied to at least a few places.&lt;/p&gt;

&lt;p&gt;Additionally, you might not want to list all of the features in the app in the documentation. I mean what’s the point? And especially, don’t do this in multiple places and services. Don’t do like I did.&lt;/p&gt;

&lt;h3&gt;
  
  
  Documentation
&lt;/h3&gt;

&lt;p&gt;This project has taught me the importance of good documentation. Even though it was mostly me alone working on this project, I would have benefitted from more documentation about the different aspects of the project.&lt;/p&gt;

&lt;p&gt;This has become especially clear now when the project has not been in active development, but instead, I have been working on some random bug fixes every few months or years. I have even ended up having a bit of discussion with myself via some TODO comments in the codebase as you can see from the screenshot below or check the &lt;a href="https://github.com/olpeh/wht/blob/c38418b1ef2be2bce96ee71fc7f580133a4e42b2/src/WorkTimer.cpp#L91-L93"&gt;lines in the codebase in GitHub with this link&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0ZJuRBCZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://olavihaapala.fi/images/23-learn/todo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0ZJuRBCZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://olavihaapala.fi/images/23-learn/todo.png" alt="Screenshot of the above linked code lines in GitHub, with the following contents: TODO: Why did I do it like this earlier? Plz refactor this Why did I comment like that?" width="880" height="241"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Something that I have learned is the importance of the following levels of documentation: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Readme, instructions how to get started etc. &lt;/li&gt;
&lt;li&gt;Testing instructions&lt;/li&gt;
&lt;li&gt;Release / deployment instructions, with enough detail &lt;/li&gt;
&lt;li&gt;Good commit messages &lt;/li&gt;
&lt;li&gt;Clear and good code comments where needed&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Managing Internationalization
&lt;/h3&gt;

&lt;p&gt;I wanted to provide my application in multiple languages. For this, I have been using &lt;a href="https://www.transifex.com/"&gt;Transifex&lt;/a&gt; which is a great tool for crowdsourced translations. I received surprisingly many contributions from the community in the Transifex project. The application is available in 10+ languages translated by more than 20 volunteers. A few weeks ago, someone requested to translate the app into Polish.&lt;/p&gt;

&lt;p&gt;However, handling the different languages adds to the maintenance overhead of the project and makes the release process slightly more complicated. This is something I should have automated but never did.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Importance of a Supportive Community
&lt;/h3&gt;

&lt;p&gt;For a platform to become successful, it is important to have an open and active community. I think the Sailfish OS developers community has been super helpful and there have been multiple people helping me a lot when I have had trouble with this project. Special shoutout to kimmoli and coderus for their help!&lt;/p&gt;

&lt;h3&gt;
  
  
  Refactoring
&lt;/h3&gt;

&lt;p&gt;Refactoring should happen in small phases and not in a big bang refactoring. I ended up working on a way too large refactoring that touched most of the codebase and I almost ended up giving up on it. The end result was a bit nicer codebase but with some newly introduced bugs. I never continued the refactorings further, so now the codebase is not as nice as I would like it to be.&lt;/p&gt;

&lt;p&gt;There is a lot of duplication in the codebase, so one option would be to start getting rid of those piece by piece and after that start transforming the codebase oto a more recommended way of building UI applications in Qt. However, I most probably never will do this. Instead I would probably start from scratch and build a new version that would be better designed both from the technical perspective and the UX and UI perspectives.&lt;/p&gt;

&lt;h3&gt;
  
  
  Don’t Start with Advanced Features Too Early
&lt;/h3&gt;

&lt;p&gt;I made the mistake of implementing some advanced features such as email export, database export, CSV export and import as well. This led into more complex maintenance since I had to try to keep this in mind when thinking about fixing something or adding more features. Everything needed now to be backwards compatible and I tried to make sure importing a database dump would work even across different versions of the application.&lt;/p&gt;

&lt;p&gt;To be honest I’m not even sure if this worked all the time, and I’m not sure if any of the users ever used these features. Lesson learned: focus on the core features.&lt;/p&gt;

&lt;h3&gt;
  
  
  App Development Is Different from the Web Development
&lt;/h3&gt;

&lt;p&gt;Web development is relatively easy in terms of updates. Most of the websites don’t utilize lots of local data on the users device. Of course some exceptions exist and LocalStorage is an example of local data stored on a user’s device that sometimes needs migration etc. Most websites, you can deploy a new version at any time, and your users will get the new version when they load the page again.&lt;/p&gt;

&lt;p&gt;In the world of mobile applications, you can’t just deploy a new version. What you can do is to submit a new version of the application to be reviewed. After approval, it will be available for your users to download from the app store if they wish to do so. Sailfish OS has no option for automatic updates and there is no way to force the users to upgrade.&lt;/p&gt;

&lt;p&gt;My application is working fully offline with a local only database. This is a good idea from the users perspective, but could easily become a hell to maintain. In some cases having the data stored on a central server could be considered as a nice benefit. Especially if some database migrations need to take place.&lt;/p&gt;

&lt;p&gt;On the web, pages may break easily due to changes in the backend API and some users may be keeping a tab open for months (true story BTW!). On the web, this is not considered critical, and the user is often told to try to refresh the page, close the browser and try again etc. On the native side however, an app crashing is usually considered more critical.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fun Facts about the Project
&lt;/h2&gt;

&lt;p&gt;To end this blog post, I’d like to share a couple of fun facts about this projec.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fun Fact 1
&lt;/h3&gt;

&lt;p&gt;Here’s a fun fact about how I built a solution for getting notifications for comments and reviews even though the platform did not have a support for this.&lt;/p&gt;

&lt;p&gt;When submitting an app to be reviewed and added to the Jolla Store, there is or at least used to be a critical feature missing in the process: &lt;strong&gt;notifications&lt;/strong&gt;. You would not get notified when your app got either approved or rejected. Additionally, you would not receive any notifications when users commented or rated your application in the store. The only way to notice that someone had commented or given a new review was to actually open the app store on a Sailfish OS device and check the comments there.&lt;/p&gt;

&lt;p&gt;However, if you log into &lt;a href="https://harbour.jolla.com/"&gt;harbour.jolla.com&lt;/a&gt; where the applications are submitted for review, you can see the amount of reviews and likes etc. for your own applications. At that time I did not realize there was an API also available for this data, so my solution was a bit more complicated than necessary.&lt;/p&gt;

&lt;h4&gt;
  
  
  My Solution for Notifications
&lt;/h4&gt;

&lt;p&gt;I had a Selenium script running on an interval of 15minutes. The script would&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Open &lt;a href="https://harbour.jolla.com/"&gt;harbour.jolla.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Log in&lt;/li&gt;
&lt;li&gt;Go to my applications&lt;/li&gt;
&lt;li&gt;Scrape the amount of reviews, likes etc. from the HTML&lt;/li&gt;
&lt;li&gt;Write those into a file&lt;/li&gt;
&lt;li&gt;Compare with previous values&lt;/li&gt;
&lt;li&gt;If they had changed, send a IRC message to myself with a text saying something like “Working Hours Tracker has received a new comment”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On my server where I was running my &lt;code&gt;irssi&lt;/code&gt; IRC instance, I had a perl script running that would send an XMPP notification to myself for all the private messages I received.&lt;/p&gt;

&lt;p&gt;On my mobile phone, I had an XMPP client installed, that would notify me of new XMPP message.&lt;/p&gt;

&lt;p&gt;When receiving a message, I would then see that I need to open the Jolla Store and check the comment from there&lt;/p&gt;

&lt;p&gt;My solution was complicated but it worked all the time, except from when it didn’t. Sometimes I needed to go kill some of the processes on the server after it started running out of memory because some Firefox processes were left running in the background for some reason. I never spent time on investigating why this happened, I simply killed those processes every once in a while.&lt;/p&gt;

&lt;p&gt;Additionally I extended this solution to save the amount of downloads, likes and active installations into a database and I was able to visualize those on the web page using Highcharts. The solution is not working anymore, but it was visible on the homepage for this project: &lt;a href="https://wht.olpe.fi/"&gt;wht.olpe.fi&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can see how it used to look like in the screenshot below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--o6Es_tuZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://olavihaapala.fi/images/23-learn/downloads.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--o6Es_tuZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://olavihaapala.fi/images/23-learn/downloads.png" alt="Screenshot of the download stats for working hours tracker" width="880" height="587"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Fun Fact 2
&lt;/h3&gt;

&lt;p&gt;Huge part of the users for my app seem to be from Germany for some reason. See this tweet about WHT download stats in OpenRepos:&lt;/p&gt;


&lt;blockquote class="ltag__twitter-tweet"&gt;
      &lt;div class="ltag__twitter-tweet__media"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--mX07B91e--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pbs.twimg.com/media/B7afI-lIgAAC8EN.png" alt="unknown tweet media content"&gt;
      &lt;/div&gt;

  &lt;div class="ltag__twitter-tweet__main"&gt;
    &lt;div class="ltag__twitter-tweet__header"&gt;
      &lt;img class="ltag__twitter-tweet__profile-image" src="https://res.cloudinary.com/practicaldev/image/fetch/s--8pi93x8t--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pbs.twimg.com/profile_images/791601013232992256/LoEtKJ5M_normal.jpg" alt="Olavi Haapala 🇫🇮 profile image"&gt;
      &lt;div class="ltag__twitter-tweet__full-name"&gt;
        Olavi Haapala 🇫🇮
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__username"&gt;
        @0lpeh
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__twitter-logo"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ir1kO05j--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-f95605061196010f91e64806688390eb1a4dbc9e913682e043eb8b1e06ca484f.svg" alt="twitter logo"&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__body"&gt;
      WHT download details from &lt;a href="https://twitter.com/hashtag/openrepos"&gt;#openrepos&lt;/a&gt; &lt;br&gt;Whats up with Germany? Almost as many as Finland...&lt;br&gt;&lt;a href="https://twitter.com/hashtag/jolla"&gt;#jolla&lt;/a&gt; 
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__date"&gt;
      19:19 PM - 15 Jan 2015
    &lt;/div&gt;


    &lt;div class="ltag__twitter-tweet__actions"&gt;
      &lt;a href="https://twitter.com/intent/tweet?in_reply_to=555806583113146368" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fFnoeFxk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-reply-action-238fe0a37991706a6880ed13941c3efd6b371e4aefe288fe8e0db85250708bc4.svg" alt="Twitter reply action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/retweet?tweet_id=555806583113146368" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--k6dcrOn8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-retweet-action-632c83532a4e7de573c5c08dbb090ee18b348b13e2793175fea914827bc42046.svg" alt="Twitter retweet action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/like?tweet_id=555806583113146368" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SRQc9lOp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-like-action-1ea89f4b87c7d37465b0eb78d51fcb7fe6c03a089805d7ea014ba71365be5171.svg" alt="Twitter like action"&gt;
      &lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/blockquote&gt;


&lt;h2&gt;
  
  
  The Most Important Lesson Learned
&lt;/h2&gt;

&lt;p&gt;I think the most important lesson this project has taught me is that you learn by doing. I think the fact that I can laugh at the code I wrote a few years ago is a good sign because it means I have learned a bunch of things and if I were to start a similar project now, I would make better decisions and make new kinds of mistakes – but not the same stupid mistakes again.&lt;/p&gt;

&lt;p&gt;This project has been supported by the Futurice Open Source sponsoring initiative: &lt;a href="https://spiceprogram.org/oss-sponsorship/"&gt;Spice Program&lt;/a&gt;. In fact, this project probably helped me land a job at &lt;a href="https://futurice.com/"&gt;Futurice&lt;/a&gt; in the first place.&lt;/p&gt;

&lt;p&gt;Cheers for reading the post. This ended up being my longest blog post so far and I still would have wanted to write more, but I guess It’s time to publish this.&lt;/p&gt;

</description>
      <category>learning</category>
      <category>sailfishos</category>
    </item>
    <item>
      <title>Do Everyone a Service by Writing Good Commit Messages</title>
      <dc:creator>Olavi Haapala</dc:creator>
      <pubDate>Tue, 16 Mar 2021 10:22:00 +0000</pubDate>
      <link>https://forem.com/olpeh/do-everyone-a-service-by-writing-good-commit-messages-50ah</link>
      <guid>https://forem.com/olpeh/do-everyone-a-service-by-writing-good-commit-messages-50ah</guid>
      <description>&lt;h2&gt;
  
  
  Author’s Notice
&lt;/h2&gt;

&lt;p&gt;Somehow, I feel like the importance of good commit messages can not be overstated and a good commit message is worth its bytes weight in gold. Writing good commit messages is also easy to forget or may feel like a waste of time, which it isn’t. That’s why I wanted to write this blog post.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Anatomy of Good Commit Messages
&lt;/h2&gt;

&lt;p&gt;This blog post does not aim to be a definite guide for &lt;strong&gt;how&lt;/strong&gt; to format your commit messages. In fact, that’s totally irrelevant in my opinion. I think it’s one of those conventions that does not matter. You can read my thoughts on that topic in my previous blog post:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag__link"&gt;
  &lt;a href="/futurice" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__org__pic"&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--AKji_bGW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/practicaldev/image/fetch/s--M0Ku-pMn--/c_fill%2Cf_auto%2Cfl_progressive%2Ch_150%2Cq_auto%2Cw_150/https://dev-to-uploads.s3.amazonaws.com/uploads/organization/profile_image/3021/41011d05-9976-4ed6-a82b-909914599c45.png" alt="Futurice" width="150" height="150"&gt;
      &lt;div class="ltag__link__user__pic"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--neA7X_Hf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/practicaldev/image/fetch/s--UQ_v8Zsl--/c_fill%2Cf_auto%2Cfl_progressive%2Ch_150%2Cq_auto%2Cw_150/https://dev-to-uploads.s3.amazonaws.com/uploads/user/profile_image/10689/02d2fdd9-72af-438e-b628-28a78b1d5d65.jpg" alt="" width="150" height="150"&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="/futurice/conventions-don-t-matter-what-matters-is-consistency-4lp2" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Conventions Don’t Matter – What Matters Is Consistency&lt;/h2&gt;
      &lt;h3&gt;Olavi Haapala for Futurice ・ Feb 2 '21 ・ 3 min read&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#conventions&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#teamwork&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#practises&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


&lt;p&gt;However, here are some of the aspects that make a good commit message. Taking these points to use will make you or your colleagues thank you later.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Why
&lt;/h3&gt;

&lt;p&gt;The most important thing to convey in a commit message in addition to what was changed is the &lt;strong&gt;why&lt;/strong&gt; something was done in a certain manner. Quite often I look at some code changes and try to wonder &lt;strong&gt;why&lt;/strong&gt; things were done like this even if &lt;code&gt;git blame&lt;/code&gt; would say that it was me who did the change. In these cases, simple commit messages like &lt;code&gt;Use LibraryA instead of LibraryB&lt;/code&gt; give no additional value. Instead of a better commit message in this case could have been something like this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Use LibraryA instead of LibraryB in order to avoid a bug in LibraryB&lt;/p&gt;

&lt;p&gt;There is a bug in LibraryB, that causes undefined behaviour in certain cases when the user clicks this button. Using LibraryA instead fixes the issue since the library does not use the problematic library “xyz” under the hood like LibraryB does&lt;/p&gt;

&lt;p&gt;For more information, check the release notes: xyz.github.io/releases/1.2.3&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;As you can see, this commit message tells the future you or anyone else bumping into the commit message the what and most importantly the &lt;strong&gt;why&lt;/strong&gt; at a quick glance.&lt;/p&gt;

&lt;h3&gt;
  
  
  Commit Messages Are Like Documentation
&lt;/h3&gt;

&lt;p&gt;Good and clear commit messages act like documentation. Writing documentation is always painful, right? But if you do it immediately when commiting your code changes, it’s not that big of an effort.&lt;/p&gt;

&lt;p&gt;In order to learn more about the importance of clear commit messages, you can watch this amazing talk by my colleague and friend, Juhis: &lt;a href="https://hamatti.org/talks/contemporary-documentation/"&gt;Contemporary Documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;However, don’t forget the actual documentation as well as adding clear comments in your code. I think that if a solution requires either a longer explanation in a code comment, it should also have a longer explanation in the commit message and vice versa.&lt;/p&gt;

&lt;h3&gt;
  
  
  Good Commit Messages Makes Bisecting Easy
&lt;/h3&gt;

&lt;p&gt;As I wrote in one of my previous blog posts about git bisect, &lt;code&gt;git bisect&lt;/code&gt; is a powerful tool for quickly finding problematic commits. In those cases a clear commit message helps as well. Read the blog post for more.&lt;/p&gt;

&lt;p&gt;You can read the blog post here:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag__link"&gt;
  &lt;a href="/olpeh" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--neA7X_Hf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/practicaldev/image/fetch/s--UQ_v8Zsl--/c_fill%2Cf_auto%2Cfl_progressive%2Ch_150%2Cq_auto%2Cw_150/https://dev-to-uploads.s3.amazonaws.com/uploads/user/profile_image/10689/02d2fdd9-72af-438e-b628-28a78b1d5d65.jpg" alt="olpeh"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="/olpeh/git-bisect-is-your-friend-in-need-87l" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Git Bisect Is Your Friend in Need&lt;/h2&gt;
      &lt;h3&gt;Olavi Haapala ・ Jan 25 '21 ・ 3 min read&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#git&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


&lt;h3&gt;
  
  
  Commits Should Only Contain Related Files and Changes
&lt;/h3&gt;

&lt;p&gt;I like to think of commit messages like labels that are added to certain lines of code forever. The latest labels are often easily visible in editors and when using &lt;code&gt;git blame&lt;/code&gt; or even in UIs like in GitHub.&lt;/p&gt;

&lt;p&gt;This is why it is important to keep in mind that a commit should only contain changes that are related to the actual change described by the commit message. It feels weird to have some files changed with a commit message that has nothing to do with that particular file.&lt;/p&gt;

&lt;p&gt;Of course, this could happen by accident as well, if using &lt;code&gt;git add .&lt;/code&gt; or similar solution to include all the changes. I think it is important to always inspect what is going to be added and usue chunks. This is extremely easy with a graphical user interface for git. I’m personally using &lt;a href="https://www.gitkraken.com/"&gt;GitKraken&lt;/a&gt; and I’m happy with it.&lt;/p&gt;

&lt;p&gt;Git blame, despite its negatively loaded name is a useful tool for inspecting the history of a file and you should be able to see &lt;strong&gt;why&lt;/strong&gt; certain lines have been changed if good commit message practices have been in use. The &lt;strong&gt;why&lt;/strong&gt; is the most important part, and even in projects where you are the only developer, after some time, you’ll have no clue why something was done in a certain manner. Even more so, if someone else is looking at your code and commits.&lt;/p&gt;

&lt;h3&gt;
  
  
  Commit Messages Are Not For Your PR
&lt;/h3&gt;

&lt;p&gt;Commit messages are not supposed to be used to communicate something that happens in your pull request. In fact, PRs have nothing to do with &lt;code&gt;git&lt;/code&gt; itself, but rather something that tools like GitHub and others have come up with and added to their set of tools. Some people might even wonder what’s the difference between git and GitHub.&lt;/p&gt;

&lt;p&gt;A large pull request should be split into smaller, meaningful commits with clear commit messages. This makes the reviewing easier and faster. However, try to keep the PRs small and avoid huge long lasting PRs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Something That’s Obvious Now Is Not Obvious Later
&lt;/h3&gt;

&lt;p&gt;When working on some features in a codebase, you have quite a lot of implicit context and knowledge in your head. This is something that may be obvious while working on it, but later on it probably isn’t obvious anymore. When coming back to some piece of code after a while, you’ll want to see clear and informative commit messages.&lt;/p&gt;

&lt;h3&gt;
  
  
  Commit Messages Can Be Used for Announcements
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://twitter.com/jevakallio/status/1366317647965618177"&gt;Jani Eväkallio&lt;/a&gt; recently published an interesting open source tool called &lt;a href="https://github.com/jevakallio/git-notify"&gt;git-notify&lt;/a&gt;, which makes it possible to use git commit messages for announcing some important changes to your team members or even your future self.&lt;/p&gt;

&lt;p&gt;Check it out at: &lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--566lAguM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/jevakallio"&gt;
        jevakallio
      &lt;/a&gt; / &lt;a href="https://github.com/jevakallio/git-notify"&gt;
        git-notify
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      🙉 📣 Communicate important updates to your team via git commit messages
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;I think this is a brilliant idea and may save a lot of frustration and time in larger development teams.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sometimes You Don’t Care and That’s Okay
&lt;/h2&gt;

&lt;p&gt;Sometimes it’s okay to not care. Such cases include doing a hobby project in your free time and you are the only developer ever going to touch that codebase.&lt;/p&gt;

&lt;p&gt;However, you never know what happens and suddenly you might not be the only person contributing to the codebase anymore. This is when good commit messages may save a lot of time and frustration by acting like a good documentation. Additionally, when the time passes and you are not actively working on the codebase, you most likely will forget some aspects of it. This is when your own commit messages turn to be helpful for you when you are trying to look at a piece of code and you have no recollection of &lt;strong&gt;why&lt;/strong&gt; things were done in a certain way in the past.&lt;/p&gt;

</description>
      <category>git</category>
      <category>conventions</category>
      <category>practices</category>
    </item>
    <item>
      <title>The Rules of React Hooks - And How We Messed up</title>
      <dc:creator>Olavi Haapala</dc:creator>
      <pubDate>Mon, 15 Mar 2021 10:22:25 +0000</pubDate>
      <link>https://forem.com/futurice/the-rules-of-react-hooks-and-how-we-messed-up-4015</link>
      <guid>https://forem.com/futurice/the-rules-of-react-hooks-and-how-we-messed-up-4015</guid>
      <description>&lt;p&gt;React Hooks have quickly become the recommended way to handle component local state and side effects in React function components. Getting started with hooks is quite straightforward, but you might need to change the way you think about your components, especially when it comes to the useEffect hook.&lt;/p&gt;

&lt;p&gt;This blog assumes you know the basics of React Hooks – if you don’t &lt;a href="https://reactjs.org/docs/hooks-intro.html"&gt;you can find out more here&lt;/a&gt; – and will dive a bit deeper into how they should be used. I’ll also be sharing a bit about the mistakes we made and how it took us nearly a month to fix the mess.&lt;/p&gt;

&lt;h3&gt;
  
  
  React hooks - easy to learn, hard to master
&lt;/h3&gt;

&lt;p&gt;React Hooks were launched in React version 16.8 and they have quickly become a popular way to handle components, local states, and component side effects, among other things. They’re quite easy to get started with, but they're challenging to master properly – you have to learn to think a bit differently compared to React’s traditional class components and lifecycle hooks, and there are certain rules that you have to follow. &lt;/p&gt;

&lt;h3&gt;
  
  
  Some examples of hooks and how to use them
&lt;/h3&gt;

&lt;p&gt;The simplest hook is the useState hook, which takes as an argument the initial state. useState is a function that returns an array with two items in it: the first is the actual state and the second is a function that sets the state. Another of the built-in hooks is useEffect, which is for running side effects in your React function components. For example, if you have a shopping cart with a button to add a banana, when a banana is added you might want the document title to be updated as a side effect. With useEffects, you define the dependencies – you can think of it like defining the array and how often you want to run the function. If you leave it as an empty array, it will only run once, after the initial render; otherwise, it will run after every render of the function, unless you define the dependencies. So, when the state changes, React just calls this function again. And from a useEffect function, you can return a cleanup function. &lt;/p&gt;

&lt;p&gt;To understand the useEffect cleanup, try this analogy from &lt;a href="https://twitter.com/ryanflorence"&gt;Ryan Florence&lt;/a&gt;. Imagine you have only one bowl in your house to eat cereal from. You wake up in the morning and eat cereal whether you're hungry or not – that's the initial render. Time passes, the state changes, and you become hungry again. Now you need to clean the bowl because it's dirty from when you ate earlier. You clean it up first and then you eat again – this is the same as React running a cleanup before running the effect again, which is also why when a component is unmounted it runs the cleanup when it's removed. &lt;/p&gt;

&lt;h3&gt;
  
  
  Easy mistakes to make with React hooks
&lt;/h3&gt;

&lt;p&gt;I’ve just mentioned two of the most important hooks, but let's talk a bit about typical mistakes with hooks. The first mistake you might make when you start using useEffect is you might forget to add the dependency array, meaning your effect will run on every render. Why is this a problem? Imagine you're doing a fetch in your useEffect. This would happen on every render, causing a new render because something was changing the state of the component. This would make it render again, causing an infinite loop. Another typical mistake you can make when you start refactoring useEffects is having a useEffect that depends on the state that is saved inside it. This causes another infinite loop, but you can solve it by doing functional state updates instead of traditional useState calls. &lt;/p&gt;

&lt;h3&gt;
  
  
  Rules to follow – and what happens when you don’t
&lt;/h3&gt;

&lt;p&gt;The simplest rule is that hooks must start with &lt;strong&gt;“use”&lt;/strong&gt; – I think React will even warn you if you try to do something that doesn't start with use. Next, call hooks should only be used at the top level of your function components, so you can't nest them in statements. This is because React only relies on the order of the hook calls, so for every render you should call the same number of hooks so that React knows which hook is which. Finally, you can only call hooks from React functions. This should probably be self-explanatory, but when I started using hooks, I wanted to use them in some utility functions, and I realized quickly it just isn't possible. ESLint is very useful to check these rules. There are two plugins that I can recommend: &lt;strong&gt;react-hooks/rules-of-hooks&lt;/strong&gt; and &lt;strong&gt;react-hooks/exhaustive-deps&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So where did we go wrong? In the beginning of a project, we used TSLint instead of ESLint, because at that point TSLint wasn't deprecated yet, so we thought it would be fine. We had the React Hooks plugin installed and enabled, but for some reason we forgot to enable the React Hooks rules, so TSLint wasn’t actually checking the rules. We had it there for months and didn't notice, and because we didn't know the rules well enough, we didn't notice that our code was piling up into a huge mess.&lt;/p&gt;

&lt;p&gt;At that point we changed from TSLint to ESLint, which was already a big refactoring PR because we also made our rules stricter. At first we had the exhaustive deps rule disabled after the refactoring, as well as one huge component where we had to add the ESLint “disable React’s rules of hooks” line, because the file was just too large to be fixed in that PR. And then I started fixing this mess and enabled the exhaustive deps rule and decided to just do what ESLint tells us. I thought it would take me a couple of days, it ended up taking more than a month to fix just the exhaustive-deps violations, including causing some regressions in production. &lt;/p&gt;

&lt;h3&gt;
  
  
  Lessons learned with React
&lt;/h3&gt;

&lt;p&gt;The most important thing we learned was to keep it simple, in both your React code base and in hooks. Even though you can make huge effects, it's better to split them into multiple effects – and if this makes your component code looks ugly, you can abstract it away into a custom hook. Secondly, you should always enable ESLint rules and enforce them, and it’s best to have ESLint in your editor. At this point I’d also like to recommend &lt;strong&gt;&lt;a href="https://github.com/phenomnomnominal/betterer"&gt;Betterer&lt;/a&gt;&lt;/strong&gt; – a cool tool that can be used in legacy projects and in larger, ongoing projects to stop you from making the project worse over time. You add tests that make sure you stop doing the wrong things and forces you to do better in future. This is handy when you don’t have time, energy or resources for these kinds of huge refactoring PRs. &lt;/p&gt;

&lt;p&gt;I also learned that custom hooks are quite cool. They are a really useful way to share code and logic between components. And during this refactoring I've learned when to use useReducer and when to use useState. useState is fine, but if you have more than, say, three useStates and you need to change a few of them at the same time but they're relying on each other, then it's better to use useReducer with one state object and then dispatch actions that update the state.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where to learn more about React and React hooks
&lt;/h3&gt;

&lt;p&gt;If you want to learn more about hooks and the rules of hooks, React’s official docs are amazing – they explain the rules and why you have to follow them. If I had read them to start with I wouldn’t have made the mistakes I did! I’d also recommend taking a look at Dan Abramov’s blog, &lt;a href="https://overreacted.io/"&gt;overreacted.io&lt;/a&gt;. &lt;a href="https://overreacted.io/a-complete-guide-to-useeffect/"&gt;A complete guide to useEffect&lt;/a&gt; is interesting, as is &lt;a href="https://overreacted.io/react-as-a-ui-runtime/"&gt;React as a UI Runtime&lt;/a&gt;, and &lt;a href="https://overreacted.io/how-are-function-components-different-from-classes/"&gt;how are function components different from classes&lt;/a&gt; will teach you some important differences. &lt;/p&gt;

&lt;p&gt;This blog was based on one of my &lt;a href="https://www.youtube.com/playlist?list=PL4su8m9nd245Q0PgsJhrbU65BmdhayFKQ"&gt;Tech Weeklies&lt;/a&gt; talks. You can listen to the &lt;a href="https://www.youtube.com/watch?v=VHvqnFU8dPA"&gt;full episode here&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>react</category>
      <category>javascript</category>
      <category>hooks</category>
    </item>
    <item>
      <title>You Will Never Be Done with Accessibility</title>
      <dc:creator>Olavi Haapala</dc:creator>
      <pubDate>Tue, 09 Mar 2021 09:55:00 +0000</pubDate>
      <link>https://forem.com/olpeh/you-will-never-be-done-with-accessibility-4ci6</link>
      <guid>https://forem.com/olpeh/you-will-never-be-done-with-accessibility-4ci6</guid>
      <description>&lt;p&gt;Making a website accessible is challenging and requires a big effort from the whole team including developers and designers. Ideally, accessibility should be considered already at the very early stages of a project instead of trying to “fix” it afterwards when in fact it’s already too late.&lt;/p&gt;

&lt;p&gt;Accessibility is not a simple binary feature of a website either. You can’t say that one website is accessible and another one is not. It’s actually all about &lt;strong&gt;how&lt;/strong&gt; well and easily accessible the website is.&lt;/p&gt;

&lt;p&gt;Creating accessible websites may sound like a straightforward engineering task, but it requires a lot of attention to details and knowledge about how the web works and how different users browse the web. However accessibility is not rocket science, if you take it into consideration all the way from the very beginning of the project including the design and concepting phase of the project.&lt;/p&gt;

&lt;p&gt;Additionally, you can get quite far by using semantic HTML and native input elements (don’t forget labels!). They are mostly designed to be accessible to everyone. However, this blog post is not about &lt;strong&gt;how&lt;/strong&gt; to create an accessible website.&lt;/p&gt;

&lt;h2&gt;
  
  
  Accessibility Is Never Done
&lt;/h2&gt;

&lt;p&gt;You should never think that you are done with accessibility. Saying your site is accessible and you are done with accessibility is like saying that cutting the grass is done. It will soon need to be cut again.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3DS-iPcc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nenxkqybm0swq47acbao.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3DS-iPcc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nenxkqybm0swq47acbao.jpg" alt="Moist grass in the sunset" width="880" height="587"&gt;&lt;/a&gt;&lt;br&gt;
Photo by &lt;a href="https://unsplash.com/@jweckschmied?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Jonas Weckschmied&lt;/a&gt; on &lt;a href="https://unsplash.com/s/photos/grass?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There will always be some parts of the project that could still be improved, some technologies might evolve and the conventions and guidelines may change over time. The dependencies of your project may introduce new bugs and accessibility violations and you yourself or anyone else on the team may accidentally make the accessibility worse. Projects are evolving over time and you should keep a close eye on accessibility in order to not make it worse.&lt;/p&gt;

&lt;p&gt;The same goes for web performance. In this sense, web accessibility and web performance go quite nicely hand-in-hand. Making a website perform well and fast means it becomes more accessible to people on low-end devices or slow networks either temporarily or permanently. Similarly to accessibility, web performance should also be monitored and improved over time in order to not make it worse.&lt;/p&gt;
&lt;h2&gt;
  
  
  Automating Is Not a Silver Bullet
&lt;/h2&gt;

&lt;p&gt;So, if web accessibility and performance are important and you should be keeping an eye on them over time, surely there is a way to automate everything, right?&lt;/p&gt;

&lt;p&gt;However, there is only so much you can do with automation and automatic testing for accessibility violations. Some of the issues can only be solved by manually testing and sometimes only by interviewing your end users that use your service and have some disabilities.&lt;/p&gt;
&lt;h3&gt;
  
  
  Learn More
&lt;/h3&gt;

&lt;p&gt;If you want to read more about automatic accessibility testing and its limitations, read this blog post by my colleague, &lt;a href="https://twitter.com/EevisPanula"&gt;Eevis&lt;/a&gt; &lt;/p&gt;


&lt;div class="ltag__link"&gt;
  &lt;a href="/eevajonnapanula" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--boyAh242--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/practicaldev/image/fetch/s--LfxV3vKg--/c_fill%2Cf_auto%2Cfl_progressive%2Ch_150%2Cq_auto%2Cw_150/https://dev-to-uploads.s3.amazonaws.com/uploads/user/profile_image/201004/f567403b-648c-4922-996e-a95132d48cbe.jpg" alt="eevajonnapanula"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="/eevajonnapanula/automated-accessibility-testing-is-a-good-start-but-you-need-to-test-manually-too-13f2" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Automated Accessibility Testing Is a Good Start - But You Need To Test Manually Too&lt;/h2&gt;
      &lt;h3&gt;Eevis (she/her) ・ Feb 14 '21 ・ 7 min read&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#a11y&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#webdev&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#react&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#testing&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


</description>
      <category>a11y</category>
    </item>
    <item>
      <title>JavaScript Should Be Your Last Resort</title>
      <dc:creator>Olavi Haapala</dc:creator>
      <pubDate>Tue, 02 Mar 2021 08:36:00 +0000</pubDate>
      <link>https://forem.com/olpeh/javascript-should-be-your-last-resort-5dje</link>
      <guid>https://forem.com/olpeh/javascript-should-be-your-last-resort-5dje</guid>
      <description>&lt;h2&gt;
  
  
  JS Is Your Hammer
&lt;/h2&gt;

&lt;p&gt;When working on modern frontend web development, using your favorite framework of choice, it can be sometimes tempting to solve all the problems with JavaScript. Sometimes this happens unconsciously as JS is what you mostly use in your day-to-day development work.&lt;/p&gt;

&lt;p&gt;This is similar to the situation described by Abraham Maslow in 1966:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I suppose it is tempting, if the only tool you have is a hammer, to treat everything as if it were a nail.&lt;/p&gt;

&lt;p&gt;– &lt;a href="https://en.wikipedia.org/wiki/Law_of_the_instrument" rel="noopener noreferrer"&gt;Wikipedia: Law of the instrument&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd86iyhq2qmc8t2eaqw1p.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd86iyhq2qmc8t2eaqw1p.jpg" alt="Photo of a hammer about to hit some nails"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;span&gt;Photo by &lt;a href="https://unsplash.com/@faustomarques?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Fausto Marqués&lt;/a&gt; on &lt;a href="https://unsplash.com/s/photos/hammer-fausto-marques?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; In this blog post, I’m only talking about JS even though I’m mostly using TS in my projects – it ends up as JS after compilation anyways.&lt;/p&gt;

&lt;h3&gt;
  
  
  What to Take into Account When Implementing UI
&lt;/h3&gt;

&lt;p&gt;This mindset of using JS for everything causes unnecessary processing that needs to be run on your end users’ devices as client-side JS. All the JS resources on a website need to be downloaded, parsed and executed by the web browser. This is quite often the cause of slow and unresponsive websites on low-end mobile devices or slow network speeds.&lt;/p&gt;

&lt;h4&gt;
  
  
  How You Should Be Thinking Instead:
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;Can this be done in HTML?&lt;/li&gt;
&lt;li&gt;If not, can I solve it with HTML + CSS?&lt;/li&gt;
&lt;li&gt;And if nothing else works, the solution probably requires a minimal amount of JS in addition to HTML and CSS&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This way of thinking is not about what is easiest for you as a developer. You may be a JavaScript focused frontend developer, and solving most of your problems with it feels natural for you. However, you should be thinking about your end users. Client-side JS is the single biggest problem when it comes to web performance. You can read some of my thoughts on web performance from my other blog posts. You can find some links at &lt;a href="https://olavihaapala.fi/2021/03/02/js-should-be-your-last-resort.html#also-read-these" rel="noopener noreferrer"&gt;the bottom of this blog post on my personal blog&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Can This Be Done in HTML?
&lt;/h3&gt;

&lt;p&gt;Plan and implement the basic structure and semantics of the feature in plain HTML without any extra styles and using native HTML elements and functionality. If some additional styling or features are needed, go to step 2.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Try to Solve It with HTML + CSS
&lt;/h3&gt;

&lt;p&gt;Use CSS to apply the additional styling or animation that is required, still keeping the semantics and accessibility in my mind. If some additional interactivity is required in the particular piece of UI you are building, go to step 3.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Use HTML + CSS + JS
&lt;/h3&gt;

&lt;p&gt;Add the minimum amount of JS required to fulfill the requirements. Keep in mind that something that can be solved without JS should probably be solved without JS.&lt;/p&gt;

&lt;p&gt;When you’re done, show your code to your colleagues and let them review it. Perhaps there is still something unnecessary parts in your code, that could be solved without having a client-side JS cost for your users.&lt;/p&gt;

&lt;h2&gt;
  
  
  Simple Example
&lt;/h2&gt;

&lt;p&gt;This problem applies to almost anything in web frontend development, but here is a simple practical example that should help me prove my point.&lt;/p&gt;

&lt;p&gt;Imagine you are working on a React project, and you are working on a feature that has some UI parts that should only become visible after a certain delay, let’s say after 2s.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using React Hooks
&lt;/h3&gt;

&lt;p&gt;If you are used to solving your problems with React and Hooks, your initial solution could look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;thingVisible&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setThingVisible&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;timeoutId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;setThingVisible&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;cleanup&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;clearTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timeoutId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;thingVisible&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;section&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Here&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;s a thing for you!&amp;lt;/section&amp;gt; : null;

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

&lt;/div&gt;



&lt;p&gt;This is fine and works. Probably you notice no difference in performance either on your hyper powerful developer machine. And probably, there is no real performance issue in this case. But imagine if these pile up and suddenly you would have tens or hundreds of similar unnecessary JS computations to be run on the client-side or some larger and longer executions that are taking place.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using HTML + CSS Animation
&lt;/h3&gt;

&lt;p&gt;Using CSS, you can animate content to appear on the page with a delay using CSS animations and &lt;code&gt;animation-delay&lt;/code&gt;. This is supported by all browsers and could even have a better end user experience as you could fade the content in or use any other ways of making the content appear more smoothly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The HTML:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;section&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"fade-in"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Here's a thing for you!&lt;span class="nt"&gt;&amp;lt;/section&amp;gt;&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The CSS:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.fade-in&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;animation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;fadeIn&lt;/span&gt; &lt;span class="m"&gt;2s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;animation-delay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;animation-fill-mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;forwards&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;@keyframes&lt;/span&gt; &lt;span class="n"&gt;fadeIn&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nt"&gt;from&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nt"&gt;to&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Don’t Use CSS for What You Can Do with HTML
&lt;/h2&gt;

&lt;p&gt;Similarly, don’t do something with CSS that you could and should be doing in HTML.&lt;/p&gt;

&lt;p&gt;An extreme example of this was that we had accidentally been using margins to separate two words from each other, instead of using a space in between the words!&lt;/p&gt;

&lt;p&gt;This was obviously not a good idea from at least the following perspectives:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It might not follow the font size, letter spacing etc.&lt;/li&gt;
&lt;li&gt;It is not needed, waste of effort and processing&lt;/li&gt;
&lt;li&gt;If someone would need to copy the text, there would be no space in between the words&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Frontend Development Is Not Easy
&lt;/h2&gt;

&lt;p&gt;Web frontend development is not an easy topic to master. It is something you can get started with quite rapidly, but mastering it requires some level of experience and understanding the whole picture in order to be able to solve the right problems on the right level using the right tools. Solving something on the frontend has many levels and details baked in it.&lt;/p&gt;

&lt;p&gt;Additionally, you’ll need to understand when a problem should be solved on the backend instead of the frontend for various reasons such as performance, usability or maintainability among others.&lt;/p&gt;

&lt;p&gt;However, keep in mind that sometimes you don’t need to try to reach for a perfect solution and something that works might be good enough to be shipped to production and to be used by your end users.&lt;/p&gt;

</description>
      <category>performance</category>
      <category>html</category>
      <category>css</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Modern and Accessible Marquee Clone with TailwindCSS</title>
      <dc:creator>Olavi Haapala</dc:creator>
      <pubDate>Tue, 23 Feb 2021 09:20:00 +0000</pubDate>
      <link>https://forem.com/olpeh/modern-and-accessible-marquee-clone-with-tailwindcss-5abb</link>
      <guid>https://forem.com/olpeh/modern-and-accessible-marquee-clone-with-tailwindcss-5abb</guid>
      <description>&lt;h2&gt;
  
  
  What Even Is/Was marquee?
&lt;/h2&gt;

&lt;p&gt;Marquee is a deprecated HTML element that renders scrolling content. For more information, check &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/marquee"&gt;the MDN page for marquee for more details&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It is not recommended to use the &lt;code&gt;&amp;lt;marquee&amp;gt;&lt;/code&gt; element even though it might still work and additionally in modern days, you rarely need anything like this. However, sometimes you do and you might need a solution to this using modern web technologies.&lt;/p&gt;

&lt;p&gt;Example &lt;code&gt;&amp;lt;marquee&amp;gt;&lt;/code&gt; if your browser still supports it:&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
Here's an example for you, that should render this text as an element with scrolling text if your browser still supports these.&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;If you see nothing above here, it means dev.to is understandably escaping the content and rendering HTML tags from user input is not supported. You can check &lt;a href="https://olavihaapala.fi/2021/02/23/modern-marquee.html"&gt;this post on my personal blog&lt;/a&gt; in order to check how the &lt;code&gt;&amp;lt;marquee&amp;gt;&lt;/code&gt; would work on your browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problems with Marquee
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Not very user friendly – many people do not like scrolling text.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Deprecated and not matching the modern web design.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Stressful for the user who might miss some of the content and has to wait for the scrolling to start from the beginning.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Not respecting user’s preference for reduced motion. Many people will feel physically ill or sick when a website has (too many) moving elements on it.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  DO NOT USE JS FOR THIS
&lt;/h2&gt;

&lt;p&gt;When implementing a &lt;code&gt;&amp;lt;marquee&amp;gt;&lt;/code&gt;-like element using modern technologies, please bare in mind that you (probably) won’t need JS for the job outside of maybe using something like React to render the element.&lt;/p&gt;

&lt;p&gt;My example implementation is using React in a Next.js project, but I chose not to use JS to achieve the wanted result. It is often better to try to achieve as much as possible without JS whenever feasible. JS is the root of all performance issues and especially when used for something that can be achieved using HTML and CSS only. I will probably write another blog post about this topic at a later point in time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Marquee Using CSS Only
&lt;/h2&gt;

&lt;p&gt;To achieve a rolling text effect, the only thing we need is some HTML and CSS. The effect can be achieved with CSS animations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Basic Solution With HTML + CSS
&lt;/h3&gt;

&lt;p&gt;In order to animate the text and get the feeling that the text “wraps around” and goes on infinitely, we need to add the elements twice to the DOM and animate them starting from different positions. First element will start from &lt;code&gt;0&lt;/code&gt; and go all the way to &lt;code&gt;-100%&lt;/code&gt; and the other one will start from &lt;code&gt;100%&lt;/code&gt; and go to &lt;code&gt;0&lt;/code&gt;. If we animate both elements with the same duration, the first element will be reset back to the initial position when the second element reaches the starting point, thus the user won’t notice when the animation starts over again.&lt;/p&gt;

&lt;p&gt;Here is a basic example snippet to achieve a &lt;code&gt;&amp;lt;marquee&amp;gt;&lt;/code&gt; like effect using HTML and CSS:&lt;/p&gt;

&lt;h4&gt;
  
  
  HTML
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;article&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"wrapper"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;ul&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"marquee"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;Item 0&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;Item 1&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;Item 2&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;Item 3&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;Item 4&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;Item 5&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;ul&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"marquee2"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;Item 0&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;Item 1&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;Item 2&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;Item 3&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;Item 4&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;Item 5&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/article&amp;gt;&lt;/span&gt;

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

&lt;/div&gt;



&lt;h4&gt;
  
  
  CSS
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;article&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="no"&gt;red&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;overflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;white-space&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;nowrap&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;350px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.wrapper&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;relative&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.marquee&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;animation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;marquee&lt;/span&gt; &lt;span class="m"&gt;5s&lt;/span&gt; &lt;span class="n"&gt;linear&lt;/span&gt; &lt;span class="n"&gt;infinite&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.marquee2&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;animation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;marquee2&lt;/span&gt; &lt;span class="m"&gt;5s&lt;/span&gt; &lt;span class="n"&gt;linear&lt;/span&gt; &lt;span class="n"&gt;infinite&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;ul&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;list-style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;padding-left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;li&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;@keyframes&lt;/span&gt; &lt;span class="n"&gt;marquee&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nt"&gt;from&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;translateX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nt"&gt;to&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;translateX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;-100%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;@keyframes&lt;/span&gt; &lt;span class="n"&gt;marquee2&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nt"&gt;from&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;translateX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nt"&gt;to&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;translateX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;And here’s a link to a codepen with the above content and a functioning version of it:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://codepen.io/olavih/pen/rNWGPda"&gt;Codepen.io basic marquee example&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Using TailwindCSS
&lt;/h3&gt;

&lt;p&gt;Tailwind config here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;animation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;marquee&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;marquee 30s linear infinite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;marquee2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;marquee2 30s linear infinite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nx"&gt;keyframes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;marquee&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0%&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;translateX(0%)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;100%&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;translateX(-100%)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nx"&gt;marquee2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0%&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;translateX(100%)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;100%&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;translateX(0%)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;And now we can use these animations using the classes that Tailwind generates for us:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;article&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"flex whitespace-no-wrap overflow-x-hidden"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"relative"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;ul&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"flex animate:marquee"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"m-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Item 0&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"m-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Item 1&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"m-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Item 2&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"m-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Item 3&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"m-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Item 4&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"m-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Item 5&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;ul&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"flex absolute top-0 animate:marquee2"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"m-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Item 0&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"m-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Item 1&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"m-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Item 2&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"m-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Item 3&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"m-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Item 4&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"m-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Item 5&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/article&amp;gt;&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;And here’s a link to a codepen with the above content and a functioning version of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Accessibility Aspects
&lt;/h2&gt;

&lt;p&gt;Having animations and motion on a webpage can make some people feel physically ill or sick. That’s why it is important that we as developers provide the users a way to opt-in to reduced motion. Many operating systems provide on option on the system level to set a &lt;code&gt;prefers reduced motion&lt;/code&gt; setting, and you as a web developer can use that for reducing the motion using the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion"&gt;prefers-reduced-motion&lt;/a&gt; media query.&lt;/p&gt;

&lt;p&gt;Tailwind has these built-in as utility classes called &lt;code&gt;motion-safe&lt;/code&gt; and &lt;code&gt;motion-reduce&lt;/code&gt; which we can use to make the &lt;code&gt;&amp;lt;marquee&amp;gt;&lt;/code&gt; clone accessible and user friendly. In order to use these, make sure you add this to your Tailwind config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;variants&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;animation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;motion-safe&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;motion-reduce&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Then we can enable the animation only if the user has not preferred reduced motion by using the class &lt;code&gt;motion-safe:animate-marquee&lt;/code&gt; and &lt;code&gt;motion-safe:overflow-x-hidden&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- As a fallback we show scrollbar but hide overflow and animate it in case:
    - The user has not preferred reduced motion
    - The user's browser suppports prefers-reduced-motion media query 
 --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;article&lt;/span&gt;
  &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"flex whitespace-no-wrap overflow-x-scroll motion-safe:overflow-x-hidden"&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"relative"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;ul&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"flex motion-safe:animate:marquee"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"m-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Item 0&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"m-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Item 1&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"m-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Item 2&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"m-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Item 3&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"m-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Item 4&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"m-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Item 5&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;ul&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"flex absolute top-0 motion-safe:animate:marquee2"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"m-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Item 0&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"m-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Item 1&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"m-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Item 2&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"m-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Item 3&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"m-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Item 4&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"m-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Item 5&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/article&amp;gt;&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;As another usability and accessibility improvement, we might want to pause the animation on hover by setting the &lt;code&gt;animation-state&lt;/code&gt; to &lt;code&gt;paused&lt;/code&gt;. In this case we use a class name to indicate clearly that when hovering over it, we pause the animation on any of its children. Why do we need to do this? Because if we would only pause the animation when hovering over the element that has either &lt;code&gt;marquee&lt;/code&gt; or &lt;code&gt;marquee2&lt;/code&gt; animations, the other list, which at that point of time would be non visible, would continue animating and soon appear on the element. This would not be a nice experience as the texts would overlap.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.pause-animations-on-children-on-hover&lt;/span&gt;&lt;span class="nd"&gt;:hover&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;animation-play-state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;paused&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Learn More
&lt;/h3&gt;

&lt;p&gt;Here’s nice blog post on this topic by Josh Comeau:&lt;a href="https://www.joshwcomeau.com/react/prefers-reduced-motion/"&gt;Accessible Animations in React&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Optimization
&lt;/h2&gt;

&lt;p&gt;In our case we wanted to start the animation only after receiving data from an API. In this case we could use the &lt;code&gt;will-change&lt;/code&gt; attribute to hint the browser that this element will be animated.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.will-change-transform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;will-change&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/will-change"&gt;See the MDN page for more information&lt;/a&gt; and please note the huge note there.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; will-change is intended to be used as a last resort, in order to try to deal with existing performance problems. It should not be used to anticipate performance problems.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Based on this note, I think I should actually not have used this, but instead measure the performance and only improve it if there are some performance problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fun Fact
&lt;/h2&gt;

&lt;p&gt;Even though &lt;code&gt;&amp;lt;marquee&amp;gt;&lt;/code&gt; is deprecated, it’s really not gone, the functionality is currently available in most web browsers but it’s not recommended to use it as it can be removed any time in the future without a warning. See &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/marquee"&gt;the MDN page for marquee for more details&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;When I started drafting this blog post and used &lt;code&gt;&amp;lt;marquee&amp;gt;&lt;/code&gt; in the &lt;code&gt;title&lt;/code&gt; and &lt;code&gt;excerpt&lt;/code&gt; of this post, the page contents started scrolling to left and I had to fix this by escaping the &lt;code&gt;title&lt;/code&gt; and &lt;code&gt;excerpt&lt;/code&gt; before rendering them as HTML.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--68kjNRaG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://olavihaapala.fi/images/19-marquee/marquee.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--68kjNRaG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://olavihaapala.fi/images/19-marquee/marquee.gif" alt="My webpage contents started scrolling, due to  raw `&amp;lt;marquee&amp;gt;` endraw  in content" width="880" height="560"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So, remember to escape your content, especially if it’s input directly from your users.&lt;/p&gt;

&lt;p&gt;I first thought this blog post would be about implementing this in React and TailwindCSS, but as I mentioned, my solution uses no JS, so the framework used for rendering the HTML does not matter.&lt;/p&gt;

</description>
      <category>marquee</category>
      <category>css</category>
      <category>animations</category>
      <category>a11y</category>
    </item>
    <item>
      <title>An open PR is the first 90% of the job – the other 90% remains</title>
      <dc:creator>Olavi Haapala</dc:creator>
      <pubDate>Tue, 16 Feb 2021 10:41:00 +0000</pubDate>
      <link>https://forem.com/olpeh/an-open-pr-is-the-first-90-of-the-job-the-other-90-remains-4d18</link>
      <guid>https://forem.com/olpeh/an-open-pr-is-the-first-90-of-the-job-the-other-90-remains-4d18</guid>
      <description>&lt;h2&gt;
  
  
  Foreword
&lt;/h2&gt;

&lt;p&gt;You probably have heard about the ninety-ninety rule in software engireening. This post introduces my variation of the rule.&lt;/p&gt;

&lt;p&gt;The point of this blog post is not to scare you, but rather to help you understand why estimation is difficult and often fails. You may need to update your mental model for estimations because it’s too easy to not consider all the non-coding related work in estimations. Unless you are a seasoned developer with lots of experience in estimating, my advice is to try to stay away from estimating if you can.&lt;/p&gt;

&lt;h2&gt;
  
  
  The First Thing You Should Learn About Estimations
&lt;/h2&gt;

&lt;p&gt;Estimating is &lt;strong&gt;hard&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Estimating is &lt;strong&gt;challenging&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Your estimates will be &lt;strong&gt;too low&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Estimating is &lt;strong&gt;difficult&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Estimating is &lt;strong&gt;guessing&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Your estimations will be &lt;strong&gt;wrong&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Double&lt;/strong&gt; or &lt;strong&gt;triple&lt;/strong&gt; your initial estimates – quite often &lt;strong&gt;π&lt;/strong&gt; is a nice multiplier and there are some theories behind this as well.&lt;/p&gt;

&lt;p&gt;You should probably try to &lt;strong&gt;avoid having to do estimates&lt;/strong&gt; if possible.&lt;/p&gt;

&lt;p&gt;If you have to estimate, try to take into account the &lt;strong&gt;unkown unknowns&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Did I mention estimating is &lt;strong&gt;hard&lt;/strong&gt;?&lt;/p&gt;

&lt;h3&gt;
  
  
  How Hard Can It Be?
&lt;/h3&gt;

&lt;p&gt;Quite often, you might be foolishly thinking that coding is the biggest part of creating new features or fixing existing bugs in a product. Sadly, this is rarely the case. In many cases, it’s the other way round and the actual coding is the smallest part of the total amount of work. Investigations, debugging, testing, QA, communication, and documenting tend to take most of the time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Based on Personal Experiences
&lt;/h3&gt;

&lt;p&gt;This blog post is mostly based on my personal experiences in contributing to different open source projects and also my experiences at work related projects. Additionally, I have some experience in doing estimations for &lt;a href="https://iltametsuri.fi/"&gt;my side business&lt;/a&gt;, where I provide tree removal services on my free time. My customers often want a fixed price for the job. It is often difficult to estimate how long time it takes to remove a tree. Many times, the seemingly biggest job of felling the tree is actually the fastest part of the job and the rest takes surprisingly long in comparison. Even though the field is completely different from software development, I can see some similarities in how challenging the estimations are.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Ninety-Ninety Rule
&lt;/h2&gt;

&lt;p&gt;I believe the ninety-ninety rule was originally meant as a joke, but it has some level of truth in it and in surprisingly many cases the final fixes and fine tuning takes way more time than expected – quite often the other other 90% of the time.&lt;/p&gt;

&lt;p&gt;Here’s a direct quote from &lt;a href="https://en.wikipedia.org/wiki/Ninety-ninety_rule"&gt;the Wikipedia page about the Ninety-ninety rule&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The first 90 percent of the code accounts for the first 90 percent of the development time. The remaining 10 percent of the code accounts for the other 90 percent of the development time.&lt;/p&gt;

&lt;p&gt;— Tom Cargill, Bell Labs&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  My Variation of the Ninety-Ninety Rule
&lt;/h3&gt;

&lt;p&gt;My variation of the ninety-ninety rule is as it says in the title of this blog post:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;An open PR is the first 90% of the job – the other 90% remains.&lt;/p&gt;

&lt;p&gt;— Olavi Haapala&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It is often too easy to only think about the time spent coding a fix or a feature when doing estimations. However, based on my experience, that accounts only for the first 90% of the time – the other 90% remains. After you have coded something and opened a PR, there’s still quite a lot of work remaining before the task is completed. This is also why you should be focusing on finalizing one task all the way to prod before starting a new one.&lt;/p&gt;

&lt;h3&gt;
  
  
  Abandoned PRs Are the Worst
&lt;/h3&gt;

&lt;p&gt;I have personally opened quite many PRs in open source libraries without taking into account the amount of work that remains after you open a PR. You most probably will receive some feedback about your code changes and need to modify the code. Additionally you might need to add more tests and documentation, and communicate about the changes. All of this, even if small changes and casual communication takes quite a lot of time and mental energy if nothing else. Some of my PRs have been left abandonded due to lack of time or motivation to work on the remaining 90% of the work.&lt;/p&gt;

&lt;p&gt;Opening a PR and then abandoning it might be a huge burden for the maintainers. They need to keep communicating with you, checking up with your PR and waiting for you to contribute to the remaining 90% of the work.&lt;/p&gt;

&lt;p&gt;So, when thinking about opening a PR to a open source repository, remember that the other 90% of the work still remains even if you have coded a fix or a new feature. Coding is “easy”, but getting code finalized, approved, tested, merged, and deployed is more challenging. And don’t forget the feedback and possible changes from your users after you have deployed a change or a fix.&lt;/p&gt;

&lt;h3&gt;
  
  
  Maintenance Cost
&lt;/h3&gt;

&lt;p&gt;In this blog post, I don’t even talk about the costs and work after a fix or a feature has been shipped to production. That’s a whole other story and this blog post would be too long if I would include that as well. However, that’s a real fact and you should consider the total cost and effort required including the maintenance after deploying something.&lt;/p&gt;

&lt;h2&gt;
  
  
  Things to Take into Account
&lt;/h2&gt;

&lt;p&gt;When working on a bugfix or a new feature to a codebase, there are a few things you should take into account. I’m here focusing mostly on open source libraries, but the same probably applies to most projects.&lt;/p&gt;

&lt;p&gt;First of all, check if someone has had the same issue and if it is already fixed or a simple workaround exists. Usually, you would start from the project’s issues. If you’re not lucky, you’ll have to post a new issue and explain the problem and mention that you are willing to work on a fix for it.&lt;/p&gt;

&lt;p&gt;Of course, these instructions may vary depending on the project you are contributing to, so take your time and read the contributing instructions which quite often are found either in the &lt;code&gt;README.md&lt;/code&gt; or the &lt;code&gt;CONTRIBUTING.md&lt;/code&gt; file. And if the instructions are missing, maybe that’s something you can and should contribute to first.&lt;/p&gt;

&lt;p&gt;Now, that you have communicated the issue and your intent to fix it, you can start the actual coding work. Try to follow the existing code style in the code base and avoid fancy abstractions that are not readable by others. Keep in mind that code is mostly read instead of written so readability is more important than the amount of code lines.&lt;/p&gt;

&lt;h3&gt;
  
  
  What’s Next
&lt;/h3&gt;

&lt;p&gt;After you have implemented a fix, there are a few more things to take into account:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Remember to write tests or update existing tests to match the new output. And especially in cases of bug fixes, write a failing test first before fixing the bug. This way you can make sure your fix works, and that the bug should not appear again in the future because your test should stop that from happening.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Write clear and descriptive commit messages with meaningful and logical chunks of code. I could write another blog post about the importance of good commit messages, but meanwhile you should watch this amazing talk by my colleague and friend, Juhis: &lt;a href="https://hamatti.org/talks/contemporary-documentation/"&gt;Contemporary Documentation&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Write a clear description for your PR with possible screenshots before and after, explanations for why things were done in a certain way, and instructions for testing your code changes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Check that your code has no conflicts, and double check that your changes still work. If something worked yesterday, it does not mean it works today.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Respond to feedback on your PR even if you disagree or if you don’t need to change anything. Thank the reviewers. If you receive change requests, implement those changes and go back to step 4.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The next steps depend on the project: in some projects you merge your changes yourself and deploy to prod, but in many cases you need to wait for the maintainers to merge and release a new version.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Closing Words
&lt;/h2&gt;

&lt;p&gt;I hope this blog post was at least somewhat useful to you.&lt;/p&gt;

&lt;p&gt;If you did not pick up anything else from this blog post, at least you can keep in mind that estimating is hard and you should avoid doing it if you are able to choose.&lt;/p&gt;

</description>
      <category>estimating</category>
      <category>9090rule</category>
    </item>
    <item>
      <title>Writing Blog Posts Using Speech-to-Text</title>
      <dc:creator>Olavi Haapala</dc:creator>
      <pubDate>Tue, 09 Feb 2021 05:25:00 +0000</pubDate>
      <link>https://forem.com/olpeh/writing-blog-posts-using-speech-to-text-13aj</link>
      <guid>https://forem.com/olpeh/writing-blog-posts-using-speech-to-text-13aj</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;No, at least based on my experience, I won’t be using this approach in the future. Writing with a proper computer keyboard with 10 fingers is in my experience way faster and less error prone than speech to text, based on this experiment. This is true in my case because I’m lucky enough to have two healthy and functioning hands and I’m able to use a proper keyboard and computer.&lt;/p&gt;

&lt;p&gt;I’m not trying to downplay the usefulness of these kind of technologies as way to make writing more accessible and easier for different people and different use cases. I occasionally use speech-to-text for writing short messages as well. The technology is also getting better and better over time, and there may be better solutions available that I haven’t heard about.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started With Speech to Text
&lt;/h2&gt;

&lt;p&gt;The other day, I had this idea of drafting blog posts on my mobile phone by using the speech-to-text keyboard input. This would allow me to draft blog posts easily while doing something else at the same time, or simply while lying on a sofa. So, I wanted to give this a go, and this is how the two first sentences looked like. I started drafting this blog post a few weeks ago and I have no clue what I tried to say here:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;my phone and this is how did people with.&lt;/p&gt;

&lt;p&gt;Taylor dayne I had an idea what is&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Not a good start! However, after a few sentences, the results improved, and the rest of this blog post is mostly drafted by speech-to-text with some manual editing and fine tuning afterwards.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Actual Content Drafted Using STT
&lt;/h2&gt;

&lt;p&gt;The other day, I had an idea about writing blog posts by dictating them using my phone. So, now I’m trying this out. I will be talking to my phone and drafting the post like this. This seems to not be working well. I’m on my Android phone OnePlus 7T Pro and I’m using the Google keyboard as it can listen to me and write it down. Of course, I have to admit that I’m not a native English speaker and that’s why it might not recognize my words in the way I pronounce them. Also, somehow it seems like this starts working better when I focus on this when I look at the screen and try to speak a bit more slowly and clearly and think about letting the phone get what I mean.&lt;/p&gt;

&lt;h3&gt;
  
  
  Not Ready to Give up Just Yet
&lt;/h3&gt;

&lt;p&gt;So, I’m not ready to give up yet. I will try to finish this blog post and my idea is that maybe I’ll post the raw output or at least a couple sentences from it so you can see what kind of output my phone produced for me. In theory this should be a powerful way for drafting blog posts because you could quickly draft some notes – dump your brain to text and then you can edit it later and iterate on it and then publish it when you have finalized the text.&lt;/p&gt;

&lt;h3&gt;
  
  
  Wishful Thinking
&lt;/h3&gt;

&lt;p&gt;At least based on this test, this will be pretty useful too for me. On the other hand, if I would be talking in more technical terms, I think the keyboard really won’t be able to recognize all my words. I haven’t looked in more detail in the Google keyboard how this works but it somehow feels like it might be learning more if you’re using it. If I actually start using this now, it may learn more about my words and even the technical terms I would be using.&lt;/p&gt;

&lt;h2&gt;
  
  
  Any Tips for Me?
&lt;/h2&gt;

&lt;p&gt;Have you tried drafting your blog posts by using speech-to-text? How did it work for you? How does your writing process look like?&lt;/p&gt;

&lt;p&gt;Are there some better tools for this purpose that I should know of?&lt;/p&gt;

&lt;p&gt;Feel free to reach out to me.&lt;/p&gt;

</description>
      <category>blog</category>
      <category>speechtotext</category>
      <category>workflow</category>
    </item>
    <item>
      <title>Conventions Don’t Matter – What Matters Is Consistency</title>
      <dc:creator>Olavi Haapala</dc:creator>
      <pubDate>Tue, 02 Feb 2021 09:26:00 +0000</pubDate>
      <link>https://forem.com/futurice/conventions-don-t-matter-what-matters-is-consistency-4lp2</link>
      <guid>https://forem.com/futurice/conventions-don-t-matter-what-matters-is-consistency-4lp2</guid>
      <description>&lt;h2&gt;
  
  
  Consistency Is the Key
&lt;/h2&gt;

&lt;p&gt;When working in a larger development team, it’s important to have clear conventions for various aspects of the project. Some of the conventions could be about the team’s ways of working, such as code review processes, git practices and other topics. Most time consuming debates usually are about code style and coding conventions. I personally think that the conventions really don’t matter. What matters is that everyone follows the agreed conventions consistently.&lt;/p&gt;

&lt;p&gt;This is also why code formatting tools, such as Prettier have become widely adopted and popular. Prettier puts an end to the non-important discussions about coding style, such as using or omitting semicolons or trailing commas and other useless discussions which are quite typical discussion in JavaScript or TypeScript projects. Prettier takes care of formatting the code, and in your code reviews, you can focus on things that matter.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Conventions Are Needed
&lt;/h3&gt;

&lt;p&gt;However, there’s a lot more into conventions than code formatting. Especially when using general purpose languages such as JavaScript or TypeScript or libraries such as React. There often are multiple ways of handling different scenarios in the code base. Sometimes developers working simultaneously on different branches implementing different features, but technically speaking solving a similar problem, may end up solving the problem in vastly different ways. Often, it is impossible to say that one of the ways is wrong and the other one not. What matters is that the coding conventions are consistent. This is why the team needs to agree on the conventions and then enforce them and train everyone in the team to follow the agreed conventions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Don’t Blindly Copy Best Practises
&lt;/h2&gt;

&lt;p&gt;There are a lot of examples of long form best practices and conventions written up, which you can check out and adopt in your team as it suits your team. Something that works for others, may not work in your cause though. It is always best to discuss together and agree on the conventions that work for your team and project. However, I would argue that the conventions themselves are unimportant. The only thing that matters is that everyone is following the same conventions and doing so consistently. Over time, the code base should remain somewhat uniform to the extent it is possible.&lt;/p&gt;

&lt;p&gt;You may think that is a bad idea, and stops innovation and adopting new trends and technologies. I dare to disagree. New conventions can be agreed on, and when a new convention is agreed on, it should be used in the codebase from that day on. Either by refactoring the whole code base to follow the new convention, which should be doable if the previous convention was followed carefully, or by using tools such as &lt;a href="https://github.com/phenomnomnominal/betterer"&gt;phenomnomnominal/betterer&lt;/a&gt; to incrementally adopt a new convention, and stop anyone from adding new code that does not follow the newly agreed convention. It is equally important to document the agreed conventions and keep the documentation up-to-date over time in addition to making sure everyone on the team hears about and understands the agreed conventions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conventions Should Not Be Carved in Stone
&lt;/h2&gt;

&lt;p&gt;A team’s conventions should be evolving as the team evolves, learns something new, and new members join the team. It is impossible to agree on perfect conventions that would cover all the future aspects taking into account all the things that are going to happen in the project, in the tech field and in the world. A good example of this is how many teams used to work mostly co-located at offices, but suddenly, due to COVID-19, had to switch to working fully remotely. This has forced teams to evolve their ways of working and conventions in order to adapt to the new situation and continue collaborating in an effective manner.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Works for Your Team
&lt;/h2&gt;

&lt;p&gt;How are the conventions agreed on in your team? How do you document your conventions? What has been the most challenging topic to agree on? I would be interested to know that. Comment below or &lt;a href="https://twitter.com/0lpeh/status/1356532110056689665"&gt;comment on this Twitter thread&lt;/a&gt; or if you wish, you can contact me with DM or in any other way that suits you best.&lt;/p&gt;

</description>
      <category>conventions</category>
      <category>teamwork</category>
      <category>practises</category>
    </item>
    <item>
      <title>Git Bisect Is Your Friend in Need</title>
      <dc:creator>Olavi Haapala</dc:creator>
      <pubDate>Mon, 25 Jan 2021 20:55:00 +0000</pubDate>
      <link>https://forem.com/olpeh/git-bisect-is-your-friend-in-need-87l</link>
      <guid>https://forem.com/olpeh/git-bisect-is-your-friend-in-need-87l</guid>
      <description>&lt;h2&gt;
  
  
  Debugging Is Hard
&lt;/h2&gt;

&lt;p&gt;Sometimes you have to debug bugs that are confusing and you may have no clue why something happens. It may feel impossible to identify what is going on and why. Quite often in these cases, it would be helpful to know when did something start going wrong. In other words, it would be helpful to identify which particular commit caused the issue you are investigating. Not to blame anyone, but in order to find the problem and fix it. In fact, quite often it’s you yourself who have introduced the issue, so blaming won’t help as it never does.&lt;/p&gt;

&lt;h2&gt;
  
  
  Git Has Superpowers
&lt;/h2&gt;

&lt;p&gt;Git is a powerful tool, but some of its powers are underrated or unused. Maybe because not many people know how to use the more advanced or rarely used features of git. In this blog post, I’m going to explain how &lt;code&gt;git bisect&lt;/code&gt; can be used to quickly identify a problematic commit. With the help of git bisect, you’re often able to save a lot of manual investigation when debugging challenging issues in your codebase.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to Use Git Bisect
&lt;/h2&gt;

&lt;p&gt;Git bisect is a powerful tool and I recommend trying it out, especially if the following conditions are met:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You have an issue in your codebase&lt;/li&gt;
&lt;li&gt;You don’t know what caused the issue but you want to fix it&lt;/li&gt;
&lt;li&gt;You are able to find an older commit where the issue does not appear&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  How to Use Git Bisect In Practice
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Run &lt;code&gt;git bisect start&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Find and check out a commit where the issue happens. Most probably your latest commit has the issue you are investigating&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;git bisect bad&lt;/code&gt; to mark that commit as bad&lt;/li&gt;
&lt;li&gt;Randomly check out an older commit and make sure the particular issue does not appear in it&lt;/li&gt;
&lt;li&gt;Mark that commit as good by running &lt;code&gt;git bisect good&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;git bisect run&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Git will automatically checkout a commit for you using binary search algorithm to effectively help you find the problematic commit. Try reproducting the issue, and mark the commit as either bad or good: &lt;code&gt;git bisect bad|good&lt;/code&gt;. Now, you should see something like this in your terminal output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git bisect good
Bisecting: 183 revisions left to test after this (roughly 8 steps)
[7aa559c43a988b0c9e189411f785f0e753a48721] Merge remote-tracking branch 'origin/whatever-branchname into another-branchname

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

&lt;/div&gt;



&lt;p&gt;After you have done the marking enough times, which should not take too long. Git will be able to tell you which commit first introduced the particular problem. In my case the output looked something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;➜ git:(d76efd76e) ✗ git bisect bad
d76efd76e08307f75e4cd1fca4d5f332b3933753 is the first bad commit
commit d76efd76e08307f75e4cd1fca4d5f332b3933753
Author: Olavi Haapala &amp;lt;email@redacted.com&amp;gt;
Date: Tue Oct 27 11:34:28 2020 +0200
&amp;gt;
Upgrade next from 9.4.4 to 9.5.5
&amp;gt;
package.json | 2 +-
yarn.lock | 1139 +++++++++++++++++++++++++++++-----------------------------
2 files changed, 561 insertions(+), 580 deletions(-)

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

&lt;/div&gt;



&lt;p&gt;In this case the particular problem was introduced already a few months ago, when I upgraded Next.js from 9.4.4 to 9.5.5. Notice, how this also highlights the importance of good commit messages. I was immediately able to see what this commit was doing and could take a look at the release notes for Next.js to investigate why this issue got introduced. It would have taken me hours and hours to manually go through all the hundreds of commits after this bug got introduced. Git bisect saved me from doing that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Almost Done
&lt;/h2&gt;

&lt;p&gt;After you have found out the cause for a problem, the work is almost done. Fixing a known issue takes almost no time compared to hunting a problematic piece of code from a larger code base. Of course this is not always true, but in most cases it is.&lt;/p&gt;

&lt;h2&gt;
  
  
  Save Even More Time When Using Git Bisect
&lt;/h2&gt;

&lt;p&gt;If you want to save even more time when using git bisect to hunt down a problematic commit, you can let git automatically check if the commits are bad or good. After the command &lt;code&gt;git bisect run&lt;/code&gt;, you can give a command to be run which determines if a commit is good or bad. In some cases it could be running your tests or in other cases you could make a script that looks for something else that goes wrong. Git bisect opens up a world of possibilities beyond manually checking different commits and PRs and trying to guess when a problem first started appearing.&lt;/p&gt;

&lt;p&gt;Here’s a screenshot of a tweet I sent a few years ago about enjoying the magic of git bisect:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://twitter.com/0lpeh/status/1042720270522429440"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ivEhFtBi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://olavihaapala.fi/images/15-git-bisect/tweet.png" alt="Me on my laptop in front of a fireplace" width="598" height="666"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you haven’t used &lt;code&gt;git bisect&lt;/code&gt; before, please give it a try. I promise you will save some time after you learn to effectively use it.&lt;/p&gt;

</description>
      <category>git</category>
    </item>
    <item>
      <title>My 2020 in Review</title>
      <dc:creator>Olavi Haapala</dc:creator>
      <pubDate>Thu, 21 Jan 2021 10:19:00 +0000</pubDate>
      <link>https://forem.com/olpeh/my-2020-in-review-46kj</link>
      <guid>https://forem.com/olpeh/my-2020-in-review-46kj</guid>
      <description>&lt;h2&gt;
  
  
  Author’s notice
&lt;/h2&gt;

&lt;p&gt;I have not been actively publishing new content in my blog for a longer time now, due to trouble with my blog setup, failing to rewrite the blog from scratch, and on top of that, you know, just 2020 being 2020.&lt;/p&gt;

&lt;p&gt;Now, that I finally have my blog setup working again and as I’m trying to ramp up my content production, I thought it would be interesting to write about how the past year has been for me and how the year 2021 is looking like at this point of time. As this is a more personal blog post, I thought it would be nice to add some pictures as well. Enjoy!&lt;/p&gt;




&lt;h2&gt;
  
  
  2020 Was a Historical Year
&lt;/h2&gt;

&lt;p&gt;2020 was a weird year in many ways. I’m quite sure, the last year was a weird year for everyone. And we all know about the pandemic and other unexpected things that happened, so I will not be talking about those. In stead I will try to focus on other stuff, but the pandemic has had an impact on almost everything.&lt;/p&gt;

&lt;h3&gt;
  
  
  Work
&lt;/h3&gt;

&lt;p&gt;2020 was a busy year for me at work. We had just finished a longer site renewal project in March 2020 and launched it to production, when the whole pandemic situation started massively hitting Finland as well. And in addition to that we had an even larger project starting up due to an acquisition that came to us as a bit of a surprise. I ended up working too much in 2020 and did not have too much time for stuff like open source and personal projects.&lt;/p&gt;

&lt;h3&gt;
  
  
  Working From Home or Anywhere
&lt;/h3&gt;

&lt;p&gt;I have been working fully remotely since the beginning of March 2020. Working from home feels pretty good to me as I live a bit further away from the office. Now, that I don’t need to commute to work every day, I save almost 2 hours everyday. On the other hand, working from home seems to easily lead into working more than I should. Starting the workday earlier than normally is easy without having to commute to work. Ending the workday is more difficult at home than leaving the office after a day at work. I used to work remotely every once in a while earlier as well, but working full time remotely is quite different.&lt;/p&gt;

&lt;p&gt;&lt;a href="///images/14-2020-in-review/remote.jpg"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--loZeXsoX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://olavihaapala.fi/images/14-2020-in-review/remote.jpg" alt="Me on my laptop in front of a fireplace" width="587" height="718"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Events in 2020
&lt;/h3&gt;

&lt;p&gt;Events have turned into online events, and in the beginning they felt interesting and fun. Now, there seems to be a lack of interest in online events due to the lack of social interaction, which happens in physical events when you bump into random people and start discussing various interesting topics. Despite all the events being held online, I have still managed to present quite many times in our weekly tech event called Tech Weeklies. You can &lt;a href="https://futurice.com/blog/tech-weeklies-as-a-learning-platform"&gt;learn more about Tech Weeklies from this blog post in The Futurice blog&lt;/a&gt;. Naturally, not all of my talks are public due to non-public content, but you can see links to the recordings in YouTube of some of my talks in &lt;a href="https://dev.to/talks/"&gt;the talks page&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Learning by Presenting
&lt;/h4&gt;

&lt;p&gt;Presenting is interesting as it forces you to learn more about the topic you are going to present about. Even if you think you know something, only if you are able to explain it to others, you actually know it. In many cases, I have had to look into the basics again, in order to be able to talk about that topic. Presenting is fun and I recommend you to try it out if you haven’t. Please note though, that the first time presenting might not be super pleasant as you get nervous about it. It will get easier over time and through practice. After a few presentations, you can start concentrating on the audience and not only on what you were supposed to say next. I personally find presenting in person easier than presenting remotely. When presenting in a virtual event, you don’t see your audience and you get no feedback from them during your presentation.&lt;/p&gt;

&lt;p&gt;&lt;a href="///images/14-2020-in-review/presenting.jpg"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Yy3_tdRO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://olavihaapala.fi/images/14-2020-in-review/presenting.jpg" alt="My 3 screen home office setup" width="880" height="507"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Side Business
&lt;/h3&gt;

&lt;p&gt;In September 2019, I founded a company called &lt;a href="https://iltametsuri.fi/"&gt;Iltametsuri Oy&lt;/a&gt;. The company is my side business/hobby for cutting down trees on my free time. During 2020, I did about 30 smaller or larger gigs, which is surprisingly good in my opinion. This job acts as an interesting balance for the (home) office work where I stare at screens all day long. Getting outside and doing something physical gives me a lot of energy and motivation.&lt;/p&gt;

&lt;p&gt;&lt;a href="///images/14-2020-in-review/snowmobile.jpg"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--KLqUcgh_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://olavihaapala.fi/images/14-2020-in-review/snowmobile.jpg" alt="Me on a snowmobile pulling a sled full of logs" width="880" height="661"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Most of the company’s gigs so far have been for friends and colleagues, but increasingly I get new contacts due to my online precense and a Google ad I’m paying for. My initial ad budget was 3€/day but I recently increased it to a whopping 6€/day. During the first fiscal year, all of the revenue (and a bit more) has been invested on buying the best available tools, some of which you can see in the image below.&lt;/p&gt;

&lt;p&gt;&lt;a href="///images/14-2020-in-review/iltametsuri.jpg"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--HhTTPgHF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://olavihaapala.fi/images/14-2020-in-review/iltametsuri.jpg" alt="Me posing and leaning to my chainsaw after felling a large tree" width="880" height="1130"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Vacation
&lt;/h3&gt;

&lt;p&gt;On the other hand, 2020 was a nice year. Despite busy and stressful times at work, I was able to have more than 8 weeks of vacation last year! Not all of it was paid vacation, some weeks were balance leave, but still. And when I say vacation, I mean vacation as you can see from the below screenshot of my GitHub contributions. By the way, almost all of my contributions last year were on work project. I did not do much open source work in 2020. Last summer I enjoyed the Finnish nature more than any previous years in my life.&lt;/p&gt;

&lt;p&gt;&lt;a href="///images/14-2020-in-review/olpeh.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--bkcz8hyl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://olavihaapala.fi/images/14-2020-in-review/olpeh.png" alt="Contribution chart for olpeh on GitHub" width="753" height="190"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="///images/14-2020-in-review/mokki.jpg"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--j_SQ-ZvO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://olavihaapala.fi/images/14-2020-in-review/mokki.jpg" alt="My hand holding a long drink in, with a person jumping into water in the background and a beautiful sunset." width="602" height="955"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="///images/14-2020-in-review/kautokeino.jpg"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0QEbpQI3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://olavihaapala.fi/images/14-2020-in-review/kautokeino.jpg" alt="A river in sunset." width="880" height="404"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Vacation Routines
&lt;/h4&gt;

&lt;p&gt;My routine for starting a vacation includes closing Slack and work email on my laptop, trying to avoid using my laptop at all, and deleting Slack and work accounts from my mobile phone completely. The only way my supervisor or colleagues could reach me on my vacation would be by calling me, but who even makes normal calls nowadays? Not even once have I been interrupted on my vacation due to work.&lt;/p&gt;

&lt;p&gt;&lt;a href="///images/14-2020-in-review/saana.jpg"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8qKMfiGi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://olavihaapala.fi/images/14-2020-in-review/saana.jpg" alt="View from Saana fell." width="880" height="404"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  2021
&lt;/h2&gt;

&lt;p&gt;The year 2021 is off to a promising start. We have interesting new challenges at work, I started the year actively by doing a lot more cross country skiing than I normally would do since we finally have proper winter with lots of snow even in southern Finland, and on top of all that, I have more time and energy for writing blog posts.&lt;/p&gt;

&lt;p&gt;&lt;a href="///images/14-2020-in-review/skiing.jpg"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--raQ23OS1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://olavihaapala.fi/images/14-2020-in-review/skiing.jpg" alt="Selfie after skiing. Lot of snow on my beard." width="880" height="879"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  My Goal for 2021
&lt;/h3&gt;

&lt;p&gt;My goal for 2021 is to start producing more content, including more tutorial videos. At least with the blogging, I’m already on a quite nice track.As several people have said, consistency is the key. So, my goal is to start consistently producing interesting content in the form of blog posts, presentations and videos.&lt;/p&gt;

&lt;p&gt;Who knows, if in 2021 we could again have physical events such as conferences and meetups face to face. That remains to be seen, but let’s hope for the best.Stay tuned for more content in my blog this year! You can also send me wishes on topics that you would like me to write about.&lt;/p&gt;

</description>
      <category>2020</category>
    </item>
    <item>
      <title>Server Side Rendering: Why We Have Gone Full Circle</title>
      <dc:creator>Olavi Haapala</dc:creator>
      <pubDate>Wed, 11 Dec 2019 06:40:00 +0000</pubDate>
      <link>https://forem.com/olpeh/we-have-gone-full-circle-40fi</link>
      <guid>https://forem.com/olpeh/we-have-gone-full-circle-40fi</guid>
      <description>&lt;h3&gt;
  
  
  Author’s notice
&lt;/h3&gt;

&lt;p&gt;This blog post is based on one of my recent tech talks “Why is SSR relevant in 2019” and represents my limited view on the topic.Please bare that in mind when reading this blog post.My view on the history of the web may have mistakes and is certainly not covering all aspects.In the beginning of this post I’m trying to explain some of the key concepts and how the web works before diving deeper into why Server Side Rendering is relevant today.&lt;/p&gt;

&lt;p&gt;I almost got carried away and did not get into writing about the main topic until late in this post, so please feel free to jump over the first half of the post and start reading from 4 Reasons Why SSR is Highly Relevant Today.&lt;/p&gt;

&lt;p&gt;This blog post is highly opinionated, read at your own risk!&lt;/p&gt;




&lt;h2&gt;
  
  
  How the Web Looks Like Today
&lt;/h2&gt;

&lt;p&gt;The web as we know it, consists of web pages and applications built with a huge variety of technologies.However, there is one thing all web pages have in common, HyperText Markup Language (HTML).In the end all of the web technologies end up creating the webpage content as HTML that gets rendered on the screen.Most of the web pages also use Cascading Style Sheets (CSS) for styling and JavaScript (JS) for dynamic functionality.More and more modern web pages are built as JS Single Page Applications (SPA) that rely heavily on the JS runtime for rendering content on the screen and handling the application logic and navigations.This does not necessarily mean writing all the code as JS directly, but there are multiple compile-to-js languages and tools available.Most commonly known is TypeScript (TS).&lt;/p&gt;

&lt;p&gt;Even though all web pages are quite similar from technical point of view, there are huge variations in the page “weights” as in how heavy and slow performing the different web pages are.Performance is directly connected with how likely your users are to return to your web page.If you are running an e-commerce business, and trying to sell something, a bad performance will directly affect your sales.&lt;/p&gt;

&lt;p&gt;Sadly the situation does not seem to get better over time even with the evolution of technology and web development tools.One could easily assume better tools and tech would make the web a better place for everyone, and most importantly for the increasing amount of users browsing on mobile devices.&lt;/p&gt;

&lt;p&gt;In fact, based on HTTP Archive, the amount of web traffic from mobile and tablet devices surpassed the traffic from desktop devices already in 2016 globally.This can bee seen in the below image.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://olavihaapala.fi/images/11-ssr/internet_usage_2009_2016_ww.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--00b-P2Sr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://olavihaapala.fi/images/11-ssr/internet_usage_2009_2016_ww.png" alt="Global internet usage stats from 2009 to 2016."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Based on &lt;a href="https://httparchive.org/reports/page-weight?start=2017_05_01&amp;amp;end=2018_05_15&amp;amp;view=list"&gt;the HTTP Archive stats&lt;/a&gt;, web pages got ~20% heavier and slower on almost all metrics when comparing 2017 to 2018.Most of the increase in page weight and slowness can be explained by the increasing amount of JS on web pages.On the other hand, many web pages are a lot more complex nowadays than they used to (or need to!) be.&lt;/p&gt;

&lt;p&gt;If you like to read more about heavy webpages and the biggest reason for slow webpages, go ahead and read &lt;a href="https://medium.com/@addyosmani/the-cost-of-javascript-in-2018-7d8950fbb5d4"&gt;The Cost Of JavaScript In 2018&lt;/a&gt; (sorry, Medium link!) and &lt;a href="https://v8.dev/blog/cost-of-javascript-2019"&gt;The Cost of JS in 2019&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Some Definitions
&lt;/h2&gt;

&lt;p&gt;Let’s look at some of the commonly used terms and their definitions before diving deeper into the main topic of this blog post.&lt;/p&gt;

&lt;h3&gt;
  
  
  Server Side Rendering
&lt;/h3&gt;

&lt;p&gt;In a Server Side Rendered (SSR) web page, the web server returns an HTML document that already contains the content as HTML.The browser will render the document contents on the screen whenever it receives the document and parses it from top to bottom.&lt;/p&gt;

&lt;h3&gt;
  
  
  Client Side Rendering
&lt;/h3&gt;

&lt;p&gt;Client Side Rendering on the other hand refers to a technique where the server only returns a skeleton of the application in the HTML document.The HTML only acts as a template and has a link to the JS application bunldle, which the browser downloads, parses, and excecutes.The JS application then renders the contents by hooking itself into a root div that is part of of the server rendered HTML contents.Usually this means something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;body&amp;gt;
  &amp;lt;div id="app"&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;/body&amp;gt;

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



&lt;h3&gt;
  
  
  Isomorphic Rendering
&lt;/h3&gt;

&lt;p&gt;Isomorphic Rendering on the other hand, refers to a technique where both SSR and CSR are combined in a web application.The server renders the document contents as well as the initial application state as JSON in the HTML.After rendering the SSR content on the screen and loading the JavaScript bundle, the JS application then reads the initial state from the server rendered response and boots up the JS application.This phase is called rehydration, the static server rendered HTML document gets hydrated into a JavaScript SPA.This approach tries to achieve the best of both worlds by achieving a fast first paint because of SSR content and fast SPA navigations after the application has been rehydrated.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Short and Simplified History of the Web
&lt;/h2&gt;

&lt;p&gt;The web was invented for about 30 years ago.In the beginning everything was fully server side rendered web pages.First web pages were documents containing text and links, images were added a bit later.More and more interactivive elements were aded as part of the web when JavaScript invented.Interactive elements suc as Flash, Quicktime, RealPlayer, and Shockwave came along.Still, at this time most of the web pages were built using SSR with mostly PHP and friends.&lt;/p&gt;

&lt;h3&gt;
  
  
  JS Fatique
&lt;/h3&gt;

&lt;p&gt;A multitude JS libraries started popping up such as MooTools, jQuery, and Backbone.These allowed developers to create interactive web applications more easily using JS instead of Flash or other old school tools.Later on, Angular.js was published and in around 2014 React was published.This started a new era in the web development scene.Suddenly “everyone” was doing client side rendered JS based SPAs.This caused web pages to get heavier and slower.Something needed to be done.&lt;/p&gt;

&lt;p&gt;Progressive Web Applicationss got introduced.Along other features, they provided an additional cache layer on the client level using Service Workers.However, in my opinion this is a wrong solution to the problem of slow web pages.&lt;/p&gt;

&lt;h3&gt;
  
  
  Back to the Roots
&lt;/h3&gt;

&lt;p&gt;Recently the web development community has started realizing that going full on with client side rendering was a mistake and ruined the web performance, especially on cheaper and slower devices.Now, suddenly “everyone” is talking about SSR again and many frameworks and platforms provide solutions for that.This is a good direction from web performance point of view.&lt;/p&gt;

&lt;h2&gt;
  
  
  Accelerated Mobile Pages
&lt;/h2&gt;

&lt;p&gt;Accelerated Mobile Pages (AMP) is Google’s iniative for making the web faster on mobile devices.I think the initiative in itself is good, but I don’t like the way Google tries to enforce it and take the ownership of the web and its contents by serving pages from their own cache servers.Google is also misusing its huge position in the market by prioritizing AMP pages in Google search results.Sadly most of the people are using Google for searching, which forces companies to start considering using AMP in order to achieve good positions in search results.Due to the above mentioned reasons, I would not recommend supporting or using AMP at all.&lt;/p&gt;

&lt;p&gt;AMP in itself is a good library for creating web pages, because it limits the possibilities in which you are able to create horrible user experiences on the web.&lt;/p&gt;

&lt;p&gt;Even AMP recently announced a revolutionary new technology called SSR in the AMP library as you can see in the below screenshot of Ricky Mondello’s sarcastic tweet:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://twitter.com/rmondello/status/1160201903566802949"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--WXfEOmIL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://olavihaapala.fi/images/11-ssr/rmondello.png" alt="Screenshot of Ricky Mondello's tweet: https://twitter.com/rmondello/status/1160201903566802949"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I agree with Ricky, that this is ridiculous.We have had HTML and SSR for 30 years, what do we need AMP for?&lt;/p&gt;

&lt;p&gt;Okay, time to move to the &lt;em&gt;actual&lt;/em&gt; topic of this blog post.&lt;/p&gt;




&lt;h2&gt;
  
  
  4 Reasons Why SSR is Highly Relevant Today
&lt;/h2&gt;

&lt;p&gt;Here are my 4 reasons for using SSR today.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Performance
&lt;/h3&gt;

&lt;p&gt;Web pages that are rendered only on the client side tend to have quite slow time to First Paint (FP) and First Meaningful Paint (FMP), because the browser has to first download, parse, and excecute the JS bundle before it can paint the contents on the screen.The below screenshot tries to visualize what these different metrics means.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://olavihaapala.fi/images/11-ssr/perf-metrics-load-timeline.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--RE1Uw0C_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://olavihaapala.fi/images/11-ssr/perf-metrics-load-timeline.png" alt="Timeline that explains the different performance metrics."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Image source: &lt;a href="https://developers.google.com/web/fundamentals/performance/user-centric-performance-metrics"&gt;User-centric Performance Metrics&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In order to achieve a good user experience, try to move these events as close to the left as possible, meaning as fast after navigation start as possible.Additionally Time To Interactive (TTI) is an important metric, because before that moment in time, your users can’t start interacting with the interactive parts of your site.&lt;/p&gt;

&lt;p&gt;This is where SSR has its biggest win.With server rendered pages, the browser is able to paint the document contents on the screen immediately when it has received the document and goes through the contents of it.With this approach, it is possible to achieve&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Search Engine Optimization (SEO)
&lt;/h3&gt;

&lt;p&gt;Google and other search engines most probably will not run the JS on your web page when indexing the web.There are rumours that Google is running the JS, but with a 2 weeks delay.For most of the businesses where a good SEO is critical, a 2 weeks delay is not acceptable. &lt;del&gt;And as a cherry on top, Google reportedly runs your JS with an ancient version of Chrome, which is even worse than IE9.&lt;/del&gt; &lt;em&gt;Edit&lt;/em&gt;: This was not true anymore, &lt;a href="https://searchengineland.com/google-will-ensure-googlebot-runs-the-latest-version-of-chromium-316534"&gt;Google bot now uses the latest version of Chromium&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So, if you care about good SEO, use SSR.However, if you have a fully functional SPA and don’t want to invest time in implementing SSR, there are ready made services that does the SSR for you.At least Netlify has SSR as a service that you can start using if you are hosting in Netlify.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Accessibility
&lt;/h3&gt;

&lt;p&gt;Accessibility is about making it possible for &lt;em&gt;everyone&lt;/em&gt; to access and understand the contents on your webpage.Accessibility is about performance as well.A slow webpage is not accessible to everyone, only those on high-end devices and fast network connection speeds.&lt;/p&gt;

&lt;p&gt;Some people even prefer to browse the web without having JS enabled.If you SSR your main content, your web page will be accessible even withou JS enabled.If your application uses SPA navigations when the user clicks on links, don’t worry, they will work fine as normal links that have been working for 30 years already without JS.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Not Everything Needs to be an App
&lt;/h3&gt;

&lt;p&gt;Remember, that even though creating web applications is popular, not everything nees to be an application.In many cases, a simple HTML based SSR web page is enough.Say, a landing page for a newly started company.You don’t need to build it as a SPA application, especially if you build it as a one-pager with navigations happening in that same page.There’s a revolutionary technology for achieving just that, and it’s called anchor tags or &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt;.So, please use the platform #usetheplatform.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Words
&lt;/h2&gt;

&lt;p&gt;No matter, what technology you use, remember to consider your real end users over a better Developer Experience (DX).In the end, the only thing that matters is that your users are happy.In most of the cases that means having a fast experience even on mobile devices and flaky connections.By optimizing your web application or web page for slower devices and slower networks speeds, you will end up making it a better experience to everyone.Similarly, by focusing on making your application accessible, you will end up making it more usable to everyone.&lt;/p&gt;

</description>
      <category>ssr</category>
      <category>webperf</category>
      <category>a11y</category>
    </item>
  </channel>
</rss>
