Build Mobile iOS, Android, and Web Apps Using React and Ionic, Part II

Write Once Run Anywhere Is Alive And Well In Modern JavaScript Development

This is Part II in a series. Part I described what Ionic is and how to setup a starter project. In this second article I’ll demonstrate how similar Ionic development is to React. And we’ll continue to build out our camera app.

In developing this story I decided to use a photo application, as it demonstrates the hardware interface capabilities of Ionic. Note to keep things simple I’ll always refer to Ionic related components as Ionic, even when the capability may be coming from Stencil or Capacitor. When a distinction would be helpful I’ll be more specific (if you’re not sure what I’m referring to, please read Part I of my story). So let’s layout what we’ll build in some detail before we begin coding. Our app, MyPhotos, will do what most camera focused applications do: allow users to take pictures with the device camera and then display those pictures in a list. For navigation, we’ll use tabs. We’ll have one tab link to a screen that provides our camera access. And we’ll have another tab that uses a list to show thumbnail views of all our app images. We won’t be building a Photos killer, but this exercise should be good enough to help you get a feel for Ionic mobile development on iOS devices. Note I’m skipping Android to make the series a little shorter, but it’s also a fully supported platform.

Getting Started With Code

In order to begin coding with Ionic we need to know what assets are available and how writing code with Ionic actually works. Let’s start doing this by opening our MyPhotos project from Part I and taking a look at the contents of src.

If you open the App.tsx file again you’ll see that our chosen style of UI navigation is tabs (I won’t show the file again as it’s very long and I’ve already displayed it in Part I). Having said that, the actual routing to screens is done with React Router’s Route tags. The components you see like IonReactRouter and IonRouterOutlet are just wrappers to allow Ionic access to React Router services. Now as you look through the folders you’ll see pages and theme. The React components inside pages represent screens on the phone, and those screens are displayed whenever the relevant tab element has been pressed. You might be wondering how url routing ties into a locally running mobile app, but there’s actually no contradiction here. You’ll recall React Router uses virtual routing, that is the routes are not actually existing on a server. They are local to the application and allow the React app to trigger screen loads based upon those url routes. So the Ionic team realized there was no reason to throw out React Router just to do a mobile app. Everything still just works.

Now let’s look at one of the pages files. In the App.tsx file on the first Route tag we see the url “/tab1”, which is associated to the Tab1 component inside the pages folder. Let’s open that file and take a look.

import {
} from '@ionic/react';
import { book, build, colorFill, grid } from 'ionicons/icons';
import React from 'react';
import './Tab1.css';
const Tab1: React.FC = () => {
return (
<IonTitle>Tab One</IonTitle>
<IonCard className="welcome-card">
<img src="/assets/shapes.svg" alt="" />
<IonCardSubtitle>Get Started</IonCardSubtitle>
<IonCardTitle>Welcome to Ionic</IonCardTitle>
Now that your app has been created, you'll want to start building out features and
components. Check out some of the resources below for next steps.
<IonList lines="none">
<IonItem href="; target="_blank">
<IonIcon slot="start" color="medium" icon={book} />
<IonLabel>Ionic Documentation</IonLabel>
<IonItem href="; target="_blank">
<IonIcon slot="start" color="medium" icon={build} />
<IonLabel>Scaffold Out Your App</IonLabel>
<IonItem href="; target="_blank">
<IonIcon slot="start" color="medium" icon={grid} />
<IonLabel>Change Your App Layout</IonLabel>
<IonItem href="; target="_blank">
<IonIcon slot="start" color="medium" icon={colorFill} />
<IonLabel>Theme Your App</IonLabel>
export default Tab1;
view raw Tab1 hosted with ❤ by GitHub

The Tab1 component represents the first screen that loads when you run the command ionic serve to start the app. Let’s go through the code. As stated previously Hooks is supported out of the box so the component is a functional one. Next in the JSX we can see that IonPage is the root container. Every component in the pages folder should start with an IonPage component at its root. Next we have the IonHeader this obviously represents a container for the very top header of the screen. If you have a header that repeats, you can of course put this code into your own header React component and reuse it. Just below IonHeader is the IonContent component; as the name implies this represents the body of your screen. You should have only one IonContent per screen. This component mostly controls scroll related events in the body, for example ionScrollStart, ionScroll, ionScrollEnd — the names are pretty self explanatory of what they do. Note all standard HTML tags that React supports are still supported. But by using the Ionic components you are getting theme-able, pre-built, great looking controls, with lot’s of additional functionality for free. In other words an Ionic React project, is still just a React project. It’s just that in addition to standard React features Ionic components and services have been added. Take a look at all the control choices here. OK, we’re now almost ready to make changes to code but let’s also take a look at the theme folder and styling.

The theme folder contains a file called variables.css. It is an easy to access container for all of the theme related colors that Ionic uses in its component styles. Modifying this file allows you to quickly change the color scheme of your app from a single location (in a later post I’ll get into how to use this file to create a dark theme for your application). The syntax may seem strange, but attributes like “— ion-color-primary” are actually variables, much like the variables inside SASS files. This capability is a newer feature in standard CSS and allows you to define a variable, for example a color, and reuse it throughout your app. It even works inside your JSX style attributes. After defining a variable, you use it by calling var. For example

color: var(--ion-color-primary);

Placing this attribute inside your CSS or style property allows you to reference the variable’s true value by just passing the name. So instead of using #0cd1e8 use a meaningful variable name instead.

In addition to CSS variables Ionic components have a consistent set of properties that can be used as shortcuts to quickly change the style of an element. For example most Ionic components have a property called color. The value of color can be a string that represents one of the variables from the variables.css file. However instead of using the full variable name you use just the last word. So for example if you use

<IonButton color=”secondary”>My Button</IonButton>

your button will receive the color of the variable “ — ion-color-secondary”. Obviously writing that short property is better than doing something like

<IonButton style={{ color: '#1ed7f82' }}></IonButton>

Not only for length, but also readability. Additionally Ionic components are usually aggregate HTML and Web Component elements. So setting for example the style color will not necessarily have the desired effect, because you might be changing the color of a different element than the one you intended. So again, setting the property of the Ionic component is a shorter way of getting the desired style change. Let’s hook into this Ionic capability by creating our own color variable that we will use for emphasizing certain elements. Open variables.css and add the following

--ion-color-accent: #69bb7b;--ion-color-accent-rgb: 105, 187, 123;--ion-color-accent-contrast: #ffffff;--ion-color-accent-contrast-rgb: 255, 255, 255;--ion-color-accent-shade: #5ca56c;--ion-color-accent-tint: #78c288;

Now we need to create a new CSS file in order to add a class that maps to our newly created variables.css entry. So create a file called base.css inside of the theme folder and add

.ion-color-accent {--ion-color-base: var(--ion-color-accent);--ion-color-base-rgb: var(--ion-color-accent-rgb);--ion-color-contrast: var(--ion-color-accent-contrast);--ion-color-contrast-rgb: var(--ion-color-accent-contrast-rgb);--ion-color-shade: var(--ion-color-accent-shade);--ion-color-tint: var(--ion-color-accent-tint);}

We needed to create this class in order for Ionic to recognize a new CSS variable as an Ionic component property. Now let’s go back to the App.tsx file and add our base.css import so that all components can get this class. Type under the variables.css import

import './theme/base.css';

Now let’s open Tab1.tsx and try resetting all the color properties of the bottom IonIcon entries to use “accent”. It will look like this

The project should auto compile and then your screen should have the new color

As you can see the icons under Resources have changed to pink.

Creating Our First Screen

Let’s start by adding a new file called Camera.tsx and write that from scratch. But before we do this we’ll need to install two more packages. The first is pwa-elements. This package is a Web Components package built with Stencil. It provides the interface for the Camera, and other device services, but only for browsers. On mobile devices you will get the native camera interface. The next package is react-hooks. This package exposes much of Capacitors capabilities as hooks. So we will be able to access the camera by using hooks.

npm i @ionic/pwa-elements @ionic/react-hooks

After installing these two packages open the index.ts file and update like below

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import * as serviceWorker from './serviceWorker';
import { defineCustomElements } from '@ionic/pwa-elements/loader';
ReactDOM.render(<App />, document.getElementById('root'));
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers:
view raw index.ts hosted with ❤ by GitHub

From pwa-elements we are importing defineCustomElements and then wrapping the window object so that calls can be intercepted. Again, this means at runtime on the browser this will cause any calls to the camera or other supported services to have an Ionic UI presentation. If you don’t do this you will get an error like below, when running in the browser, as the desired interface will not exist.

Next let’s create the Camera.tsx file if we have not done so already. For now just create an empty Camera function that is exported. We’ll fill it in later. Now let’s update the tab element inside of Apps.tsx to point to our new Camera.tsx file. We’ll start from the top of the file. Since Ionic uses the ionicons library for its own icons, we need to update the import statement using ‘ionicons/icons’ to replace the flash icon with the camera icon. Next go down to the first Route element and replace the path property with “/camera” and the component property with Camera. We’ll associate this route to the tab later. Next update the last Route element and change the Redirect to property to be “/camera”, so that the camera screen loads by default. And then on the first IonTabButton let’s change the tab property to be “camera” and href to be “/camera”. Since Ionic has integrated React Router to their own controls this change allows a click on this tab to trigger the “/camera” route. Next change IonIcon’s icon property to be “camera” and IonLabel’s text to be Camera. Once complete you should see this code.

import React from 'react';
import { Redirect, Route } from 'react-router-dom';
import {
} from '@ionic/react';
import { IonReactRouter } from '@ionic/react-router';
import { apps, camera, send } from 'ionicons/icons';
import Tab2 from './pages/Tab2';
import Tab3 from './pages/Tab3';
import Details from './pages/Details';
/* 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 Camera from './pages/Camera';
const App: React.FC = () => (
<Route path="/camera" component={Camera} exact={true} />
<Route path="/tab2" component={Tab2} exact={true} />
<Route path="/tab2/details" component={Details} />
<Route path="/tab3" component={Tab3} />
<Route path="/" render={() => <Redirect to="/camera" />} exact={true} />
<IonTabBar slot="bottom">
<IonTabButton tab="camera" href="/camera">
<IonIcon icon={camera} />
<IonTabButton tab="tab2" href="/tab2">
<IonIcon icon={apps} />
<IonLabel>Tab Two</IonLabel>
<IonTabButton tab="tab3" href="/tab3">
<IonIcon icon={send} />
<IonLabel>Tab Three</IonLabel>
export default App;
view raw Camera App.tsx hosted with ❤ by GitHub

Now let’s start updating the Camera file too. Since this article is getting a bit long let’s just get the camera up and running and in the next part we’ll fix the styling. Add this code into the Camera.tsx file.

import React, { useCallback, useEffect } from 'react';
import { CameraResultType } from '@capacitor/core';
import { useCamera, availableFeatures } from '@ionic/react-hooks/camera';
import { IonButton, IonContent, IonHeader, IonToolbar, IonTitle, IonPage, IonText } from '@ionic/react';
const Camera: React.FC = () => {
const { photo, getPhoto } = useCamera();
const triggerCamera = useCallback(async () => {
if(availableFeatures.getPhoto) {
await getPhoto({
quality: 100,
allowEditing: false,
resultType: CameraResultType.Uri
}, [getPhoto]);
useEffect(() => {
console.log('photo', photo);
}, [photo]);
const onClick = () => {
if(availableFeatures.getPhoto) {
return (
{photo && <img src={photo.webPath} alt="my pic" />}
<IonButton onClick={onClick}>
return (
<IonText>No Camera Available</IonText>
export default Camera;
view raw Camera.tsx hosted with ❤ by GitHub

Starting from the top of the file it shows we need two new imports: @capacitor/core and @ionic/react-hooks/camera. Capacitor provides the core services to interface into hardware and react-hooks converts those calls into hooks style calls. If you have not enabled Capacitor yet you’ll need to do so first.

ionic integrations enable Capacitor
ionic cap add ios

Moving further down you’ll notice the call to useCamera and the returned properties: photo and getPhoto. GetPhoto initializes the camera instead of retrieving the taken image, which confused me a bit but nevertheless that’s what it does. The photo object is the object that will receive the image data once the actual picture is taken. Depending on the options given to getPhoto it will have the images data and path information or a base64 encoded string representing the image. After getPhoto you can see we call useCallback to memoize the actual call to getPhoto. The resultant triggerCamera call is what is used to trigger the actual initialization of the camera later in the code. After the useCallback call, you can see there is a test for the availability of the getPhoto function, availableFeatures.getPhoto. In the @ionic/react-hooks component there is a separate folder for each device service. In our case, as is shown, we are using the camera. In each service folder there is an availableFeatures call that allows you to test if the device has that capability or not. Since we are working cross platform and across devices, having this sort of testing ability is a good thing. Next the actual call to getPhoto shows that it is passed some parameters when called. The most important of which is the resultType, which we will give the CameraResultType.Uri parameter. Using the Uri type gives us a property called webPath, which we can use on our img tags since html img elements only recognize url strings and not file paths. After that you can see some fairly obvious code like useEffect, which is doing logging of what’s inside the photo object, and the camera button click handler, which again is only initializing the camera. But after this code is the JSX which contains an IonPage root container, another header and of course the IonContent, which has the button and an img tag that will receive the picture once clicked. Now that we’ve set everything up let’s do a run of the app. First we need to build the app again and make sure everything compiles so type the following

ionic build
ionic cap copy ios
ionic cap sync ios
ionic cap open ios

The commands above do the following. First we build the app, with ionic build. Next we copy the updated source files over to XCode. Then we also need to bring over any iOS specific plugins so that Capacitor can connect our web view to the relevant device services. We use sync for that. And finally we open the project. After executing these commands try and run the iOS XCode project on your device (I’ve already gone over how to setup the XCode project in Part I). Here’s a picture of the final screen with an incredible photo of my desk lamp!

The End (for Part II anyway)

Some of you who are not familiar with native coding might find the process a little arduous. Not only did we write code and add imports that were not necessarily obvious. We also had to execute command line calls to make sure that the native project build was always in sync with the web build, which also means we’ll have to do that for every platform we intend to support. However let’s think about the end result here. Without writing a lick of native code. That by the way, we would have otherwise had to write 3 or more times (iOS, Android, Desktop, Web). We managed to get access to the camera across device platforms with only a single code base and using Hooks coding style to boot. To me this is quite amazing.

This is the end of Part II. Source code can be found here, In the next section we’ll make our camera screen a little prettier using Cards and some alignment of the image. And we’ll learn about the useStorage hook so that we can save our photos locally and then list them on our second tab with thumbnails.

Published by David Choi

Developer Lead Where devs help devs and make money.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

Create your website with
Get started
<span>%d</span> bloggers like this: