DEV Community

Morgan Feeney
Morgan Feeney

Posted on

VAT Calculator built with React & Next

Recently I whipped up a handy VAT calculator. Not only that, I went further and created a website to house the calculator, with a view to adding even more calculators as and when needed.

Here's the finished: VAT calculator

Here's a roundup of all the main things I did to get this up and running from a dev perspective:

  • Bootstrapped create-next-app
  • Setup Typescript
  • Created the Calculator component, which was built using smaller components
    • Button
    • Input
  • Wrote the calculations for VAT, and then used in functions that calculate VAT either by deducting from Gross or adding to a Net figure
  • Came up with some fairly rudimentary validation that checks to see if a string matches some regex (inputs convert values to strings)
  • Deployed for free using Vercel

The reason I went this far is that it's a tech-stack I'm working with in my day-to-day job, you don't have to do all this setup, it's not necessary, I'm honing my skills in this area.

Here's the calculator code:

import React, { FC, useState, useRef } from 'react';
import Input from '../Input';
import Button from '../Button';
import style from './Calculator.module.css';

const Calculator: FC = () => {
  const [amount, setAmount] = useState('0');
  const [vatRate, setVatRate] = useState('20');

  const getPercentage = Number(vatRate) / 100;
  const correctPercentage = 1 + getPercentage;

  const amountIncludingVat = Number(amount) * correctPercentage;
  const amountExcludingVat = Number(amount) / correctPercentage;

  const netRef = useRef(null);
  const grossRef = useRef(null);

  const twoDecimals = (unit): string => Number(unit).toFixed(2);

  const validation = (unit, pattern): boolean => {
    let valid = false;
    if (typeof unit !== 'undefined' && typeof unit === 'string') {
      valid = !!unit.match(pattern);
    }
    return valid;
  };

  const amountIsValid = validation(amount, /[0-9]/g) && amount !== '0';
  const vatRateIsValid = validation(vatRate, /^\d+(.\d)?\d*$/g);

  const validationRules = [amountIsValid, vatRateIsValid];

  const scrollToResult = (result): void => {
    const allTrue = validationRules.every((element) => {
      return element === true;
    });

    if (allTrue) {
      result.current.scrollIntoView({
        behavior: 'smooth',
        block: 'start',
      });
    }
  };

  return (
    <>
      <div className={style.andResults}>
        <form className={style.root}>
          <Input
            labelText="Amount"
            onChange={(e) => setAmount(e.target.value)}
            id="amount"
            type="number"
            value={amount}
            random
            isValid={amountIsValid}
            validationText="Please enter a number greater than 0"
          />
          <Input
            labelText="VAT Rate %"
            onChange={(e) => setVatRate(e.target.value)}
            id="vat-rate"
            type="number"
            value={vatRate}
            isValid={vatRateIsValid}
            validationText="Please enter a vat rate, e.g. 17.5"
          />
          <div className={style.buttonWrapper}>
            <Button onClick={() => scrollToResult(netRef)} importance="primary">
              Add VAT
            </Button>
            <div className={style.buttonDivider}>Or</div>
            <Button
              onClick={() => scrollToResult(grossRef)}
              importance="secondary"
            >
              Remove VAT
            </Button>
          </div>
        </form>
        <div ref={netRef} id="net-results" className={style.result}>
          <h2>Results when adding vat</h2>
          <Input
            labelText="Net amount (excluding VAT)"
            id="net-amount"
            type="text"
            value={twoDecimals(amount)}
            readOnly
          />
          <Input
            labelText={`VAT at (${vatRate}%)`}
            id="vat-rate-2"
            type="text"
            value={twoDecimals(amountIncludingVat - Number(amount))}
            readOnly
          />
          <Input
            labelText="Gross amount (including VAT)"
            id="gross-amount"
            type="text"
            value={twoDecimals(amountIncludingVat)}
            readOnly
          />
        </div>
        <div ref={grossRef} id="gross-results" className={style.result}>
          <h2>Results when removing vat</h2>
          <Input
            labelText="Gross amount (including VAT)"
            id="gross-amount-2"
            type="text"
            value={twoDecimals(amount)}
            readOnly
          />
          <Input
            labelText={`VAT at (${vatRate}%)`}
            id="vat-rate-3"
            type="text"
            value={twoDecimals(Number(amount) - amountExcludingVat)}
            readOnly
          />
          <Input
            labelText="Net amount (excluding VAT)"
            id="net-amount-2"
            type="text"
            value={twoDecimals(amountExcludingVat)}
            readOnly
          />
        </div>
      </div>
    </>
  );
};

export default Calculator;
Enter fullscreen mode Exit fullscreen mode

Top comments (1)

Collapse
 
registriransum profile image
Info Comment hidden by post author - thread only accessible via permalink

Hey Morgan, your app looks amazing! Just a feedback, you might include radio buttons of VAT percentages. This is to save time when people are going to use your app multiple times in the day. Something like this one vat-calculator.uk
Cheers

Some comments have been hidden by the post's author - find out more