Vue 3 Currency Converter Component
Tags: vue, javascript, webdev, typescript
Published: 2024-03-09 21:56:59

In this article I'm going to share how to write vue3 component that convert currencies with calculation of user bonuses (it means if you give users promo codes or any other benefits). 

## Basic layout

First we need to make a sketch of our component template:

```vue
<template>
  <div>
    <input 
      :class="$style.input" 
      @input="valueHandler(($event.target as HTMLInputElement).value)" 
      @keypress="press" v-model="value"
    >EUR
  </div>
  <div>You will receive with bonus</div>
  <div>
    <input 
      :class="$style.input" 
      @input="valueReceiveHandle(($event.target as HTMLInputElement).value)" 
      v-model="valueReceive"
      @keypress="press"
    >USD
  </div>
</template>

<style module>
.input {
  border: solid 2px #cdcdcd;
  padding: 10px 15px;
  margin: 10px;
  font-size: 16px;
}
</style>
```

In input handler function I set event target value from template because I want to call this one in another component parts

## Currency Conversion Logic

**Ok, how it works when user enters some data.** 
When user enters data to the first input your function should to convert source value by currency rate and update value in second input. We need create two variables value and valueReceive and set as v-model directive value. 

```typescript
const value = ref(100)
const valueReceive = ref(0)
```

Then write the pure conversion functions:

```typescript
const convert = (value: number) => {
  return  Number(value.toFixed(2))
}


type ConverterType = {
  value: number;
  rate: number;
}

const convertTo = ({ value, rate }: ConverterType) => {
  return convert(value * rate)
}

const convertFrom = ({ value, rate }: ConverterType) => {
  return convert(value / rate)
}
```

## Component handlers

After that let's define component props rate and modelValue:

```typescript
const props = withDefaults(
  defineProps<
    Partial<{
      rate: number;
    }>
    & {
      modelValue: number;
    }
  >()
  ,
  {
    rate: 1,
  }
)
```

Define handle functions for inputs:

```typescript
const valueHandler = (value: number | string) => {
  valueReceive.value = convertTo({ value:  Number(value), rate: props.rate })
}

const valueReceiveHandle = (_value: number | string) => {
  value.value = convertFrom({
    value: Number(_value),
    rate: props.rate
  })
}
```

It looks good, but let's add watcher for modelValue prop. It will call valueHandler and emit for update modelValue, and don't forget to set `immediate: true`, otherwise it wont fire when the component is mounted or component takes on newest value. 

```typescript
const emits = defineEmits<{
  'update:modelValue': [value: number]
}>()

watch(() => props.modelValue, (_value: number) => {
  emits('update:modelValue', _value)
  value.value = _value
  valueHandler(_value)
}, {
  immediate: true
})
```

So this works, but how about handling a user's keypress, because if a user can enter any symbols, this is not expected behavior for the component - accept only numbers and dot:

```typescript
const press = (e: KeyboardEvent) => {
  const onlyDigitsAndDots = /^[0-9]*\.?[0-9]*$/
  if (!onlyDigitsAndDots.test(e.key)) e.preventDefault()
}
```

## User bonuses

After writing the main logic, let's imagine, customer (your project manager for example) comes to us and says: "I want to the app gives users the opportunity to apply bonuses and make calculations". We say: "Ok, it's easy"

We need to write two functions: calculateBonus (will calculate bonus in receive input) and subtractBonus (will subtract the bonus from final value and set it to value input), and bonusPercent prop. 

```typescript
type BonusType = {
  value: number;
  percent: number;
}

const calculateBonus = ({value, percent}: BonusType) => {
  return value + (value * (percent / 100))
}

const subtractBonus = ({ value, percent }: BonusType) => {
  return value / (1 + percent / 100)
}
```

Let's refactor our handle functions:

```typescript
const valueReceiveHandle = (_value: number | string) => {
  value.value = convertFrom({
    value: subtractBonus({value: Number(_value), percent: props.bonusPercent}),
    rate: props.rate
  })
}

const valueHandler = (value: number | string) => {
  const _value = props.bonusPercent ? calculateBonus({
    value: Number(value), percent: props.bonusPercent
  }) : Number(value)
  valueReceive.value = convertTo({ value: _value, rate: props.rate })
}
```

## Use it

How to use converter component:

```typescript
<template>
  <CurrencyConverter
    v-model="value"
    :rate="1.08"
    :bonus-percent="27"
  />
</template>

<script setup lang="ts">
import CurrencyConverter from './components/CurrencyConverter.vue'
import { ref } from 'vue';

const value = ref(100)
</script>
```

### I hope it was useful for you, I pushed this [code on github](https://github.com/zorin-e/currency-converter), you can fork it or just see it.