VAT Calculator built with React & Next
Published: 2020-11-05 08:50:57

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](https://calculators.icu/online-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](https://nextjs.org/docs/api-reference/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](https://vercel.com/)

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:

```jsx
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;
```