Currency converter app in React and Mlyn
vaukalak
Posted on December 14, 2021
👋 Let build a currency converter app:
The application should allow to edit the amount in the input fields and change currency. The amount in another input should change in the base of the conversion rate.
For a working example see this codesandbox (It also contains an advanced example).
First of all, we need to define our data domain. We need to take a currency as a reference point, let use USD:
// USD to currency price
const usdRates = {
USD: 1,
BYN: 2.5,
CAD: 1.260046,
CHF: 0.933058,
EUR: 0.806942,
GBP: 0.719154
};
// list of currency names
const availableCurrencies = Object.keys(usdRates);
Now we can setup the root state:
export default function App() {
const state$ = useSubject({
// entered amount expressed in USD
baseAmount: 0,
// list of currently compared currencies
currencies: ["USD", "EUR"]
});
return (/* jsx here */);
}
Yeah, that's all boilerplate we need. And finally some JSX:
<div className="App">
<Currency
amount$={state$.baseAmount}
currency$={state$.currencies[0]}
/>
<Currency
amount$={state$.baseAmount}
currency$={state$.currencies[1]}
/>
</div>
Operation state$.baseAmount
created a read/write lens to baseAmount
property. Calling state$.baseAmount()
will return its current value and state$.baseAmount(1)
will change the baseAmount
value. The update will bubble to the root state, cause encapsulated object is immutable. Also, you can subscribe to this value. This enables 2-way binding.
Same thing for state$.currencies[0]
, it will read/write the first element of the currency
array.
Now let write an incomplete version of the Currency
component.
const Currency = seal(({ amount$, currency$ }) => {
return (
<div>
<Mlyn.select bindValue={currency$}>
{availableCurrencies.map((c) => (
<option key={c}>{c}</option>
))}
</Mlyn.select>
{/* text input code here */}
</div>
);
});
Mlyn.select
is a wrapper over the plain select
element, it has a property bindValue
which accepts a read/write value, and creates a 2-way binding to it. Internally Mlyn.select
will observe currency$
value, and re-render when it's changed. When a selector option will be selected currency$
(and hence the root state) will be updated.
To write the input we can't just bind amount$
to it, cause we need to display the derived value of the currency:
// will not give the expected result,
// cause USD amount will be displayed
<Mlyn.input bindValue={amount$} />
Ok. This will be the hardest part.
One of good things of 2-way binding, is that you can wrap binded value within a function, that will perform read/write derivation logic. So let create a function that will convert amount in a currency to/from USD amount:
// function that will curry references to `baseAmount$`
// and `currency$` subjects
const convertCurrencyAmount = (baseAmount$, currency$) =>
// returns function to use as 2-way bindable value
(...args) => {
// if function has been invoked with params
// checks if it is a write operation
if (args.length > 0) {
const newAmount = parseFloat(args[0]);
// writes new value to the subject
baseAmount$(newAmount / ratesToUSD[currency$()]);
} else {
// it is a a read operation, return converted value
// note that this code will create subscription and
// routing will rerun whenever baseAmount$ or currency$
// values will changed
return baseAmount$() * ratesToUSD[currency$()];
}
};
The above function is a simplified version, in reality we should do some input validation:
const convertCurrencyAmount = (baseAmount$, currency$) =>
(...args) => {
if (args.length > 0) {
// if user erases all text make value 0.
const value = args[0] === "" ? 0 : parseFloat(args[0]);
// skip non-numeric updates
if (!isNaN(value)) {
baseAmount$(value / usdRates[currency$()]);
}
} else {
const newAmount = baseAmount$() * usdRates[currency$()];
// avoid very long numbers like 0.999999999
return Math.round(newAmount * 100) / 100;
}
};
Now you can use pass the converted currency lens to the amount input:
<Mlyn.input
bindValue={convertCurrencyAmount(baseAmount$, currency$)}
/>
For more examples and docs about mlyn, I invite you to check the github repo page.
Posted on December 14, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.