<?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: Maxim Titov</title>
    <description>The latest articles on DEV Community by Maxim Titov (@titovmx).</description>
    <link>https://dev.to/titovmx</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%2F1055392%2F6a89eee0-515e-4882-bf0e-c0082fafb676.jpg</url>
      <title>DEV Community: Maxim Titov</title>
      <link>https://dev.to/titovmx</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/titovmx"/>
    <language>en</language>
    <item>
      <title>nth-child ninja power</title>
      <dc:creator>Maxim Titov</dc:creator>
      <pubDate>Mon, 08 Jul 2024 16:07:05 +0000</pubDate>
      <link>https://dev.to/titovmx/nth-child-ninja-power-5ebc</link>
      <guid>https://dev.to/titovmx/nth-child-ninja-power-5ebc</guid>
      <description>&lt;p&gt;In this article, I will share a small example of the &lt;code&gt;:nth-child()&lt;/code&gt; pseudo-class usage that allows one to efficiently work with dynamic lists.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem
&lt;/h2&gt;

&lt;p&gt;Let's first look at the problem I had. My task was to render a tree with different types of nodes. One of the node types presents a list, and I wanted to add alternating zebra-like backgrounds to them. Below is the visual representation of the UI I've been aiming for.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvp7rw2hhkfojxwnsysx4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvp7rw2hhkfojxwnsysx4.png" alt="Tree fragment diagram" width="800" height="369"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ok, it seems that I should use a common pattern to match even nodes with an offset - &lt;code&gt;2n + 4&lt;/code&gt; works for this example.&lt;/p&gt;

&lt;p&gt;However, the difficulty is that this list of nodes is dynamic and the nodes can be different. For instance, not every node has a description, and, in this case, offset changes. If a user has permission to edit nodes, there will be an additional node after the description to select all node items for bulk editing. All of this makes offset of node items unpredictable.&lt;/p&gt;

&lt;p&gt;According to its definition the pseudo-class &lt;code&gt;:nth-child()&lt;/code&gt; matches elements based on the indexes of the elements in the child list of their parents. It means that the selector &lt;code&gt;.nodeItem:nth-child(even)&lt;/code&gt; will not work as might be expected. It will highlight only &lt;code&gt;.nodeItem&lt;/code&gt; nodes but analyse whether the element is even or not among all siblings.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi0xqluvygc657pdv1u6h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi0xqluvygc657pdv1u6h.png" alt=":nth-child(even) applied in the lists with different structure" width="800" height="255"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Solution
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;:nth-child()&lt;/code&gt; has syntax to match selectors though. The &lt;code&gt;:nth-child()&lt;/code&gt; argument can not only describe the function to calculate indices but also restrict them by any selector. For the node example, it looks the following way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.nodeItem:nth-child(even of .nodeItem) {}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now it truly scans only &lt;code&gt;.nodeItem&lt;/code&gt; nodes and applies styles to even nodes among them!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsaenvcuzsbo67btq0hhb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsaenvcuzsbo67btq0hhb.png" alt=":nth-child(even of) applied in the lists with different structure" width="800" height="256"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While it is very useful major browsers have supported this syntax only since the spring of 2023 so please still be careful and consider JavaScript solutions if you need to apply a similar approach to styling your dynamic lists with support for older browsers.&lt;/p&gt;

</description>
      <category>frontend</category>
      <category>webdev</category>
      <category>css</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>5 engineering interview hints</title>
      <dc:creator>Maxim Titov</dc:creator>
      <pubDate>Mon, 01 Jul 2024 06:06:23 +0000</pubDate>
      <link>https://dev.to/titovmx/5-engineering-interview-hints-3051</link>
      <guid>https://dev.to/titovmx/5-engineering-interview-hints-3051</guid>
      <description>&lt;p&gt;Today, I want to share a few quick hints to help you frame your job search preparation and better understand interviewers' expectations. Recently, I participated in a &lt;a href="https://www.youtube.com/live/NT3bAtdBcGg?si=pvC32P1aeQAmUCUq"&gt;podcast (in Russian)&lt;/a&gt; devoted to frontend interviews and preparation, where we discussed each type of interview in detail. Despite their differences, these interviews share many common elements.&lt;/p&gt;

&lt;p&gt;I have over 10 years of engineering experience. I've interviewed candidates at various levels and been through the interview process myself. Last year, I navigated the job market and understand how challenging it is, even for senior engineers, especially if you are located outside the US or EU and looking for relocation and remote options.&lt;/p&gt;

&lt;p&gt;So let’s go to the most interesting part.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Minify feedback loop
&lt;/h2&gt;

&lt;p&gt;I often see engineers preparing for a job search by trying to fill every gap in their knowledge. They read computer science books, take courses, and experiment with unfamiliar frameworks and libraries. While continuous learning is great, it’s not the most effective approach for job searching. You don’t need to know everything to pass an interview. Interviews have their own rules, and it is beneficial to practice these specific skills as early as possible.&lt;/p&gt;

&lt;p&gt;Mock interviews are invaluable for practicing and getting feedback on your answers, communication, and the actual knowledge gaps you need to address. You can ask friends, ex-colleagues, or people in your network to conduct different types of interviews for you. Another option is using online services like &lt;a href="http://pramp.com/"&gt;pramp.com&lt;/a&gt;, where people interview each other. Eventually, you can also take on the role of the interviewer to gain insight into what signals are expected from candidates.&lt;/p&gt;

&lt;p&gt;I also recommend investigating the job market from day one and creating two lists. The first list should include top-priority companies where you really want to work. The second list should consist of less interesting options. Start with the second list to get a feel for the process, validate your CV, and refine your interview answers. This way, you can gather valuable information about potential questions without being too disappointed if things don't go as planned.&lt;/p&gt;

&lt;p&gt;So try to get quick feedback on your readiness and start real interviews earlier to not be frustrated about the fact that the interviews are different from what you expected after a half year of tech preparation.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Focus on communication
&lt;/h2&gt;

&lt;p&gt;As I mentioned earlier, interviews have their own rules. The process is not perfect, and interviewers have very limited time to assess your skills and level. This is why practicing communication is crucial. Interviewers look for signals about the tasks you've completed, the scope of your work, whether you just wrote code or organized large projects, or resolved company-level issues. It is important to focus on the value you have delivered. The helpful framework to tell about your experience is STAR - describe the project &lt;strong&gt;s&lt;/strong&gt;ituation, &lt;strong&gt;t&lt;/strong&gt;ask you need to complete, &lt;strong&gt;a&lt;/strong&gt;ctions you are performed for it, and final &lt;strong&gt;r&lt;/strong&gt;esult&lt;/p&gt;

&lt;p&gt;For technical interviews, interviewers expect not only specific knowledge but also problem-solving skills, the ability to refine requirements, recognize task constraints, clearly deliver solutions, and test them.&lt;/p&gt;

&lt;p&gt;You will need to articulate your experience and approach interview tasks as real problems you are solving in your usual responsibilities.&lt;/p&gt;

&lt;p&gt;I strongly recommend speaking out loud during your tech interviews, whether it is live coding challenges, algorithmic sections, or system design interviews. Demonstrate your thought process and clearly explain your solutions. You may not always have enough time to complete the tasks, but discussing your approach helps the interviewer understand that you know how to solve it.&lt;/p&gt;

&lt;p&gt;Remember, the interview is a dialogue, not a monologue. Always start by clarifying the requirements to ensure you understand them correctly. For coding and algorithmic tasks, explain your approach or write the algorithm in pseudocode. For system design, begin with a high-level overview before diving into details. If you go off track or encounter difficulties, the interviewer can assist you if they understand your current approach.&lt;/p&gt;

&lt;p&gt;The best way to gauge how well you communicate your knowledge and skills is again to get feedback from mock and real interviews.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Prepare for specific environment
&lt;/h2&gt;

&lt;p&gt;The specific challenge of interviews is that you will need to solve problems in an unfamiliar environment.&lt;/p&gt;

&lt;p&gt;Previously, you would write solutions on paper or a whiteboard, but now most interviews are remote. The coding and algorithmic sections are usually conducted using online editors with limited syntax highlighting and code suggestions. You must be comfortable writing code in your chosen language, stay fluent, and be able to debug and test your solutions. Practice on platforms like &lt;a href="https://leetcode.com/"&gt;leetcode&lt;/a&gt;, &lt;a href="https://codepen.io/"&gt;CodePen&lt;/a&gt; or &lt;a href="https://codesandbox.io/"&gt;CodeSandbox&lt;/a&gt; to solve algorithmic tasks and code challenges.&lt;/p&gt;

&lt;p&gt;System design interviews typically require you to write down requirements, draw diagrams, and create data models. Practice on such platforms to avoid wasting valuable time figuring out how to draw your solution. I recommend Excalidraw, a minimalistic and quick-to-go tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Show your interest
&lt;/h2&gt;

&lt;p&gt;Interviews usually include 5-10 minutes at the end for you to ask questions about the company. Remember, the interview is bidirectional - you're also evaluating them as your potential workplace. Research the company, understand what they do, and show your interest by asking insightful questions. For example, identify their competitors, analyze the company's unique selling points, and ask how they achieve these and what challenges they face.&lt;/p&gt;

&lt;p&gt;Your questions can also signal your level of expertise. Ask technical questions about their engineering processes and solutions, as well as their product strategies. It's best to prepare these questions in advance.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Stay positive
&lt;/h2&gt;

&lt;p&gt;The interview process can be tough and feel like a full-time job. It’s easy to get frustrated by questions and challenges you don't like, but staying positive is crucial. Everyone involved in hiring is trying to find the best candidate, and they have limited time to assess each one. Your best strategy is to remain positive, empathic and friendly, share well-prepared stories about your experience, and send the right signals during technical interviews.&lt;/p&gt;

&lt;p&gt;Do not forget to rest properly and avoid burnout during this process.&lt;/p&gt;




&lt;p&gt;Hope you find it useful! Write in comments that you would recommend others from your experience. Good luck with your upcoming interviews!&lt;/p&gt;

</description>
      <category>softwareengineering</category>
      <category>interview</category>
      <category>career</category>
    </item>
    <item>
      <title>Automate your changelog and monthly product updates in your blog with AI</title>
      <dc:creator>Maxim Titov</dc:creator>
      <pubDate>Tue, 23 May 2023 07:56:53 +0000</pubDate>
      <link>https://dev.to/titovmx/automate-your-changelog-and-month-product-updates-in-your-blog-with-ai-498p</link>
      <guid>https://dev.to/titovmx/automate-your-changelog-and-month-product-updates-in-your-blog-with-ai-498p</guid>
      <description>&lt;h2&gt;
  
  
  What I built
&lt;/h2&gt;

&lt;p&gt;The solution helps open-source maintainers to track changes in the product and tell the users about the updates.&lt;/p&gt;

&lt;h3&gt;
  
  
  Category Submission: Maintainer Must-Haves
&lt;/h3&gt;

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

&lt;p&gt;&lt;a href="https://changelog-automation.vercel.app/"&gt;Blog with updates&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Screenshots
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Changelog metadata is added automatically when a new Pull Request is created. &lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Vm_14-bQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0bmbrsstab1bdnbo58an.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Vm_14-bQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0bmbrsstab1bdnbo58an.png" alt="Automatically added changelog metadata" width="800" height="180"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A new version is added automatically when Pull Request is merged into the main branch. &lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--droZg2Dy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ku4m6qgt5z5g45x4fjai.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--droZg2Dy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ku4m6qgt5z5g45x4fjai.png" alt="Automatically added version" width="800" height="221"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;New blog post is created based on the changelog history. &lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--cXSLvlL2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ues571oaq99mpw7so41m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--cXSLvlL2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ues571oaq99mpw7so41m.png" alt="Pull request with a new blog post" width="800" height="472"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Pull request merged, blog post is on &lt;a href="https://changelog-automation.vercel.app/2023_April/"&gt;production&lt;/a&gt;! &lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7NnqscM0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qpgyjmo84hojma3gh3ie.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7NnqscM0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qpgyjmo84hojma3gh3ie.png" alt="New blog post deployed" width="800" height="814"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;Maintainers have never had enough time to handle all the issues with the projects. They have to play different roles when they work on open-source projects. While it is necessary to develop and test the product itself, and solve the issues opened by the users, the maintainers have to keep history of changes and notify everyone with updates. Moreover, we have a great writer for blog posts. His name is... Sure, ChatGPT!&lt;/p&gt;

&lt;p&gt;I created a repository that demonstrates how GitHub Actions can help the maintainers to automate all the routines related to the Changelog of the project and create nice blog posts with AI telling the users what happened to the product for the last month.&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://github.com/titovmx/changelog-automation"&gt;Source code&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://github.com/titovmx/changelog-automation/blob/main/LICENSE"&gt;MIT&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Background (What made you decide to build this particular app? What inspired you?)
&lt;/h2&gt;

&lt;p&gt;I would appreciate having such tools some time ago when I had been working on open-source projects with a small team. It is quickly getting clear that even if your project is awesome you have to find a way to tell the world about it and provide updated information to your existing users. However, developers spent most of the time on improvements and fixes. Besides, they need to provide clear and detailed documentation, handle user feedback, and not forget to test the solution before merging a breaking API change... So the communication to the users can be lost, they just would not know what and when you changed. A Changelog file helps the team and users follow all the changes, and blog posts are able to regularly deliver useful information to a large audience so everyone will stay in touch and you don't need anymore to ping every user to try your new features.&lt;/p&gt;

&lt;h3&gt;
  
  
  How I built it (How did you utilize GitHub Actions or GitHub Codespaces? Did you learn something new along the way? Pick up a new skill?)
&lt;/h3&gt;

&lt;p&gt;The demo assumes having a monorepo with the source of the main product, e.g. some awesome marketplace, a company blog with monthly product updates, and a set of GitHub actions.&lt;/p&gt;

&lt;p&gt;The solution includes three parts:&lt;/p&gt;

&lt;p&gt;I. When the developer creates Pull Request with a new awesome feature we want to verify that changes are added to the Changelog file. Changelog.md is located at the root of the project and contains a list of updates in the following format:&lt;/p&gt;

&lt;p&gt;[version] [description] [date]&lt;/p&gt;

&lt;p&gt;Besides, the newly opened PR adds metadata on the top of the Changelog using &lt;a href="https://semver.org/"&gt;Semantic Versioning&lt;/a&gt; with a version level (MAJOR | MINOR | PATCH) and description. By default, it adds a new commit with a "PATCH" change so the maintainer could check and update the PR before the merge.&lt;/p&gt;

&lt;p&gt;The workflow called &lt;code&gt;add-changelog-metadata&lt;/code&gt; is responsible for it in the demo repository.&lt;/p&gt;

&lt;p&gt;II. The next workflow called &lt;code&gt;add-changelog-version&lt;/code&gt; will update changelog file after it is merged to the main branch, and transform metadata into the new version line. The previous version is increased by SemVer specification and the date of changes delivered to production is added.&lt;/p&gt;

&lt;p&gt;III. The final workflow works by schedule and adds a new blog post written by AI. On the first day of a new month, the workflow takes the changes for the previous month, passes it to ChatGPT API, and returns his nice answer as a blog post content. The new PR would be created so the maintainer could look through it and add edits. A blog is built with Gatsby and hosted with Vercel &lt;a href="https://changelog-automation.vercel.app/"&gt;here&lt;/a&gt; so right after the merge changes could be delivered to the website!&lt;/p&gt;

&lt;p&gt;I've been very excited during this small project to play with these tools, deep dive into GitHub Actions API to find solutions for building these workflows and, of course, feel the power of the content created by AI.&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://semver.org/"&gt;Semantic Versioning&lt;/a&gt;&lt;br&gt;
&lt;a href="https://platform.openai.com/docs/guides/completion"&gt;ChatGPT completion API&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.gatsbyjs.com/starters/gatsbyjs/gatsby-starter-blog"&gt;Gatsby Starter Blog&lt;/a&gt;&lt;br&gt;
&lt;a href="https://vercel.com"&gt;Vercel&lt;/a&gt;&lt;/p&gt;

</description>
      <category>githubhack23</category>
      <category>webdev</category>
      <category>chatgpt</category>
    </item>
    <item>
      <title>Chart library design</title>
      <dc:creator>Maxim Titov</dc:creator>
      <pubDate>Mon, 24 Apr 2023 05:41:34 +0000</pubDate>
      <link>https://dev.to/titovmx/chart-library-design-5dal</link>
      <guid>https://dev.to/titovmx/chart-library-design-5dal</guid>
      <description>&lt;p&gt;Data visualization is a common task in modern software. Users can better understand large data sets visually represented and get meaningful insights. There are a bunch of libraries that help to solve it. They render different types of charts based on data that is passed into them. Usually, chart libraries use D3.js under the hood. It’s a library in charge of drawing particular chart primitives. D3.js can render data using SVG and Canvas, and later I will discuss the difference between these two approaches.&lt;/p&gt;

&lt;p&gt;In this article, I am describing a solution for the problem of rendering different types of charts, and I will also rely on low-level rendering provided by D3.js. It might be useful not only if you need to build it but also as an exercise for preparing for a front-end engineer interview.&lt;/p&gt;

&lt;h3&gt;
  
  
  Requirements
&lt;/h3&gt;

&lt;p&gt;First, let’s define what exactly we want to build. We want to create such a component that will render a chart of a certain type based on any data set. We also want to customize how it looks, and we want to interact with the data. It means the component should do the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Take data and render charts of different types (e.g. bar, line, pie, dot plots).&lt;/li&gt;
&lt;li&gt;Provide styling for different groups of data, e.g. different bars could be of different colors.&lt;/li&gt;
&lt;li&gt;Change the data scale by zooming in and out. The default min/max and step values should be calculated based on the data set or provided by a chart user.&lt;/li&gt;
&lt;li&gt;Show a popup with the details about the section of the chart on which it is clicked (x and y values).&lt;/li&gt;
&lt;li&gt;Render title and legend.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We also can mention some non-functional requirements:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Scalable, i.e. adding new types of charts and interactions should extend provided API without implementation modification. It responds to &lt;a href="https://en.wikipedia.org/wiki/Open%E2%80%93closed_principle"&gt;the open-closed principle (OCP)&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Be performant, i.e. renders and interactions are fast.&lt;/li&gt;
&lt;li&gt;Be responsive and rendered fine on various devices.&lt;/li&gt;
&lt;li&gt;Be accessible.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There could be other features and customizations, however, we will focus on providing these since they look the most crucial and the final design is considered to be extendable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Models
&lt;/h3&gt;

&lt;p&gt;Now we know what we want to build, let’s move to the tech details. We will map some of the mentioned entities into models that we will use in our code. I use Typescript syntax to describe it but you can think about it as pseudocode.&lt;/p&gt;

&lt;p&gt;There are some basic models below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ChartModel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;xScaleDomain&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;ScaleDomain&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;yScaleDomain&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;ScaleDomain&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;showX&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;showY&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;showPopup&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ChartType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bar&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;line&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dot&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ChartSeriesModel&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;TData&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TData&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ChartType&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;xAxis&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;number&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;yAxis&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;number&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;StyleFn&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;LegendModel&lt;/span&gt; &lt;span class="o"&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;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ScaleDomain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;min&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;max&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We have two basic models. One is for the entire chart and another is for the series that we want to draw there. All fields in &lt;code&gt;ChartModel&lt;/code&gt; are optional, their purpose is to configure the features of the chart and customize the UI.&lt;/p&gt;

&lt;p&gt;The required fields &lt;code&gt;type&lt;/code&gt;, &lt;code&gt;data&lt;/code&gt;, &lt;code&gt;xAxis&lt;/code&gt;, and &lt;code&gt;yAxis&lt;/code&gt; in &lt;code&gt;ChartSeriesModel&lt;/code&gt; are necessary to understand what the user wants to render. The most interesting is what can be passed as data. There could be two approaches:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Force the user to prepare data of a certain format that our component will be working on. E.g., we can receive an array of objects, and if we know properties for the x and y axes we can group data by them and render these points (values).&lt;/li&gt;
&lt;li&gt;Receive any data and accessor functions that will return sets of values for the x and y axes.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Depending on a specific task it makes sense to choose one of the approaches to follow simpler implementation but I want to make this design as universal as possible so the model includes generic &lt;code&gt;TData&lt;/code&gt; which could be anything and use &lt;code&gt;xAxis&lt;/code&gt; and &lt;code&gt;yAxis&lt;/code&gt; methods to process the data.&lt;/p&gt;

&lt;p&gt;We will need a couple more models for styling charts based on the data point, so we consider x and y coordinates and return a style object that contains only color for now but can be extended with more (e.g. thickness, label color and position, etc.).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Point&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;StyleObj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;StyleFn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Point&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;StyleObj&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Now we have our data models ready, let’s look at the component tree we are going to create for our chart library.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--cr3X4r7Q--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8baz3m1cdb3umusekbkd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--cr3X4r7Q--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8baz3m1cdb3umusekbkd.png" alt="Chart library component tree" width="800" height="247"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The root component &lt;code&gt;Chart&lt;/code&gt; is a wrapper for all other necessary parts: two axes components, legend, and set of series. Based on input it will choose what to render. The required part is &lt;code&gt;ChartSeries&lt;/code&gt;. This component will choose the D3.js function based on &lt;code&gt;type&lt;/code&gt;, pass the data and style function into it and render returned element.&lt;/p&gt;

&lt;p&gt;Basically, we have two layers here. The first one is a component layer whose purpose is to provide interactivity and re-render DOM elements on change of the data or settings. Also, it will listen to DOM events to render popups on click and update zooming on the scroll.&lt;/p&gt;

&lt;p&gt;The second layer is the drawing layer using D3.js. Functions written with D3.js will return elements for any particular &lt;code&gt;ChartSeriesModel.type&lt;/code&gt; as well as for axes. D3.js allows drawing an axis by setting min/max values for data, and min/max values in pixels. It matches passed values, calculates equal ticks, and draws them with the axis line. In this design, &lt;code&gt;ChartSeries&lt;/code&gt; is responsible for ingesting data and providing D3.js functions with scaling values. These settings can be manually defined if the chart component user sets their own &lt;code&gt;min&lt;/code&gt;, &lt;code&gt;max&lt;/code&gt;, and &lt;code&gt;step&lt;/code&gt; with &lt;code&gt;ChartModel.scaleDomain&lt;/code&gt; object.&lt;/p&gt;

&lt;p&gt;This design allows extending library functionality as much as we need. For instance, we want to add a crosshair that shows lines parallel to axes and coordinates under the cursor. It requires adding a new component within the chart, receiving its configuration, and rendering. New D3.js functions should be provided for adding a new type of chart.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: the interaction layer can be implemented with vanilla JS or any framework. Depending on the specific implementation the UI could be composable according to this framework's best practices. For example, React JSX showing bar chart with x axis and legend could look like this:&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Chart&lt;/span&gt; &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;chartModel&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
 &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ChartSeries&lt;/span&gt; &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;chartSeriesModel&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
 &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ChartAxis&lt;/span&gt; &lt;span class="na"&gt;position&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"bottom"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
 &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Legend&lt;/span&gt; &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;legendModel&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Chart&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  API
&lt;/h3&gt;

&lt;p&gt;The API of components can be described after we defined them and the models earlier. The &lt;code&gt;Chart&lt;/code&gt; component should take the &lt;code&gt;ChartModel&lt;/code&gt; as input. From these configuration data, it gets the state with zoom and scale settings.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;Chart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ChartModel&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}):&lt;/span&gt; &lt;span class="nx"&gt;HTMLElement&lt;/span&gt;

&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ChartState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;zoom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// default value is 1&lt;/span&gt;
  &lt;span class="nl"&gt;onZoomChange&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;xScale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;d3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Scale&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;yScale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;d3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Scale&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;The &lt;code&gt;zoom&lt;/code&gt; setting will be used to tune the scale by user interaction with a chart, e.g. handling a scroll event. &lt;code&gt;xScale&lt;/code&gt; and &lt;code&gt;yScale&lt;/code&gt; could be either built from scale domain values defined by the user in &lt;code&gt;ChartModel&lt;/code&gt; or calculated automatically based on the data provided for &lt;code&gt;ChartSeries&lt;/code&gt; components. They are D3.js scales that can be of different types for different charts. Their main purpose is to map domain values to pixels on the screen.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ChartSeries&lt;/code&gt; should take the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;ChartSeries&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;TData&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ChartSeriesModel&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;TData&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;xScale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;d3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Scale&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;yScale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;d3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Scale&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}):&lt;/span&gt; &lt;span class="nx"&gt;HTMLElement&lt;/span&gt;

&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ChartSeriesState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;datum&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Point&lt;/span&gt;&lt;span class="o"&gt;&amp;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;The &lt;code&gt;model&lt;/code&gt; describes the raw data and functions to access values for the x and y. &lt;code&gt;ChartSeries&lt;/code&gt; should calculate an array of points and pass it into D3.js function that will draw a particular chart of a passed &lt;code&gt;model.type&lt;/code&gt;. The &lt;code&gt;datum&lt;/code&gt; term is used to describe input for chart function. We also should pass &lt;code&gt;xScale&lt;/code&gt; and &lt;code&gt;yScale&lt;/code&gt; to map values into their positions on the chart.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ChartAxis&lt;/code&gt; is similar for both axes which will be differentiated by the &lt;code&gt;position&lt;/code&gt; value.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;ChartAxis&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;top&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;right&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bottom&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;left&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;d3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Scale&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}):&lt;/span&gt; &lt;span class="nx"&gt;SVGElement&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;HTMLCanvasElement&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It also has &lt;code&gt;scale&lt;/code&gt; to properly draw an axis and place ticks with domain values on it. It also should react to scale changes. The &lt;code&gt;position&lt;/code&gt; just sets where we want to see the axis relative to the chart.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;Legend&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;LegendModel&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;HTMLElement&lt;/span&gt;
&lt;span class="p"&gt;}):&lt;/span&gt; &lt;span class="nx"&gt;HTMLElement&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Legend&lt;/code&gt; could render either a default view to explain different charts with their coloring (default or based on the &lt;code&gt;style&lt;/code&gt; function provided for the &lt;code&gt;ChartSeries&lt;/code&gt;) or a custom view provided with a render property.&lt;/p&gt;

&lt;h3&gt;
  
  
  Evaluation
&lt;/h3&gt;

&lt;p&gt;Well, let’s check that all functional requirements are done at this moment. We provided an opportunity to render different types of charts with a custom look by providing style callback and to react to user interactions such as zooming in/out and clicking to show popups. It is also able to render a title and a legend.&lt;/p&gt;

&lt;p&gt;The component tree structure allows combining different blocks and extending the functionality by adding new ones. New types of charts can be added by implementing new D3.js functions and including them in &lt;code&gt;ChartFnFactory&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A good performance can be achieved by smart strategies rendering the data. Basically, usage of one of the frameworks will be beneficial since they care about it from the box and tries to minimize updates on the page. To help with that we could memoize calculated datum, trigger axes updates only when scaling is changed, and avoid re-rendering of static parts while data is the same (e.g. legend).&lt;/p&gt;

&lt;h3&gt;
  
  
  Optimizations
&lt;/h3&gt;

&lt;p&gt;There are a few more optimizations we have to discuss to achieve better performance, responsiveness, and accessibility.&lt;/p&gt;

&lt;p&gt;Since users could render popups over &lt;code&gt;Chart&lt;/code&gt; these elements should have absolute positioning. Using &lt;code&gt;translate&lt;/code&gt; CSS function to move them horizontally and vertically will be better from a performance perspective.&lt;/p&gt;

&lt;p&gt;We would need height and width values in a few places to properly render axes and charts. E.g. bar height could be set up with D3.js in the following way &lt;code&gt;.attr("height", function(d) { return height - yScale(d.value); });&lt;/code&gt; where height is related to the chart. So the height and weight should be obtained from the parent element where it is placed and should listen to its change to be responsive.&lt;/p&gt;

&lt;p&gt;The next range of optimizations is about accessibility. We actually already have a title and legend that describes our chart. It would be probably a nice idea to make a &lt;code&gt;title&lt;/code&gt; to be required and additionally have &lt;code&gt;showTitle: boolean = true&lt;/code&gt;. So even If a user switches it off we could add &lt;code&gt;aria-label&lt;/code&gt; for screen readers. Likewise, we want to add &lt;code&gt;aria-label&lt;/code&gt; for the legend element to make it clear.&lt;/p&gt;

&lt;p&gt;We should verify that the colors that we use to draw charts by default contrast with the background.&lt;/p&gt;

&lt;p&gt;Additionally, we could take care of describing axes and values better. Axes should have labels and values for ticks on them. Also, the data on the chart itself could be labeled to describe it for screen readers and better visual perception.&lt;/p&gt;

&lt;h3&gt;
  
  
  SVG vs Canvas
&lt;/h3&gt;

&lt;p&gt;As was mentioned in the beginning D3.js library gives us a choice of how to render the data. Let’s consider the difference between SVG and Canvas, and their pros and cons.&lt;/p&gt;

&lt;p&gt;SVG (scalable vector graphics) is an XML-based format that is similar to HTML and will be a part of a DOM tree with many shapes as children. The browser can access these shapes, bind event listeners, and manipulate and update them as with any other DOM node.&lt;/p&gt;

&lt;p&gt;Canvas gives API to imperatively create paintings which results in a single element being added to the HTML document.&lt;/p&gt;

&lt;p&gt;Due to this core difference, the interaction with SVG is much simpler, e.g. click event can be bonded to a node and get it immediately while canvas provides us only with coordinates (x, y) on its surface. Mapping coordinates to drawn elements require additional code. One of the strategies is to put an invisible canvas behind the real where all elements are colored differently so that they can be mapped to the datum array.&lt;/p&gt;

&lt;p&gt;The downside of having many nodes in the DOM is that it requires more effort to render and update them in the browser. Here the usage of Canvas can be beneficial because it could quickly redraw the entire scene.&lt;/p&gt;

&lt;p&gt;SVG will provide better responsiveness, its nodes could change their dimensions as well as other DOM nodes depending on provided styling. Canvas again requires complete redrawing.&lt;/p&gt;

&lt;p&gt;Data binding is specific to D3.js library. Using SVG users map their data to shapes, so they are bound and can be automatically updated afterward. The data and their rendering are tightly coupled. It is different for Canvas which gives an opportunity to draw either by a set of imperative commands or create a set of dummy elements and bind them to data. While data binding makes it possible to select, enter and exit dummy elements like it is done with SVG API rendering is detached from this cycle. So the library user has to additionally care about how and when to redraw elements of a canvas scene.&lt;/p&gt;

&lt;p&gt;To sum up, SVG is simpler to achieve interactivity and responsiveness requirements and has the benefits of data binding in D3.js. Canvas can be more performant in case of having a lot of nodes in one chart. For this design I would choose SVG, and consider Canvas for only specific cases that require rendering many nodes, e.g. let’s stick with 100 nodes threshold for it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;In this article, we discussed how the extendable chart library could be built. Also, we discussed the advantages and disadvantages of visualization with SVG and Canvas. I hope you will find it useful for you and I would be glad to hear your comments and ideas on how this solution could be improved.&lt;/p&gt;

</description>
      <category>frontend</category>
      <category>programming</category>
      <category>designsystem</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>System design behind a messenger app</title>
      <dc:creator>Maxim Titov</dc:creator>
      <pubDate>Thu, 06 Apr 2023 09:21:53 +0000</pubDate>
      <link>https://dev.to/titovmx/system-design-behind-a-messenger-app-2268</link>
      <guid>https://dev.to/titovmx/system-design-behind-a-messenger-app-2268</guid>
      <description>&lt;p&gt;We daily use messenger applications such as Whatsapp and Telegram to chat with our friends. They all have a few design solutions behind the scene that directly affect user experience. In this article, I am describing some features visible from a user perspective and explaining tech details under the hood. However, it is not a complete system design and there are a lot of topics where you could dive much deeper.&lt;/p&gt;

&lt;h3&gt;
  
  
  Message delivery statuses
&lt;/h3&gt;

&lt;p&gt;All messengers indicate a state of a message. The application provides a user with information on whether the message was sent, delivered, and read by a recipient. This is very good seen on Whatsapp.&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%2Fnlhubwo06zx41m9tlws0.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnlhubwo06zx41m9tlws0.gif" alt="Status of the message changes on Whatsapp"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It shows a few stages the message goes through:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;When a user just clicked a Send button the message is sent from a client device to web servers (clock icon).&lt;/li&gt;
&lt;li&gt;When the message came to a server it sends a notification back to the client. At that moment icon changed to a single check.&lt;/li&gt;
&lt;li&gt;The web server looks for a connection that is established between it and a recipient and sends a message. The recipient client device sends back a notification to a server and after that, the server can send a notification to the sender that the message was delivered and the icon changed to double-check.&lt;/li&gt;
&lt;li&gt;Eventually, the recipient opens an application and reads the message. His device sends one more notification to the server and the server communicates to the sender that the message was read. The icon changed to blue-colored double-check.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The scheme below illustrates this process and brings a few more necessary design blocks to the further discussion.&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%2Fzkm6ezzd61l7q0fj5awd.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%2Fzkm6ezzd61l7q0fj5awd.png" alt="Message delivery design"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The messenger app has to handle more than 100 billion messages daily or roughly a million per second. The messenger app follows the distributed design to take such a load and the important part of it is Load Balancer (LB). Load Balancer applies a particular strategy to equally distribute effort between different servers.&lt;/p&gt;

&lt;p&gt;Messenger aims to quickly deliver messages between users and servers. Web sockets are used to ensure it. Web socket is a protocol providing duplex communication over a TCP connection. Keeping a web socket connection is cost cheaper than an HTTP connection. The client and server subscribe to each other and wait for new messages. That’s why all arrows on the scheme are bi-directional.&lt;/p&gt;

&lt;p&gt;Different users might be connected to different web servers so we need to have Web Socket Manager. This service is responsible for providing information about connections and routing messages to a proper web server.&lt;/p&gt;

&lt;p&gt;If the recipient is offline the message should be stored. The database should be optimized for frequent write and delete operations. Delete operation performed after the client is online and all messages are delivered to him. NoSQL database will be a choice for this case, e.g. some messengers use HBase and Cassandra to solve the task.&lt;/p&gt;

&lt;h3&gt;
  
  
  Background data fetching and strong consistency
&lt;/h3&gt;

&lt;p&gt;The web socket protocol allows messenger apps to keep connections alive for a long time and load messages even if a user is not active right now. Mobile apps often stay in the background and are able to load new messages if your device is connected to the network. You could notice that you usually see messages immediately after you unblocked the phone and opened the app. Let's look at the desktop client that was closed for a long time and there are no background processes.&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%2Fvh89k3nwoci59c7myosh.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvh89k3nwoci59c7myosh.gif" alt="Consistent loading of new messages"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this recording, the application opens a web socket connection only after its launch. When the connection is established new messages are received from the server. It is loaded not in one batch but one by one. It follows us to another important requirement of messenger system design. It is consistency. The sequence of messages is critical. It would be strange if a recipient gets messages in order different from the original, also messages can’t be lost. The consistency of this application is to provide messages in a strict order. There is a &lt;a href="https://en.wikipedia.org/wiki/CAP_theorem" rel="noopener noreferrer"&gt;CAP theorem&lt;/a&gt; saying that any distributed data store can provide only two of the following three characteristics: Consistency, Availability, and Partition Tolerance. The applications must provide Partition tolerance, hence they have to choose between Consistency and Availability. Messenger services choose Consistency over Availability. That’s why we see the picture in the figure above when the user has to wait a few seconds for chats to be loaded.&lt;/p&gt;

&lt;h3&gt;
  
  
  Downloaded files caching
&lt;/h3&gt;

&lt;p&gt;We discussed a few aspects of chatting, however, messengers also allow users to share media files. The last bit is about optimizing network bandwidth. Files are obviously larger than messages and it would be a nice idea to minimize the times when a user has to upload and download them. When the user uploads a file the server stores it and calculates a checksum that is unique for any particular file. If a user uploads a file that is already stored on the server, the application will look at the checksum and prevent duplication. From the user's perspective, we can try to repeat the following steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Upload the file. I use Telegram Saved messages to check it.&lt;/li&gt;
&lt;li&gt;Remove it and clear the device cache.&lt;/li&gt;
&lt;li&gt;Upload the file again.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There will be a noticeable difference between the first and second upload attempts, the second one is almost immediate.&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%2Fqaq7i1d4obhp2gelb5ia.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqaq7i1d4obhp2gelb5ia.gif" alt="File loading for the first time"&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%2F2bqrvl1c0l13xugma3qs.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2bqrvl1c0l13xugma3qs.gif" alt="File loading for the second time"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: the speed of checking for duplication could depend on the user’s geolocation. Distributed systems have a lot of servers around the world so the time of response (when we talk about distributed system design we usually use the term &lt;a href="https://en.wikipedia.org/wiki/Latency_(engineering)" rel="noopener noreferrer"&gt;latency&lt;/a&gt;) can be actually higher. If a user uploads the duplicate from a location that is different from the origin it might take more time because the closest server doesn’t have information about it.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Another thing to mention is Content Delivery Network (CDN). CDNs are used to store static content such as images and videos. They are based on the network infrastructure and allow to put the files closer to users geographically. Hence, the content is delivered faster and latency is lower.&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%2Fif6vooy0gpdl5pkblrod.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%2Fif6vooy0gpdl5pkblrod.png" alt="CDN usage"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;We talked about a few features of messenger applications that you see daily when you are chatting with your friends, and now you can have a better understanding of system blocks and design decisions that provide us with such user experience.&lt;/p&gt;

&lt;p&gt;Thank you for taking the time to read this article. I would love to see your feedback and favorite app features which internals you would like to know in the comments!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>systemdesign</category>
      <category>architecture</category>
      <category>programming</category>
    </item>
    <item>
      <title>What’s wrong with HTML number input</title>
      <dc:creator>Maxim Titov</dc:creator>
      <pubDate>Fri, 31 Mar 2023 06:39:03 +0000</pubDate>
      <link>https://dev.to/titovmx/whats-wrong-with-html-number-input-f57</link>
      <guid>https://dev.to/titovmx/whats-wrong-with-html-number-input-f57</guid>
      <description>&lt;p&gt;The software development job is a daily problem-solving process. Developers love picking up sophisticated issues from the backlog and avoiding trivial ones. However, any task can turn into an adventure with a ton of surprises and elements of a detective job when the developer is investigating a problem and discovering reasons for unexpected behavior.&lt;/p&gt;

&lt;p&gt;In this article, I am sharing my personal experience in creating a configurable input to type numbers, facing pitfalls along the way, and eventually, I will suggest a solution for this problem.&lt;/p&gt;

&lt;h3&gt;
  
  
  Requirements
&lt;/h3&gt;

&lt;p&gt;Let’s look at what actually had to be done. The task is to add number input. The context is that this input is a part of the application builder so users are creating their apps from a predefined set of components. The input has the following requirements:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The component should be configurable. Particularly it has three essential properties: 

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;defaultValue: number&lt;/code&gt; - should be applied when the input is empty and not touched.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;allowDecimals: boolean&lt;/code&gt; - should allow typing a decimal value, increasing and decreasing by arrows should add and subtract 1 accordingly and keep the decimal part. If the prop is switched from true to false decimal value should be floored.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;allowNegative: boolean&lt;/code&gt; - should allow typing negative values. If the prop is false, the minimum possible value is 0. If the prop is switched from true to false negative value should be changed to 0.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;The value can be changed with the help of arrow buttons, arrow keys, or manually typed.&lt;/li&gt;
&lt;li&gt;Depending on chosen settings the input should not allow typing invalid numbers. In other words, allowed symbols are digits (0..9), optionally separators (,.) and a sign (-).&lt;/li&gt;
&lt;li&gt;The UI should be customized including arrow buttons.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Restrictions by the specification
&lt;/h3&gt;

&lt;p&gt;The first idea is to use a usual HTML input with a type number. It already has &lt;code&gt;min&lt;/code&gt;, &lt;code&gt;max&lt;/code&gt; and &lt;code&gt;step&lt;/code&gt; properties that look exactly like what we need to customize behavior according to requirements. However, the HTML5 specification and browser implementations have a few peculiarities:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Setting a minimum value restricts values that a user can choose using up and down arrows but it is still necessary to handle and validate manual user input. The same for typing decimal numbers.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Decimal separator&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Chrome works with both “,” and “.”&lt;/li&gt;
&lt;li&gt;Firefox and Safari require “.” as a separator, the value with a comma is considered to be invalid.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While English-speaking countries use a decimal point as a preferred separator between integer and fractional parts many countries use a comma as a separator as well. Most of the standards (System of Units, versions of ISO 8601 before 2019, ISO 80000-1) stipulate that both a point and a comma can be used equally. From this perspective, Chrome's behavior looks preferable and should be consistent among different browsers.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;On type validation&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Chrome filters prohibited values such as letters and punctuation marks (except decimal separators).&lt;/li&gt;
&lt;li&gt;Firefox and Safari allow typing any symbols. Instead, they mark the input as invalid and the value is an empty string.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9ezwgdoi04no2n0mqk16.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%2F9ezwgdoi04no2n0mqk16.png" alt="Value and validation message of invalid number input in Firefox"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Again, the support of Chrome’s behavior looks preferable because it helps a user, doesn't provide opportunities for invalid input, and eliminates the necessity of additional validation messages.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;By default increment/decrement of decimals works in a different way than what was expected. It always tries to round a number and ignores a fractional part. Let’s say we have 1.5, then adding one will result in 2, not 2.5. &lt;a href="https://www.w3.org/TR/2011/WD-html5-20110525/common-input-element-attributes.html#attr-input-step" rel="noopener noreferrer"&gt;The step can have a value equal “any”&lt;/a&gt; that makes increment/decrement behavior as was expected. However, it doesn’t work in the current versions of Firefox.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All the things above are actually reasonable, Chrome’s input looks almost perfect so far. Despite Firefox and Safari providing worse UX they have lower usage, and these shortages could be ignored for the first version.&lt;/p&gt;

&lt;p&gt;The worst thing is when the customization of up and down arrows comes. For this purpose input component was added along with a custom controls component.&lt;/p&gt;

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

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;NativeNumericInput&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onValueChange&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;min&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;allowDecimals&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useConfigContext&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;displayValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setDisplayValue&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="dl"&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;inputRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRef&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;max&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MAX_SAFE_INTEGER&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;canIncrement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;max&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;handleIncrement&lt;/span&gt; &lt;span class="o"&gt;=&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="nx"&gt;inputRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stepUp&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;newValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;inputRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nf"&gt;onChange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newValue&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;canDecrement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;min&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;handleDecrement&lt;/span&gt; &lt;span class="o"&gt;=&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="nx"&gt;inputRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stepDown&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;newValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;inputRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nf"&gt;onChange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newValue&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleValueChange&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&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;newValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nf"&gt;setDisplayValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newValue&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newValue&lt;/span&gt; &lt;span class="o"&gt;===&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="nf"&gt;onValueChange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;onValueChange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newValue&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="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"native-input-container"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt; &lt;span class="na"&gt;htmlFor&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"native-input"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Native Input&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;br&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt;
        &lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;inputRef&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"number"&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"native-input"&lt;/span&gt;
        &lt;span class="na"&gt;min&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;min&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;step&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;allowDecimals&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;any&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;displayValue&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"input-with-custom-controls"&lt;/span&gt;
        &lt;span class="na"&gt;onChange&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleValueChange&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;NumericInputControls&lt;/span&gt;
        &lt;span class="na"&gt;canIncrement&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;canIncrement&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;canDecrement&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;canDecrement&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;onIncrement&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleIncrement&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;onDecrement&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleDecrement&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;NumericInputControls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;canIncrement&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;canDecrement&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onIncrement&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onDecrement&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="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"numeric-input-controls"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;onIncrement&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;disabled&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;canIncrement&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        ^
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;onDecrement&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;disabled&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;canDecrement&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        ^
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;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;The input component receives &lt;code&gt;value&lt;/code&gt; as a prop, &lt;code&gt;min&lt;/code&gt; property from the configuration which is 0 if &lt;code&gt;allowNegative: false&lt;/code&gt; and set &lt;code&gt;max&lt;/code&gt; to define when the buttons should be disabled. &lt;code&gt;NumericInputControls&lt;/code&gt; is just a presentation component. The increment and decrement handlers use &lt;code&gt;stepUp&lt;/code&gt; and &lt;code&gt;stepDown&lt;/code&gt; methods respectively. It works fine for integers and doesn’t work for decimals. The reason is that value of the step attribute is any, and &lt;a href="https://www.w3.org/TR/2011/WD-html5-20110525/common-input-element-attributes.html#dom-input-stepdown" rel="noopener noreferrer"&gt;these methods throw an error&lt;/a&gt; &lt;code&gt;INVALID_STATE_ERR&lt;/code&gt;. It is happening because their internal behavior multiplies the step value by n, where n is an argument of the function whose default value is 1.&lt;/p&gt;

&lt;p&gt;This leads us to write our own implementation of increment and decrement functions. Also, it means that buttons will work the same way in all browsers including Firefox. But the keyboard events for ArrowDown and ArrowUp are still based on the native implementation of number input. Considering the fact that the benefits of specific input are not used anymore, it appears that rewriting the component with a standard text input is not much harder but allows to implement of consistent behavior for all necessary features: on type validation, decimal separators, increment and decrement for decimals.&lt;/p&gt;

&lt;h3&gt;
  
  
  Solution based on text input
&lt;/h3&gt;

&lt;p&gt;Let’s first check how increment and decrement functions changed.&lt;/p&gt;

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

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleIncrement&lt;/span&gt; &lt;span class="o"&gt;=&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;newValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;preciseMathSum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;defaultValue&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;handleControlsValueChange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;max&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;newValue&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleDecrement&lt;/span&gt; &lt;span class="o"&gt;=&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;newValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;preciseMathSubtract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;defaultValue&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;handleControlsValueChange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;min&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;newValue&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleControlsValueChange&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&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;setDisplayValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
  &lt;span class="nf"&gt;onValueChange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&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;There are methods &lt;code&gt;preciseMathSum&lt;/code&gt; and &lt;code&gt;preciseMathSubtract&lt;/code&gt; to solve a floating number precision issue so fraction part length will be kept and expected. Also, the usage of &lt;code&gt;Math.min&lt;/code&gt; and &lt;code&gt;Math.max&lt;/code&gt; helps to avoid exiting the range of allowed values. The minimum value depends on &lt;code&gt;allowNegative&lt;/code&gt; setting so it is either 0 or &lt;code&gt;Number.MIN_SAFE_INTEGER&lt;/code&gt;. The maximum allows operating with numbers no more than &lt;code&gt;Number.MAX_SAFE_INTEGER&lt;/code&gt; accordingly.&lt;/p&gt;

&lt;p&gt;The next step is to return the keyboard support that we lost by switching off the number input behavior. &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&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;handleKeydown&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ArrowDown&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;canDecrement&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;handleDecrement&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ArrowUp&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;canIncrement&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;handleIncrement&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&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="nx"&gt;inputRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;keydown&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;handleKeydown&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;inputRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;keydown&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;handleKeydown&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="nx"&gt;canIncrement&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;canDecrement&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;It checks for the possibility to change the input and does the same increment and decrement operations on the keydown event only for two arrows.&lt;/p&gt;

&lt;p&gt;The last step is to verify that the user is not allowed to put invalid values into an input. These restrictions are different depending on the component settings. The user is able to type “-”, “,” and “.” only if negative and decimal numbers are allowed.&lt;/p&gt;

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

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleValueChange&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&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;newValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;isValidNumberInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;allowDecimals&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;allowNegative&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="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nf"&gt;setDisplayValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newValue&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newValue&lt;/span&gt; &lt;span class="o"&gt;===&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="nf"&gt;onValueChange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newValueWithDecimalPoint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;newValue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;,&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;.&lt;/span&gt;&lt;span class="dl"&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;newNumericValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newValueWithDecimalPoint&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newValueWithDecimalPoint&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isNaN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newNumericValue&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;onValueChange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newNumericValue&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;All the logic is encapsulated in &lt;code&gt;isValidNumberInput&lt;/code&gt; utility function. It checks an input to be either a valid number or unfinished input which could become a valid number. Internally it uses pattern matching and checking edge cases, also considering &lt;code&gt;allowDecimals&lt;/code&gt; and &lt;code&gt;allowNegative&lt;/code&gt; values. It would be more clear with examples.&lt;/p&gt;

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

&lt;span class="nf"&gt;isValidNumberInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;-1&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;allowDecimals&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="na"&gt;allowNegative&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="c1"&gt;// returns false&lt;/span&gt;
&lt;span class="nf"&gt;isValidNumberInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;-1&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;allowDecimals&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="na"&gt;allowNegative&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="c1"&gt;// returns true&lt;/span&gt;
&lt;span class="nf"&gt;isValidNumberInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;-&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;allowDecimals&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="na"&gt;allowNegative&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="c1"&gt;// returns true&lt;/span&gt;
&lt;span class="nf"&gt;isValidNumberInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;--&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;allowDecimals&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="na"&gt;allowNegative&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="c1"&gt;// returns false&lt;/span&gt;
&lt;span class="nf"&gt;isValidNumberInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1.5&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;allowDecimals&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="na"&gt;allowNegative&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="c1"&gt;// returns false&lt;/span&gt;
&lt;span class="nf"&gt;isValidNumberInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1.5&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;allowDecimals&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="na"&gt;allowNegative&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="c1"&gt;// returns true&lt;/span&gt;
&lt;span class="nf"&gt;isValidNumberInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1,5&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;allowDecimals&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="na"&gt;allowNegative&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="c1"&gt;// returns true&lt;/span&gt;
&lt;span class="nf"&gt;isValidNumberInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1.5.&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;allowDecimals&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="na"&gt;allowNegative&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="c1"&gt;// returns false&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;After this verification, the input will be converted to a number and committed if possible. If it is not a number yet it is only set as a display value for controlled input, the actual value is kept the same.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: alternatively it is possible to build one regex to check the input for necessary conditions and even add it as a pattern prop to the input. However, I personally prefer to have simpler regex and additional conditions that could be more readable. Also, putting this logic in the method allows us to cover it with unit tests.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;There is a &lt;a href="https://github.com/titovmx" rel="noopener noreferrer"&gt;repository&lt;/a&gt; with the demonstration code that is also published with &lt;a href="https://number-input-demo.vercel.app/" rel="noopener noreferrer"&gt;Vercel&lt;/a&gt; if you want to play around with this case.&lt;/p&gt;

&lt;p&gt;Despite the fact that I had to implement this task twice it was a nice exercise and interesting diving into the HTML5 specification. I hope you also enjoyed this small journey and found something useful for you!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>html</category>
      <category>frontend</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
