loading...
Cover image for Currency Picker And Formatter With Ionic React

Currency Picker And Formatter With Ionic React

daviddalbusco profile image David Dal Busco Originally published at Medium ・6 min read

One Trick A Day (35 Part Series)

1) How To Call The Service Worker From The Web App Context 2) Replace Environment Variables In Your Index.html 3 ... 33 3) Inject JavaScript Or CSS At Runtime And On Demand 4) Sometimes You Just Need A Dumb Library 5) Internationalization with Gatsby 6) How To Declare And Use Ionic Modals With Stencil 7) Get App Name And Version In Angular 8) Deploy Apps And Functions To Firebase From A Mono Repo With GitHub Actions 9) Starting In A New Company? Think Npmrc And Git Name 10) Test Angular Pipes With Services 11) Gatsby Tricks: Viewport, CSS Modules Transition And i18n Tricks 12) Takeover The Cordova Facebook Plugin Maintenance 13) Protect Your HTTP Firebase Cloud Functions 14) Create A Menu For Your Gatsby Website Without Libs 15) Create A Modal For Your Angular App Without Libs 16) Add A Slider To You Angular App 17) Test Angular Components and Services With HTTP Mocks 18) Merge Two Objects And Array To Object In JavaScript 19) JSX For Angular Developers 20) More JSX For Angular Developers 21) Create Your Own NPM Cli 22) Third Party Service Providers. Be transparent to each other! 23) React And Web Workers 24) Angular Testing: Mock Private Functions 25) React, Web Workers and IndexedDB 26) React, Web Workers, IndexedDB and ExcelJS 27) GitHub Actions: Hide And Set Angular Environment Variables 28) JavaScript Useful Functions 29) Deeplinking in Ionic Apps With Branch.io 30) Follow-up: Web Push Notifications And PWA In 2020 31) Angular And Web Workers 32) Git Commands I Always Forget 33) An Open Source Medium Like WYSIWYG Editor 34) Currency Picker And Formatter With Ionic React 35) Develop A Konami Code For Any Apps With Stencil

I share one trick a day until the original scheduled date of the end of the COVID-19 quarantine in Switzerland, April 19th 2020. One days left until this first milestone. Hopefully better days are ahead.


I was looking for a subject idea for today’s blog post and it came to my mind that I could maybe share something I learned with Tie Tracker ⏱️, a simple, open source and free time tracking app I have developed with Ionic and React again.

That’s why I’m sharing with you my solution to develop a custom currency picker and formatter.


Start

If you don’t have an Ionic React application yet, you can follow this tutorial by creating a sample one using their CLI.

ionic start

When prompted, select “React”, your application name and for example the template “blank”.


List Of Currencies

We intend to develop a custom currency picker, that’s why we need, a list of currencies. For such purpose, we can download the one provided on the Xsolla repo as it is free and licensed under MIT license.

curl https://raw.githubusercontent.com/xsolla/currency-format/master/currency-format.json -o public/assets/currencies.json

I use curl because I am using a Macbook but what does matter is to save the list of currencies in the assets folder as it will have to be shipped with the app.


TypeScript Definitions

We are going to need a TypeScript definitions to handle the list we just downloaded. That’s why we create following interfaces in ./src/definitions/currency.d.ts .

export interface Currency {
    name: string;
    fractionSize: number;
    symbol: {
        grapheme: string;
        template: string;
        rtl: boolean;
    };
    uniqSymbol: boolean;
}

export interface Currencies {
    [currency: string]: Currency;
}

Note that I am not sure that using a subfolder definitions is really the best practice, it is just something I do. Do not think it matters that much, I just like to split my code in, kind of, packages.


Modal: Currency Picker

To develop our picker I suggest that we use a modal. It should display the list of available currencies (currency name and abbreviation), allow the user to filter these and ultimately let him/her select one.

We create a new component ./src/components/CurrenciesModal.tsx which receive as properties the current selected currency and a function to close the modal and pass the user selection.

interface Props {
    closeAction: Function;
    currency: string;
}

It contains also two states. The list of currencies and a filtered one, which is, when component mounted, equals to the all list.

const [currencies, setCurrencies] = 
      useState<Currencies | undefined>(undefined);
const [filteredCurrencies, setFilteredCurrencies] = 
      useState<Currencies | undefined>(undefined);

To initiate these we use useEffect hooks and we read the JSON data we downloaded before.

useEffect(() => {
    initCurrencies();
    // eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

useEffect(() => {
    setFilteredCurrencies(currencies);
}, [currencies]);

async function initCurrencies() {
    try {
        const res: Response = 
                   await fetch('./assets/currencies.json');

        if (!res) {
            setCurrencies(undefined);
            return;
        }

        const currencies: Currencies = await res.json();

        setCurrencies(currencies);
    } catch (err) {
        setCurrencies(undefined);
    }
}

To proceed with filtering, we implement a function which read the user inputs and call another one which effectively takes care of applying a filter on the list we maintain as state objects.

async function onFilter($event: CustomEvent<KeyboardEvent>) {
    if (!$event) {
        return;
    }

    const input: string = ($event.target as InputTargetEvent).value;

    if (!input || input === undefined || input === '') {
        setFilteredCurrencies(currencies);
    } else {
        const filtered: Currencies | undefined = 
                        await filterCurrencies(input);
        setFilteredCurrencies(filtered);
    }
}

Finally we implement our modal’s GUI which contains a searchbar and a list of items , the currencies.

<IonSearchbar debounce={500} placeholder="Filter"
              onIonInput={($event: CustomEvent<KeyboardEvent>) => onFilter($event)}></IonSearchbar>

<IonList>
    <IonRadioGroup value={props.currency}>
        {renderCurrencies()}
    </IonRadioGroup>
</IonList>

Altogether our component looks like the following:

import React, {useEffect, useState} from 'react';

import {
    IonList,
    IonItem,
    IonToolbar,
    IonRadioGroup,
    IonLabel,
    IonRadio,
    IonSearchbar,
    IonContent,
    IonTitle,
    IonHeader, IonButtons, IonButton, IonIcon
} from '@ionic/react';

import {close} from 'ionicons/icons';

import {Currencies} from '../definitions/currency';

interface Props {
    closeAction: Function;
    currency: string;
}

interface InputTargetEvent extends EventTarget {
    value: string;
}

const CurrenciesModal: React.FC<Props> = (props: Props) => {

    const [currencies, setCurrencies] = 
          useState<Currencies | undefined>(undefined);
    const [filteredCurrencies, setFilteredCurrencies] = 
          useState<Currencies | undefined>(undefined);

    useEffect(() => {
        initCurrencies();
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, []);

    useEffect(() => {
        setFilteredCurrencies(currencies);
    }, [currencies]);

    async function initCurrencies() {
        try {
            const res: Response = 
                  await fetch('./assets/currencies.json');

            if (!res) {
                setCurrencies(undefined);
                return;
            }

            const currencies: Currencies = await res.json();

            setCurrencies(currencies);
        } catch (err) {
            setCurrencies(undefined);
        }
    }

    async function onFilter($event: CustomEvent<KeyboardEvent>) {
        if (!$event) {
            return;
        }

        const input: string = 
              ($event.target as InputTargetEvent).value;

        if (!input || input === undefined || input === '') {
            setFilteredCurrencies(currencies);
        } else {
            const filtered: Currencies | undefined = 
                  await filterCurrencies(input);
            setFilteredCurrencies(filtered);
        }
    }

    async function filterCurrencies(filter: string): 
                   Promise<Currencies | undefined> {
        if (!currencies) {
            return undefined;
        }

        const results: Currencies = Object.keys(currencies)
            .filter((key: string) => {
                return ((key.toLowerCase().indexOf(filter.toLowerCase()) > -1) ||
                    (currencies[key].name && currencies[key].name.toLowerCase().indexOf(filter.toLowerCase()) > -1));
            })
            .reduce((obj: Currencies, key: string) => {
                obj[key] = currencies[key];
                return obj;
            }, {});

        return results;
    }

    return (
        <>
            <IonHeader>
                <IonToolbar color="primary">
                    <IonTitle>Picker</IonTitle>
                    <IonButtons slot="start">
                        <IonButton 
                          onClick={() => props.closeAction()}>
                          <IonIcon icon={close} slot="icon-only"> 
                          </IonIcon>
                        </IonButton>
                    </IonButtons>
                </IonToolbar>
            </IonHeader>

            <IonContent className="ion-padding">
                <IonSearchbar debounce={500} placeholder="Filter"
                 onIonInput={($event: CustomEvent<KeyboardEvent>) => onFilter($event)}></IonSearchbar>

                <IonList>
                    <IonRadioGroup value={props.currency}>
                        {renderCurrencies()}
                    </IonRadioGroup>
                </IonList>
            </IonContent>
        </>
    );

    function renderCurrencies() {
        if (!filteredCurrencies 
            || filteredCurrencies === undefined) {
            return undefined;
        }

        return Object.keys(filteredCurrencies)
                     .map((key: string) => {
            return <IonItem key={`${key}`}
                            onClick={() => props.closeAction(key)}>
                <IonLabel>{filteredCurrencies[key].name} ({key})
                </IonLabel>
                <IonRadio value={key}/>
            </IonItem>
        });
    }

};

export default CurrenciesModal;

Page: Home

Our picker being ready, we can now use it. For such purpose we integrate it to the main page of our application, the home page. We are also adding a state to display the current selected currency which I initialized with CHF as it is the currency of Switzerland.

Moreover, we are also implementing a function to update the currency according the one the user would pick using our above modal.

import React, {useState} from 'react';
import {IonContent, IonHeader, IonPage, IonTitle, IonToolbar, IonModal, IonButton, IonLabel} from '@ionic/react';

import CurrenciesModal from '../components/CurrenciesModal';

const Home: React.FC = () => {

    const [currency, setCurrency] = useState<string>('CHF');
    const [showModal, setShowModal] = useState<boolean>(false);

    function updateCurrency(currency?: string | undefined) {
        setShowModal(false);

        if (!currency) {
            return;
        }

        setCurrency(currency);
    }

    return (
        <IonPage>
            <IonHeader>
                <IonToolbar>
                    <IonTitle>Home</IonTitle>
                </IonToolbar>
            </IonHeader>
            <IonContent>
                <IonModal isOpen={showModal} 
                 onDidDismiss={() => setShowModal(false)}>
                    <CurrenciesModal currency={currency}
                                     closeAction={updateCurrency}>
                    </CurrenciesModal>
                </IonModal>

                <h1>123.45 {currency}</h1>

                <IonButton onClick={() => setShowModal(true)}>
                    <IonLabel>Pick currency</IonLabel>
                </IonButton>
            </IonContent>
        </IonPage>
    );
};

export default Home;

If you implemented the above code you should now be able to run the application and pick currencies.


Format Currency

Being able to select a currency is nice, but being able to use it is even better 😉.

To format our amount, we are going to use the standard built-in object Intl.NumberFormat which is now pretty well supported by any browser.

function formatCurrency(value: number): string {
    if (currency === undefined) {
        return new Intl.NumberFormat('fr').format(0);
    }

    return new Intl.NumberFormat('fr', 
           { style: 'currency', currency: currency }).format(value);
}

Note that in the above function I hardcoded french as it is my mother tongue. This can be replaced by the one of your choice or if you are using i18next with the following dynamic language.

import i18n from 'i18next';

function formatCurrency(value: number): string {
    if (currency === undefined) {
        return new Intl.NumberFormat(i18n.language).format(0);
    }

    return new Intl.NumberFormat(i18n.language, 
           { style: 'currency', currency: currency }).format(value);
}

Finally, we are replacing the static display of the value 123.45 {currency} with the function’s call.

<h1>{formatCurrency(123.45)}</h1>

Altogether our main page now should contain the following code:

import React, {useState} from 'react';
import {IonContent, IonHeader, IonPage, IonTitle, IonToolbar, IonModal, IonButton, IonLabel} from '@ionic/react';

import CurrenciesModal from '../components/CurrenciesModal';

const Home: React.FC = () => {

    const [currency, setCurrency] = useState<string>('CHF');
    const [showModal, setShowModal] = useState<boolean>(false);

    function updateCurrency(currency?: string | undefined) {
        setShowModal(false);

        if (!currency) {
            return;
        }

        setCurrency(currency);
    }

    function formatCurrency(value: number): string {
        if (currency === undefined) {
            return new Intl.NumberFormat('fr').format(0);
        }

        return new Intl.NumberFormat('fr', 
           { style: 'currency', currency: currency }).format(value);
    }

    return (
        <IonPage>
            <IonHeader>
                <IonToolbar>
                    <IonTitle>Home</IonTitle>
                </IonToolbar>
            </IonHeader>
            <IonContent>
                <IonModal isOpen={showModal} 
                          onDidDismiss={() => setShowModal(false)}>
                    <CurrenciesModal currency={currency}
                                     closeAction={updateCurrency}>
                    </CurrenciesModal>
                </IonModal>

                <h1>{formatCurrency(123.45)}</h1>

                <IonButton onClick={() => setShowModal(true)}>
                    <IonLabel>Pick currency</IonLabel>
                </IonButton>
            </IonContent>
        </IonPage>
    );
};

export default Home;

Voilà, both our currency picker and formatter are implemented in our Ionic React application 🎉.


Summary

Ionic and React together are really fun. Checkout Tie Tracker and of course your Pull Requests to improve the app are most welcomed 😁.

Stay home, stay safe!

David

Cover photo by Pawel Janiak on Unsplash

One Trick A Day (35 Part Series)

1) How To Call The Service Worker From The Web App Context 2) Replace Environment Variables In Your Index.html 3 ... 33 3) Inject JavaScript Or CSS At Runtime And On Demand 4) Sometimes You Just Need A Dumb Library 5) Internationalization with Gatsby 6) How To Declare And Use Ionic Modals With Stencil 7) Get App Name And Version In Angular 8) Deploy Apps And Functions To Firebase From A Mono Repo With GitHub Actions 9) Starting In A New Company? Think Npmrc And Git Name 10) Test Angular Pipes With Services 11) Gatsby Tricks: Viewport, CSS Modules Transition And i18n Tricks 12) Takeover The Cordova Facebook Plugin Maintenance 13) Protect Your HTTP Firebase Cloud Functions 14) Create A Menu For Your Gatsby Website Without Libs 15) Create A Modal For Your Angular App Without Libs 16) Add A Slider To You Angular App 17) Test Angular Components and Services With HTTP Mocks 18) Merge Two Objects And Array To Object In JavaScript 19) JSX For Angular Developers 20) More JSX For Angular Developers 21) Create Your Own NPM Cli 22) Third Party Service Providers. Be transparent to each other! 23) React And Web Workers 24) Angular Testing: Mock Private Functions 25) React, Web Workers and IndexedDB 26) React, Web Workers, IndexedDB and ExcelJS 27) GitHub Actions: Hide And Set Angular Environment Variables 28) JavaScript Useful Functions 29) Deeplinking in Ionic Apps With Branch.io 30) Follow-up: Web Push Notifications And PWA In 2020 31) Angular And Web Workers 32) Git Commands I Always Forget 33) An Open Source Medium Like WYSIWYG Editor 34) Currency Picker And Formatter With Ionic React 35) Develop A Konami Code For Any Apps With Stencil

Posted on by:

daviddalbusco profile

David Dal Busco

@daviddalbusco

Creator of DeckDeckGo | Organizer of the Ionic Zürich Meetup

Discussion

markdown guide