DEV Community

zorin-e
zorin-e

Posted on

Vue 3 Currency Converter Component

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:

<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>
Enter fullscreen mode Exit fullscreen mode

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.

const value = ref(100)
const valueReceive = ref(0)
Enter fullscreen mode Exit fullscreen mode

Then write the pure conversion functions:

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)
}
Enter fullscreen mode Exit fullscreen mode

Component handlers

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

const props = withDefaults(
  defineProps<
    Partial<{
      rate: number;
    }>
    & {
      modelValue: number;
    }
  >()
  ,
  {
    rate: 1,
  }
)
Enter fullscreen mode Exit fullscreen mode

Define handle functions for inputs:

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
  })
}
Enter fullscreen mode Exit fullscreen mode

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.

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

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

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:

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

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.

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)
}
Enter fullscreen mode Exit fullscreen mode

Let's refactor our handle functions:

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 })
}
Enter fullscreen mode Exit fullscreen mode

Use it

How to use converter component:

<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>
Enter fullscreen mode Exit fullscreen mode

I hope it was useful for you, I pushed this code on github, you can fork it or just see it.

Top comments (0)