With the latest version of the Ionic framework, which lets you write mobile apps with web technologies, you can use React to build your mobile apps. Versions previous to 4 only has components for Angular, but now React support is complete. It has all the components for building apps to your liking.
Ionic 4 and later is a mobile app framework and a component library. You can build mobile apps, progressive web apps, and normal web apps. The component library can be used on its own. Of course, the hardware support remains in place for you to use if you wish. Right now, the React version of Ionic can only be used to build web apps. Only the Angular version can be built into a mobile app.
The full reference for the React version of Ionic is at https://ionicframework.com/docs.
In this article, we will build a React web app with Ionic that does currency conversion. The home page will display the list of the latest exchange rates and another page will have a form to convert the currencies of your choice. Tp get the data we use the Foreign exchange rates API, located at https://exchangeratesapi.io/ to get the exchange rates, and we use the Open Exchange Rates API, located at https://openexchangerates.org/, to get the list of currencies.
To start, we will start by installing the Ionic CLI. We run:
npm install -g ionic@latest
to install the latest version to your computer. Next we run:
ionic start currency-converter --type=react
then select the Sidenav option to create an Ionic project with a left side bar.
Next we install our own packages. We need Axios to make HTTP requests and MobX for state management. Run npm i axios mobx mobx-react
in our project folder to install them.
Now we are ready to create some pages. In the pages
folder, create ConvertCurrencyPage.jsx
and add:
import {
IonButtons,
IonContent,
IonHeader,
IonItem,
IonList,
IonMenuButton,
IonPage,
IonTitle,
IonToolbar,
IonInput,
IonLabel,
IonSelect,
IonSelectOption,
IonButton
} from "@ionic/react";import React from "react";
import { observer } from "mobx-react";
import { getExchangeRates } from "../requests";const ConvertCurrencyPage = ({ currenciesStore }) => {
const [fromCurrencies, setFromCurrencies] = React.useState({});
const [toCurrencies, setToCurrencies] = React.useState({});
const [values, setValues] = React.useState({ amount: 0 } as any);
const [submitting, setSubmitting] = React.useState(false);
const [toAmount, setToAmount] = React.useState(0);const convertCurrency = async () => {
setSubmitting(true);
if (values.amount <= 0 || !values.from || !values.to) {
return;
}
const { data } = await getExchangeRates(values.from);
const rate = data.rates[values.to];
setToAmount(values.amount * rate);
};React.useEffect(() => {
const fromCurrencies = {};
for (let key in currenciesStore.currencies) {
if (key != values.to) {
fromCurrencies[key] = currenciesStore.currencies[key];
}
}
setFromCurrencies(fromCurrencies);const toCurrencies = {};
for (let key in currenciesStore.currencies) {
if (key != values.from) {
toCurrencies[key] = currenciesStore.currencies[key];
}
}
setToCurrencies(toCurrencies);
}, [currenciesStore.currencies, values.from, values.to]);return (
<IonPage>
<IonHeader>
<IonToolbar>
<IonButtons slot="start">
<IonMenuButton />
</IonButtons>
<IonTitle>Convert Currency</IonTitle>
</IonToolbar>
</IonHeader><IonContent>
<IonList lines="none">
<IonItem>
<IonInput
type="number"
value={values.amount}
color={!values.amount && submitting ? "danger" : undefined}
min="0"
onIonChange={ev =>
setValues({ ...values, amount: (ev.target as any).value })
}
></IonInput>
</IonItem><IonItem>
<IonLabel>Currency to Convert From</IonLabel>
<IonSelect
placeholder="Select One"
color={!values.from && submitting ? "danger" : undefined}
onIonChange={ev =>
setValues({ ...values, from: (ev.target as any).value })
}
>
{Object.keys(fromCurrencies).map(key => {
return (
<IonSelectOption value={key} key={key}>
{(fromCurrencies as any)[key]}
</IonSelectOption>
);
})}
</IonSelect>
</IonItem><IonItem>
<IonLabel>Currency to Convert To</IonLabel>
<IonSelect
placeholder="Select One"
color={!values.to && submitting ? "danger" : undefined}
onIonChange={ev =>
setValues({ ...values, to: (ev.target as any).value })
}
>
{Object.keys(toCurrencies).map(key => {
return (
<IonSelectOption value={key} key={key}>
{(toCurrencies as any)[key]}
</IonSelectOption>
);
})}
</IonSelect>
</IonItem><IonItem>
<IonButton size="default" fill="solid" onClick={convertCurrency}>
Convert
</IonButton>
</IonItem>{toAmount ? (
<IonItem>
{values.amount} {values.from} is {toAmount} {values.to}.
</IonItem>
) : (
undefined
)}
</IonList>
</IonContent>
</IonPage>
);
};export default observer(ConvertCurrencyPage);
to add a form to convert currency from one to another. In this form, we filter by choices by excluding from the currency being converted to from the first drop down and exclude the currency being converted from in the second drop down. Also we have an ion-input
for the amount being converted. We get the values of the currency from the currenciesStore
, which is the MobX store that gets the list of currencies from. In the IonSelect
components we set the onIonChange
props to handler functions which set the drop down’s values. We also set the placeholder for all the inputs and selects. In the IonInput
component, we do the same with the onIonChange
handler. We show the right value by using the variables set in the values
object during change events.
When the user clicks Convert, we run the convertCurrency
function. We check if the values are set correctly before running the rest of the code. If that succeeds, then we run the getExchangeRates
function imported from requests.js
, then we set the final toAmount
by multuplying the rate with the amount.
The useEffect
callback is used for excluding the currency to convert from from the currency to convert to list and vice versa. The array in the second argument of useEffect
specifies which values to watch for.
The observer
function in the last line is for designating the component to watch the latest values from the MobX stores.
Next in Home.tsx
, we replace the existing code with:
import {
IonButtons,
IonContent,
IonHeader,
IonItem,
IonLabel,
IonList,
IonListHeader,
IonMenuButton,
IonPage,
IonTitle,
IonToolbar,
IonSelect,
IonSelectOption
} from "@ionic/react";
import React from "react";
import "./Home.css";
import { getExchangeRates } from "../requests";
import { CurrencyStore } from "../stores";
import { observer } from "mobx-react";const HomePage = ({ currencyStore, currenciesStore }) => {
const [rates, setRates] = React.useState({});const getRates = async () => {
const { data } = await getExchangeRates(
(currencyStore as CurrencyStore).currency
);
setRates(data.rates);
};React.useEffect(() => {
getRates();
}, [(currencyStore as CurrencyStore).currency]);
return (
<IonPage>
<IonHeader>
<IonToolbar>
<IonButtons slot="start">
<IonMenuButton />
</IonButtons>
<IonTitle>Home</IonTitle>
</IonToolbar>
</IonHeader>
<IonContent>
<IonList lines="none">
<IonListHeader>Latest Exchange Rates</IonListHeader> <IonItem>
<IonLabel>Currency</IonLabel>
<IonSelect
placeholder="Select One"
onIonChange={ev => {
(currencyStore as CurrencyStore).setCurrency(
ev.target && (ev.target as any).value
);
}}
>
{Object.keys(currenciesStore.currencies).map(key => {
return (
<IonSelectOption
value={key}
key={key}
selected={
(currencyStore as CurrencyStore).currency
? key == (currencyStore as CurrencyStore).currency
: key == "AUD"
}
>
{(currenciesStore.currencies as any)[key]}
</IonSelectOption>
);
})}
</IonSelect>
</IonItem>
</IonList> <IonList lines="none">
<IonListHeader>Exchange Rates</IonListHeader>
{Object.keys(rates).map(key => {
console.log(rates);
return (
<IonItem>
{key}: {rates[key]}
</IonItem>
);
})}
</IonList>
</IonContent>
</IonPage>
);
};export default observer(HomePage);
In this file, we display the exchange rates from an API. We get the currencies from the currenciesStore
so that users can see exchange rates based on different currencies. The items are display in an IonList
provided by Ionic.
The observer
function in the last line is for designating the component to watch the latest values from the MobX stores.
Next in App.tsx
, replace the following code with:
import React from "react";
import { Redirect, Route } from "react-router-dom";
import { IonApp, IonRouterOutlet, IonSplitPane } from "@ionic/react";
import { IonReactRouter } from "@ionic/react-router";
import { AppPage } from "./declarations";import Menu from "./components/Menu";
import Home from "./pages/Home";
import { home, cash } from "ionicons/icons";/* Core CSS required for Ionic components to work properly */
import "@ionic/react/css/core.css";/* Basic CSS for apps built with Ionic */
import "@ionic/react/css/normalize.css";
import "@ionic/react/css/structure.css";
import "@ionic/react/css/typography.css";/* Optional CSS utils that can be commented out */
import "@ionic/react/css/padding.css";
import "@ionic/react/css/float-elements.css";
import "@ionic/react/css/text-alignment.css";
import "@ionic/react/css/text-transformation.css";
import "@ionic/react/css/flex-utils.css";
import "@ionic/react/css/display.css";/* Theme variables */
import "./theme/variables.css";
import ConvertCurrencyPage from "./pages/ConvertCurrencyPage";
import { CurrencyStore, CurrenciesStore } from "./stores";
import { getCurrenciesList } from "./requests";const currencyStore = new CurrencyStore();
const currenciesStore = new CurrenciesStore();const appPages: AppPage[] = [
{
title: "Home",
url: "/home",
icon: home
},
{
title: "Convert Currency",
url: "/convertcurrency",
icon: cash
}
];const App: React.FC = () => {
const [initialized, setInitialized] = React.useState(false); const getCurrencies = async () => {
const { data } = await getCurrenciesList();
currenciesStore.setCurrencies(data);
setInitialized(true);
};React.useEffect(() => {
if (!initialized) {
getCurrencies();
}
}); return (
<IonApp>
<IonReactRouter>
<IonSplitPane contentId="main">
<Menu appPages={appPages} />
<IonRouterOutlet id="main">
<Route
path="/home"
render={() => (
<Home
currencyStore={currencyStore}
currenciesStore={currenciesStore}
/>
)}
exact={true}
/>
<Route
path="/convertcurrency"
render={() => (
<ConvertCurrencyPage
currenciesStore={currenciesStore}
/>
)}
exact={true}
/>
<Route exact path="/" render={() => <Redirect to="/home" />} />
</IonRouterOutlet>
</IonSplitPane>
</IonReactRouter>
</IonApp>
);
};export default App;
We modified the routes so that we use the render
prop instead of the component
prop since we want to pass in our MobX stores into the components. The stores store the currently selected currency for the home page and the list of currencies for both pages.
We get the list of currencies here for both components and set the data in store for both to retrieve by setting it in store.
Next create requests.ts
in the src
folder and add:
const axios = require('axios');
const APIURL = 'https://api.exchangeratesapi.io';
const OPEN_EXCHANGE_RATES_URL = 'http://openexchangerates.org/api/currencies.json';
export const getExchangeRates = (baseCurrency: string) => axios.get(`${APIURL}/latest?base=${baseCurrency}`)
export const getCurrenciesList = () => axios.get(OPEN_EXCHANGE_RATES_URL)
These are the code for making the HTTP requests to get the currencies and exchange rates.
Next create store.ts
to and add:
import { observable, action } from "mobx";class CurrencyStore {
@observable currency: string = 'AUD'; @action setCurrency(currency: string) {
this.currency = currency;
}
}class CurrenciesStore {
@observable currencies = {}; @action setCurrencies(currencies) {
this.currencies = currencies;
}
}export { CurrencyStore, CurrenciesStore };
The CurrencyStore
is for storing the selected currency in the home page to show the exchange rates based on the selected currency. currency
is the value observed by the home page and setCurrency
sets the currency value. Similarly, CurrenciesStore
stores a list of currencies retrieved in App.tsx
where setCurrencies
is called.
Next in tsconfig.json
, we replace the existing code with:
{
"compilerOptions": {
"experimentalDecorators": true,
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "preserve",
"noImplicitAny": false
},
"include": [
"src"
]
}
to noImplicitAny
and set it to true
.
Finally, in index.html
, replace the existing code with:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Currency Converter</title><base href="/" /><meta
name="viewport"
content="viewport-fit=cover, width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
/>
<meta name="format-detection" content="telephone=no" />
<meta name="msapplication-tap-highlight" content="no" /><link
rel="shortcut icon"
type="image/png"
href="%PUBLIC_URL%/assets/icon/favicon.png"
/><!-- add to homescreen for ios -->
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-title" content="Ionic App" />
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
</head><body>
<div id="root"></div>
</body>
</html>
to change the title.
After all the hard work is done, we get:
#React #Ionic #webdev #webapp #javascript