33
loading...
This website collects cookies to deliver better user experience
// 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);
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 */);
}
<div className="App">
<Currency
amount$={state$.baseAmount}
currency$={state$.currencies[0]}
/>
<Currency
amount$={state$.baseAmount}
currency$={state$.currencies[1]}
/>
</div>
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. state$.currencies[0]
, it will read/write the first element of the currency
array.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. 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$} />
// 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$()];
}
};
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;
}
};
<Mlyn.input
bindValue={convertCurrencyAmount(baseAmount$, currency$)}
/>