<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Jure Prnaver</title>
    <description>The latest articles on DEV Community by Jure Prnaver (@eruj22).</description>
    <link>https://dev.to/eruj22</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%2F654526%2F54fff018-2920-40ad-9943-c1bb3932f1c8.png</url>
      <title>DEV Community: Jure Prnaver</title>
      <link>https://dev.to/eruj22</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/eruj22"/>
    <language>en</language>
    <item>
      <title>What I have learned about testing</title>
      <dc:creator>Jure Prnaver</dc:creator>
      <pubDate>Sat, 10 Jun 2023 12:14:35 +0000</pubDate>
      <link>https://dev.to/eruj22/what-i-have-learned-about-testing-703</link>
      <guid>https://dev.to/eruj22/what-i-have-learned-about-testing-703</guid>
      <description>&lt;p&gt;Let's face it, testing can be challenging for many developers.  It is one of the things that we want to avoid. I certainly know that because I was also thinking the same way. But many challenges and setbacks changed my view on it. Below are some of the situations that I have experienced along the way and what I have learned from them. These lessons can help you on your path to becoming better at testing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Know why you are writing tests
&lt;/h2&gt;

&lt;p&gt;Before writing any test it's important to know why we write them. Kent C. Dodds has a great definition for that: &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;We write tests to be confident that our application will work when the user uses them. (&lt;a href="https://kentcdodds.com/blog/how-to-know-what-to-test#remembering-why-we-test"&gt;source&lt;/a&gt;)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is related to one situation at work. I took over a ticket and implemented the changes. Everything seemed to be working well and I pushed the code. After some time some tests in the CI pipeline failed. At first, I thought the tests were wrong, but it turned out that new changes broke another part of the app. That was the first time when tests got my back.&lt;/p&gt;

&lt;p&gt;This experience taught me a good lesson - tests are worth doing. They take some effort when writing them, but in the future, they can save you from making changes that you don't intend to make.&lt;/p&gt;

&lt;h2&gt;
  
  
  Look from the perspective of the user
&lt;/h2&gt;

&lt;p&gt;This is the fundamental principle. When I learned it well, it helped me immensely.&lt;/p&gt;

&lt;p&gt;When you test a part of your app, look through the perspective of a user. Think in terms of what effect your code has on the user. The same perspective applies when using queries in tests.&lt;/p&gt;

&lt;p&gt;Here are some questions to point you in the right direction: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What does the user see? &lt;/li&gt;
&lt;li&gt;How can he interact with the app? &lt;/li&gt;
&lt;li&gt;What effect does his actions have? &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Hopefully answering these questions gives you a better perspective on the user flow. &lt;/p&gt;

&lt;p&gt;From this point you can start writing tests for the common user path. Start with simple ones and progress towards more complex ones. The next thing is covering edge cases. Try to think about all the different ways that could go wrong and test them.&lt;/p&gt;

&lt;p&gt;Both opposing views should help you test most use cases. This will give you confidence that your app is running as you have imagined.&lt;/p&gt;

&lt;h2&gt;
  
  
  Writing tests test your understanding of the code
&lt;/h2&gt;

&lt;p&gt;Most probably you will get to a point when you have trouble writing a test. There are two reasons for this: you are not good at testing yet or you don't understand what the code does.&lt;/p&gt;

&lt;p&gt;I remember when I tried to improve test coverage for an older part of the code. I quickly looked through it and dived right into testing. It didn't take much time until I got stuck. The bottleneck in this situation was my understanding of what the code does. I had to take time to thoroughly inspect the code. When I  figured out what it does, I successfully finished writing tests for it.&lt;/p&gt;

&lt;p&gt;So take time to understand the code before writing tests for a component or a part of an app. &lt;/p&gt;

&lt;h2&gt;
  
  
  Write simple tests
&lt;/h2&gt;

&lt;p&gt;This seems straightforward advice, but some developers find it hard to follow. Sometimes developers fall into the trap of over-engineering the code. We write the code in a too-smart way. &lt;/p&gt;

&lt;p&gt;This can feel good at the moment but can hurt you and your team members in the future. The same thing can happen when writing tests. So we need to be aware of this phenomenon and try to actively mitigate it.&lt;/p&gt;

&lt;p&gt;Here are some helpful tips:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Test only one scenario per test: don't try to force many scenarios into a single test&lt;/li&gt;
&lt;li&gt;Copy the code rather than abstract it: Don't add any abstractions and if statements when they are not necessary. In that case, it's better to copy some of the code to have clearer tests&lt;/li&gt;
&lt;li&gt;Follow a clear and well defined structure: 

&lt;ul&gt;
&lt;li&gt;Arrange: arranging all the inputs, mocks, connections...&lt;/li&gt;
&lt;li&gt;Act: run the code&lt;/li&gt;
&lt;li&gt;Assert: set expectations, what should happen if the test runs correctly&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Debugger is your friend
&lt;/h2&gt;

&lt;p&gt;This tool can save you time when writing tests. Of course, you need to know how to use it in order to unleash all of its power. It is especially useful in a large codebase containing old code.&lt;/p&gt;

&lt;p&gt;Imagine you are testing an older component. You write a test that you think should work but it fails. You try some things and add console logs in different places, but you are still stuck.&lt;/p&gt;

&lt;p&gt;In this situation, the debugger is your best friend. You add it to the executable part of the code and run the test in debugger mode. The execution of the code stops where you put debugger. At this point, you can see all defined variables, and buttons for step over, step into, and step out. Now step over the code until you get to a point where it unexpectedly fails.&lt;/p&gt;

&lt;p&gt;Here lies the magic of the debugger. Right before the breaking point, you can inspect what is missing in the test. Did you forget to mock some function or method? What variables are missing? Add those to the test and try again. Sooner or later you should get it to run green.&lt;/p&gt;

&lt;h2&gt;
  
  
  Always test the opposite expectation
&lt;/h2&gt;

&lt;p&gt;A lot of articles advise that you: "Fail the test first, then make it green". This sounds great when you follow TDD (test-driven development), but the way I work it needed some modification. My definition is: "Make the test green, then let it fail, and lastly revert it back to green". This approach assures you that the test does what it suppose to do. &lt;/p&gt;

&lt;p&gt;Following this rule helped me in some interesting situations. I remember once when I wrote a test that ran green. Following the rule I changed the expected case to be the opposite of the previous expectation. When running the test again it was again green. &lt;/p&gt;

&lt;p&gt;You probably won't get to this situation many times, but I still find it valuable enough to follow it. The rule ensures that tests are working correctly and improve your confidence in them.&lt;/p&gt;

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

&lt;p&gt;Thank you for sticking with me until the end. I hope that your thinking about tests has been challenged and that you have learned something new.&lt;/p&gt;

&lt;p&gt;Share your experiences in the comments below. I would love to read what you have learned when testing. &lt;/p&gt;

</description>
      <category>testing</category>
      <category>learning</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Movie Database - find the perfect movie</title>
      <dc:creator>Jure Prnaver</dc:creator>
      <pubDate>Thu, 08 Dec 2022 18:55:42 +0000</pubDate>
      <link>https://dev.to/eruj22/movie-database-find-the-perfect-movie-1p4o</link>
      <guid>https://dev.to/eruj22/movie-database-find-the-perfect-movie-1p4o</guid>
      <description>&lt;h2&gt;
  
  
  What I built
&lt;/h2&gt;

&lt;p&gt;The app that let's you discover more information about your favorite movie.&lt;/p&gt;

&lt;h3&gt;
  
  
  Category Submission:
&lt;/h3&gt;

&lt;p&gt;Search No More&lt;/p&gt;

&lt;h3&gt;
  
  
  App Link
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://movie-database-33.vercel.app/" rel="noopener noreferrer"&gt;https://movie-database-33.vercel.app/&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Screenshots
&lt;/h3&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%2F2erok9m92kitahbodriz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2erok9m92kitahbodriz.png" alt="hero page"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Description
&lt;/h3&gt;

&lt;p&gt;Inside the app the main functionality is to quickly search for a movie that you are looking for. When typing you get autosuggestion with links to movies, or you can press enter to go to search page. On the top menu you can also go to all movies and filter or sort them as you wish. Clicking on single movie navigates you on the page with more information about it. There you will find a link to the page with all cast and crew. &lt;/p&gt;

&lt;p&gt;To enable saving films to watchlist you need to login using your Google account. Then inside your profile you can see the watchlist.&lt;/p&gt;

&lt;h3&gt;
  
  
  Link to Source Code
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/eruj22/movie-database" rel="noopener noreferrer"&gt;https://github.com/eruj22/movie-database&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Permissive License
&lt;/h3&gt;

&lt;p&gt;MIT&lt;/p&gt;

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

&lt;p&gt;It was so exciting to learn about this hackathon, as I love to use MongoDB. The idea for it came rather easy, as I frequently use look up more information about movies to watch. It was a good challenge to make something similar on my own.&lt;/p&gt;

&lt;p&gt;Inspiration for the layout came from TMDB, IMDb and Rotten Tomatoes. Color scheme was heavily inspired by socks that were a gift right before I started making the design for an app (not kidding, proof is in the image below).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcywasjdnlo5jfasj0rcx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcywasjdnlo5jfasj0rcx.png" alt="red socks with many little popcorns"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Technologies used
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Next.js&lt;/li&gt;
&lt;li&gt;MongoDB&lt;/li&gt;
&lt;li&gt;Emotion&lt;/li&gt;
&lt;li&gt;NextAuth&lt;/li&gt;
&lt;li&gt;Typescript&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  How I built it
&lt;/h3&gt;

&lt;p&gt;First phase of the app was sketching wireframes by hand and making a rough design in Figma. &lt;/p&gt;

&lt;p&gt;Then it was time to set up database and basic search functionality. I have populated the data in the database using data from TMDB and modified it a bit for my use case. Next up I set up search indexes to search all movies and autocomplete. These were connected to the functions in App Services.&lt;/p&gt;

&lt;p&gt;Moving on comes my favorite part, building out the frontend part of the app. The framework that helped me make data visible to users is Next.js. &lt;/p&gt;

&lt;p&gt;First I made the hero page and a connection to the database.  Next page was movies page, where you can filter through all movies. Clicking on single movie navigates you to the page with more data about the movie. Inside it is also a link to the page with all the cast and crew. &lt;/p&gt;

&lt;p&gt;Search page is a page that you see when you submit search query. Inside it there is a search component (also on hero page) that uses the function in the MongoDB called &lt;code&gt;searchMovies&lt;/code&gt;. This function searches through the whole database and returns what it finds.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;exports = (title) =&amp;gt; {
    let collection = context.services.get("mongodb-atlas").db("test").collection("singlemovies");

    const pipeline = [
      {
        $search: {
          index: "searchMovies",
          text: {
            query: title,
            path: {
              "wildcard": "*"
            },
            fuzzy: {}
          }
        }
      }
    ]

  return collection.aggregate(pipeline)  
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The index field below is used inside the function on the top to form aggregation pipeline.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "mappings": {
    "dynamic": true,
    "fields": {
      "title": [
        {
          "dynamic": true,
          "type": "document"
        }
      ]
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inside search component there is also autocomplete functionality that suggests movies based on what you type in. The function &lt;code&gt;autocompleteMovies&lt;/code&gt; returns maximum 6 items and each field consists of only title and id.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;exports = function(arg){
 let collection = context.services.get("mongodb-atlas").db("test").collection("singlemovies");

 let pipeline = [
    {
      $search: {
        index: "autocompleteMovies",
        "autocomplete": {
          "query": arg,
          "path": "title",
          "tokenOrder": "sequential"
        }
      }
    },
    {
      $limit: 6
    },
    {
      $project: {
        "title": 1,
        "_id": {"$toString" : "$_id"},
      }
    }
  ]

  return collection.aggregate(pipeline)
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The function uses search index with index field set only to &lt;code&gt;title&lt;/code&gt; and it starts suggesting when you write at least 3 letters.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "mappings": {
    "dynamic": false,
    "fields": {
      "title": [
        {
          "analyzer": "lucene.standard",
          "foldDiacritics": true,
          "maxGrams": 14,
          "minGrams": 3,
          "tokenization": "edgeGram",
          "type": "autocomplete"
        }
      ]
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Last but not least is the login functionality. For this job I picked NextAuth as widely used solution for authentication in a Next.js app. In the app you can only login using your Google account.&lt;/p&gt;

&lt;p&gt;In the end I have made final visual improvements in the app and took some time to correctly deploy it. &lt;/p&gt;

&lt;p&gt;During the development I had a lot of fun. It is very satisfying to build something new. During the process I have faced many challenges and also successfully solved many of them.&lt;/p&gt;

&lt;p&gt;One thing to note, there are only 40 movies stored in the database.&lt;/p&gt;

&lt;p&gt;Thank you for reading.&lt;/p&gt;

</description>
      <category>atlashackathon22</category>
      <category>mongodb</category>
      <category>nextjs</category>
    </item>
    <item>
      <title>Create Custom Keyboard Navigation for Grid Items</title>
      <dc:creator>Jure Prnaver</dc:creator>
      <pubDate>Sun, 16 Oct 2022 15:16:04 +0000</pubDate>
      <link>https://dev.to/eruj22/create-custom-keyboard-navigation-for-grid-items-4ibn</link>
      <guid>https://dev.to/eruj22/create-custom-keyboard-navigation-for-grid-items-4ibn</guid>
      <description>&lt;p&gt;Recently I have faced a challenge of setting up a custom keyboard navigation inside a long list of files. They are displayed both in grid and list view. As I haven't found the exact solution when googling, here is an article explaining what I have learned. Thanks to &lt;a href="https://ryanmulligan.dev/blog/project-keyboard-navigation/"&gt;Ryan Mulligan's article&lt;/a&gt; for inspiration.&lt;/p&gt;

&lt;p&gt;Prerequisite: basic knowledge of React.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://custom-keyboard-navigation.netlify.app/"&gt;Check out the working project demo site&lt;/a&gt; to see what we are building.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up starter code
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/eruj22/custom-keyboard-navigation-starter"&gt;Get starter GitHub repository&lt;/a&gt; to follow along as we dive into the code.You can clone the repository or download the whole file. &lt;/p&gt;

&lt;p&gt;Open code it in your favorite editor and run commands &lt;code&gt;yarn install &amp;amp;&amp;amp; yarn start&lt;/code&gt;. This will run the code in your local environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start of the project
&lt;/h2&gt;

&lt;p&gt;Now we are ready to start coding. Let’s first add title and show items. All the necessary data for the items is in the file &lt;code&gt;items.json&lt;/code&gt;. We can import them into the &lt;code&gt;App&lt;/code&gt; and loop through each one.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import "./App.css";
import items from "./items.json";

function App() {
  return (
    &amp;lt;main className="main"&amp;gt;
      &amp;lt;h1 className="title"&amp;gt;Custom keyboard navigation&amp;lt;/h1&amp;gt;

      &amp;lt;section className="items"&amp;gt;
        {items.map((item) =&amp;gt; {
          return &amp;lt;article&amp;gt;single item&amp;lt;/article&amp;gt;;
        })}
      &amp;lt;/section&amp;gt;
    &amp;lt;/main&amp;gt;
  );
}

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

&lt;/div&gt;



&lt;p&gt;Currently we just display the same text for each item. We can change that in order to display image and checkbox. Open the component &lt;code&gt;Item&lt;/code&gt; inside &lt;code&gt;src/components/Item.tsx&lt;/code&gt; and update the component.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import "./Item.css";

type ItemProps = {
  description: string;
  id: number;
  name: string;
  url: string;
};

function Item(props: ItemProps) {
  const { description, name, url } = props;

  return (
    &amp;lt;div className="item"&amp;gt;
      &amp;lt;input className="input" type="checkbox" name={name} id={name} /&amp;gt;
      &amp;lt;img className="image" src={url} alt={description} /&amp;gt;
    &amp;lt;/article&amp;gt;
  );
}

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

&lt;/div&gt;



&lt;p&gt;We also need to import the &lt;code&gt;Item&lt;/code&gt; into the &lt;code&gt;App&lt;/code&gt; and pass all the props to it. Here we spread all props instead of manually adding each one.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import "./App.css";
import Item from "./components/Item";
import items from "./items.json";

function App() {
  return (
    &amp;lt;main className="main"&amp;gt;
      &amp;lt;h1 className="title"&amp;gt;Custom keyboard navigation&amp;lt;/h1&amp;gt;

      &amp;lt;section className="items"&amp;gt;
        {items.map((item) =&amp;gt; {
          return &amp;lt;Item {...item} /&amp;gt;;
        })}
      &amp;lt;/section&amp;gt;
    &amp;lt;/main&amp;gt;
  );
}

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

&lt;/div&gt;



&lt;p&gt;Now we can add some styles to both components to make the app look better.&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;App.css&lt;/code&gt; we make style main and display items inside a grid.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.main {
  margin: auto;
  max-width: 1000px;
  text-align: center;
}

.items {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 16px;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the &lt;code&gt;Item.css&lt;/code&gt; we style image and absolutely position checkbox.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.item {
  position: relative;
  height: 150px;
  border-radius: 10px;
  overflow: hidden;
  cursor: pointer;
  border: 2px solid lightgray;
}

.image {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.input {
  position: absolute;
  width: 18px;
  height: 18px;
  cursor: pointer;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What we want to do next is to select checkbox when we click on the image. This makes whole user experience much better than just clicking on small checkbox. &lt;/p&gt;

&lt;p&gt;To do that we need to add state inside &lt;code&gt;Item&lt;/code&gt; to track if checkbox is checked. We add &lt;code&gt;onClick&lt;/code&gt; handler to the whole item and manually set checkbox state via &lt;code&gt;checked&lt;/code&gt; property.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { useState } from "react";
import "./Item.css";

type ItemProps = {
  description: string;
  id: number;
  name: string;
  url: string;
};

function Item(props: ItemProps) {
  const { description, name, url } = props;
  const [isChecked, setIsChecked] = useState(false);

  return (
    &amp;lt;div
      className={isChecked ? "item checked" : "item"}
      onClick={() =&amp;gt; setIsChecked(!isChecked)}
    &amp;gt;
      &amp;lt;input
        className="input"
        type="checkbox"
        name={name}
        id={name}
        checked={isChecked}
      /&amp;gt;
      &amp;lt;img className="image" src={url} alt={description} /&amp;gt;
    &amp;lt;/article&amp;gt;
  );
}

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

&lt;/div&gt;



&lt;p&gt;In the code above we have added new class name &lt;code&gt;checked&lt;/code&gt;, so we also need to add it to the &lt;code&gt;Item.css&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.checked {
  border: 2px solid rgb(36, 36, 174);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Add custom keyboard navigation
&lt;/h2&gt;

&lt;p&gt;Currently, when you tab through the app, the focus changes from one checkbox to the other. This is still okay if you have small number of items. Imagine displaying 500 items and trying to navigate via keyboard to the next section of the app. Would you be happy to tab through 500 items just to get to the next section? Probably not. Luckily, there is a better way to navigate through long lists of items.&lt;/p&gt;

&lt;p&gt;The idea is simple. You only have one checkbox with &lt;code&gt;tabIndex=0&lt;/code&gt;  (can be focused) and all the others have &lt;code&gt;tabIndex=-1&lt;/code&gt; (ignored by keyboard navigation). Then we programmatically change &lt;code&gt;tabIndex&lt;/code&gt; for each item based on which arrow the user clicks.&lt;/p&gt;

&lt;p&gt;Let's get back to our code. Inside the &lt;code&gt;App&lt;/code&gt; we add &lt;code&gt;ref&lt;/code&gt; to the &lt;code&gt;main&lt;/code&gt; so that we only listen for keyboard clicks inside it. We also add state for tracking cursor to know which item is currently focused.&lt;/p&gt;

&lt;p&gt;Inside the first &lt;code&gt;useEffect&lt;/code&gt; we add event listener. On each keyboard button press the function &lt;code&gt;handleKey&lt;/code&gt; is fired. Inside it we look for any arrow key press and based on it we modify state of the cursor. To simplify things a bit the &lt;code&gt;numberOfColumns&lt;/code&gt; is hard coded.&lt;/p&gt;

&lt;p&gt;In the second &lt;code&gt;useEffect&lt;/code&gt; we find the correct checkbox inside &lt;code&gt;main&lt;/code&gt; based on it’s name and then focus it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { keyboardKey } from "@testing-library/user-event";
import { useEffect, useRef, useState } from "react";
import "./App.css";
import Item from "./components/Item";
import items from "./items.json";

function App() {
  const itemsRef = useRef&amp;lt;HTMLElement&amp;gt;(null);
  const [cursor, setCursor] = useState(1);
  const numberOfColumns = 4;
  const totalNumberOfFiles = items.length;

  useEffect(() =&amp;gt; {
    const handleKey = (event: keyboardKey) =&amp;gt; {
      if (event.key === "ArrowRight") {
        setCursor((prevCursor) =&amp;gt; {
          if (prevCursor === totalNumberOfFiles) {
            return totalNumberOfFiles;
          }

          return prevCursor + 1;
        });
      }

      if (event.key === "ArrowLeft") {
        setCursor((prevCursor) =&amp;gt; {
          if (prevCursor === 0) {
            return 0;
          }

          return prevCursor - 1;
        });
      }

      if (event.key === "ArrowDown") {
        setCursor((prevCursor) =&amp;gt; {
          if (prevCursor + numberOfColumns &amp;gt; totalNumberOfFiles) {
            return prevCursor;
          }

          return prevCursor + numberOfColumns;
        });
      }

      if (event.key === "ArrowUp") {
        setCursor((prevCursor) =&amp;gt; {
          if (prevCursor - numberOfColumns &amp;lt; 0) {
            return prevCursor;
          }

          return prevCursor - numberOfColumns;
        });
      }
    };

    if (itemsRef.current) {
      const currentCursor = itemsRef.current;

      currentCursor.addEventListener("keyup", handleKey);

      return () =&amp;gt; currentCursor.removeEventListener("keyup", handleKey);
    }
  }, [totalNumberOfFiles, numberOfColumns]);

  useEffect(() =&amp;gt; {
    if (itemsRef.current) {
      const selectCursor = itemsRef.current.querySelector(
        `input[name='item ${cursor}']`
      );
      (selectCursor as HTMLInputElement)?.focus();
    }
  }, [cursor]);

  return (
    &amp;lt;main ref={itemsRef} className="main"&amp;gt;
      &amp;lt;h1 className="title"&amp;gt;Custom keyboard navigation&amp;lt;/h1&amp;gt;

      &amp;lt;section className="items"&amp;gt;
        {items.map((item) =&amp;gt; {
          const tabIndex = cursor === item.id ? 0 : -1;

          return &amp;lt;Item {...item} tabIndex={tabIndex} /&amp;gt;;
        })}
      &amp;lt;/section&amp;gt;
    &amp;lt;/main&amp;gt;
  );
}

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

&lt;/div&gt;



&lt;p&gt;The last thing that we need add is &lt;code&gt;tabIndex&lt;/code&gt; to the &lt;code&gt;Item&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { useState } from "react";
import "./Item.css";

type ItemProps = {
  description: string;
  id: number;
  name: string;
  url: string;
  tabIndex: number;
};

function Item(props: ItemProps) {
  const { description, name, url, tabIndex } = props;
  const [isChecked, setIsChecked] = useState(false);

  return (
    &amp;lt;div
      className={isChecked ? "item checked" : "item"}
      onClick={() =&amp;gt; setIsChecked(!isChecked)}
    &amp;gt;
      &amp;lt;input
        className="input"
        type="checkbox"
        name={name}
        id={name}
        checked={isChecked}
        tabIndex={tabIndex}
      /&amp;gt;
      &amp;lt;img className="image" src={url} alt={description} /&amp;gt;
    &amp;lt;/article&amp;gt;
  );
}

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

&lt;/div&gt;






&lt;p&gt;Thank you for reading this article. Hope you have learned something new.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/eruj22/custom-keyboard-navigation-finished"&gt;Link to the finished project GitHub repository&lt;/a&gt;&lt;/p&gt;

</description>
      <category>react</category>
      <category>a11y</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Pizza Delivery App</title>
      <dc:creator>Jure Prnaver</dc:creator>
      <pubDate>Wed, 12 Jan 2022 21:30:32 +0000</pubDate>
      <link>https://dev.to/eruj22/pizza-delivery-app-2f2d</link>
      <guid>https://dev.to/eruj22/pizza-delivery-app-2f2d</guid>
      <description>&lt;h3&gt;
  
  
  Overview of My Submission
&lt;/h3&gt;

&lt;p&gt;This app is pizza ordering app. You can browse through different pizzerias from my favorite local pizzerias. Goal of the app is to order your selected pizza easily and quickly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Submission Category:
&lt;/h3&gt;

&lt;p&gt;E-Commerce Creation&lt;/p&gt;

&lt;h3&gt;
  
  
  Link to Code
&lt;/h3&gt;

&lt;p&gt;GitHub repository - frontend: &lt;a href="https://github.com/eruj22/pizza-delivery" rel="noopener noreferrer"&gt;https://github.com/eruj22/pizza-delivery&lt;/a&gt;&lt;br&gt;
GitHub repository - backend: &lt;a href="https://github.com/eruj22/pizza-delivery-backend" rel="noopener noreferrer"&gt;https://github.com/eruj22/pizza-delivery-backend&lt;/a&gt;&lt;br&gt;
Working demo: &lt;a href="https://pizza-delivery-33.netlify.app/" rel="noopener noreferrer"&gt;https://pizza-delivery-33.netlify.app/&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Additional Resources / Info
&lt;/h3&gt;

&lt;p&gt;Technologies used:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  ReactJS&lt;/li&gt;
&lt;li&gt;  NodeJS&lt;/li&gt;
&lt;li&gt;  mongoDB&lt;/li&gt;
&lt;li&gt;  Atlas Search&lt;/li&gt;
&lt;li&gt;  Stripe&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Overview of the app
&lt;/h4&gt;

&lt;p&gt;Let's start with the backend. NodeJS backend is connected with mongoDB. There are 3 collections:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Orders: finished orders&lt;/li&gt;
&lt;li&gt;  Pizzas: information about each pizza&lt;/li&gt;
&lt;li&gt;  Pizzerias: basic information about pizzerias&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I also implemented search option. It is made for the pizzas collection and you can search for pizzas name and ingredients. The backend is deployed to Heroku. I have used free option so some users can wait for the dynos to wake up. Well, it's free, you can't argue with that.&lt;/p&gt;

&lt;p&gt;So, when the app finally starts, you will see home page. There you can select from 3 pizzerias. After clicking on one, you will see all the pizzas that it offers. You can freely select which one you would like and in modal select its size. You can see your selected pizzas on the right side.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Figblve66lj1qryfkvagl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Figblve66lj1qryfkvagl.png" alt="Single pizzeria page"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you click order items you are navigated to the checkout page. There you have a form to fill with inputs validated. In the payment section you have option to pay with cash or card. When you choose with cash, you can submit form and pizza should be on its way to you. When you choose card, you need to input credit card number (it's in testing mode). I have used stripe CardElement for displaying card payment element. &lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg77kwj3k8bv0r06pqclj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg77kwj3k8bv0r06pqclj.png" alt="Checkout page"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When your inputs are all correct and you submit your order, then you are navigated to the success page. It informs you that your order was successful. All the information about it is sent to the mongoDB collection named orders.&lt;/p&gt;

&lt;p&gt;One more thing to note. When you are in selected pizzeria page and you navigate to another page, your selected pizzas are deleted. This is because you can't order from many different pizzerias at the same time. &lt;/p&gt;

&lt;p&gt;Last but not least, the search function. It has been there the whole time, hiding in the navigation section. When you input at least 2 letters the autocomplete function is activated (presuming you tried to write pizza name or some ingredient). When you submit the searching term you are navigated to the search page, where you can see all the results. It is using mongoDB search option for the collection of pizzas. &lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpqz2jft8k4q1houcwaug.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpqz2jft8k4q1houcwaug.png" alt="Search page"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you have any additional questions or improvements, write a comment down below.&lt;/p&gt;

</description>
      <category>atlashackathon</category>
      <category>react</category>
      <category>node</category>
      <category>mongodb</category>
    </item>
  </channel>
</rss>
