How to build a Mobile App with TypeScript, Ionic, React, and MobX

How to build a Mobile App with TypeScript, Ionic, React, and MobX

In this post, you'll learn how to build a mobile app with TypeScript, Ionic, React, and MobX. Building a mood tracker app with TypeScript, Ionic, React, and MobX. We will then make it publicly accessible by deploying the app on Netlify.

Intro

In this post, I will walk you through building a mood tracker app with TypeScript, Ionic, React, and MobX. We will then make it publicly accessible by deploying the app on Netlify.

I have been building apps with Ionic for around four years now, and I still think it is a very impressive tool to have in your kit. It’s shiny, cutting-edge, full of features, developer-friendly, and, honestly, the list could go on.

I find it amazing how the Ionic team, after four years, still keeps the momentum and ships features and tools to empower its user base. React integration is one such feature that I believe is going to turn a lot of head in the dev community towards Ionic and its ecosystem.

Ionic has been historically Angular/TypeScript-based, and while its core is still developed with Angular, the team has recently spent a lot of time and resources to integrate frameworks like React and Vue to lower the barrier to entry for developers of all tastes.

I personally have no agenda against Angular, but I would be lying if I said I don’t prefer React. So naturally, as soon as Ionic announced React integration, I jumped right in and started playing with it. So far, the ease of use and the plethora of components seem too good to be true.

For the impatients

Ain’t nobody got time for reading a roughly 7,000-word blog post without knowing what they are getting into. So, for the impatients (like me):

  • Here’s the GitHub repo that contains the entire app
  • Here’s the app itself for you to take it for a quick spin. I recommend using a mobile device to access it since it’s only optimized for mobile viewing. However, it should also work on your desktop browser
  • Here’s a quick look at the end product:

Who is this for?

In order to follow this post, I would expect you to:

  • Be running a *nix based system (Linux or macOS) — sorry, Windows people (I know, it’s almost 2020, I should get off my high horse, right?)
  • Have a basic understanding of npm, TypeScript, and React
  • Have some familiarity with terminal and web development in general
  • Have your development environment set up with at least Android Studio and Xcode
  • Understand that all of the above rules are some authoritarian propaganda to stop you from reaching the supreme height in your career, and I’m not your mom, so I can’t really tell you what you can and can’t do — so it’s entirely up to you if you want to set sail on this epic journey or not
Scaffolding

The first thing you need is the Ionic npm package installed on your machine. Running npm install -g [email protected] on your terminal should get that sorted for you. Now cd into the directory where you want to create your Ionic app folder and run ionic start moodular blank --type=react.

This will drop a new folder called moodular with a bunch of files and folders in it, and the output of the command will give you a preview of what to do next. Below is what I’m seeing at the time of writing this post.


Let’s do what Ionic told us to do:

cd ./moodular
ionic serve

It should automatically open http://localhost:8100/home in your browser. If it doesn’t, open a new tab and navigate to that page in your browser to see it in action. Beautiful, right…? Nah! You’re right, it’s embarrassing. Let’s make it better — let’s get coding!

Tabs and pages

Let’s think through our app’s features for a second. For this very minimalistic app, we will need two pages. On the page that loads when I first open the app, I want to be able to log my current mood. Then, we will need another page where I can go through my previously logged moods — a History page, if you will. To allow navigating between the pages, we will use a bottom tab navigation.

The scaffolding in the previous page already gave us a page in the src/pages/Home.tsx file. Let’s create one more file in the folder, touch src/pages/History.tsx, and paste in the following code in that new file:

import { IonContent, IonPage } from '@ionic/react';
import React from 'react';

const History: React.FC = () => {
    return (
        <IonPage>
            <IonContent className="ion-padding">
                <p>History page</p>
            </IonContent>
        </IonPage>
    );
};

export default History;

Here, we are creating a React functional component named History and exporting it. The component renders an IonPage component with IonContent inside it. These components are layout building-block components Ionic provides out of the box. I just copied and pasted those from the autogenerated Home.tsx file and stripped some of the unnecessary markup.

Now that we have both pages, let’s show a footer tab bar and enable navigation between the pages. We want our tab bar to be available in every page, so it would make sense for it to be in a generic place. We will put it in the src/App.tsx file. Open that file and replace the App component’s internal with the following:

   <IonApp>
        <IonReactRouter>
            <IonTabs>
                <IonRouterOutlet>
                    <Route path="/today" component={Home} exact={true} />
                    <Route path="/history" component={History} exact={true} />
                    <Route
                        exact
                        path="/"
                        render={() => <Redirect to="/today" />}
                    />
                </IonRouterOutlet>

                <IonTabBar slot="bottom">
                    <IonTabButton tab="today" href="/today">
                        <IonIcon icon={calendarIcon} />
                        <IonLabel>Today</IonLabel>
                    </IonTabButton>

                    <IonTabButton tab="history" href="/history">
                        <IonIcon icon={happyIcon} />
                        <IonLabel>History</IonLabel>
                    </IonTabButton>
                </IonTabBar>
            </IonTabs>
        </IonReactRouter>
    </IonApp>

We are using a few new Ionic components here that weren’t there before, so we have to make sure they’re imported properly. Scroll back up to the top of the file and find the line that looks like import {...} from '@ionic/react' and replace that with this:

import { IonApp, IonRouterOutlet, IonTabs, IonTabBar, IonTabButton, IonIcon, IonLabel } from '@ionic/react';

Throughout this post, in various places, we will be using Ionic components, and sometimes, I might forget to explicitly remind you to import it from '@ionic/react'. Please let me know if you find such instances and I will fix it.

Now, let’s break down the code and see what we are doing.

IonRouterOutlet component

<IonRouterOutlet>
    <Route path="/today" component={Home} exact={true} />
    <Route path="/history" component={History} exact={true} />
    <Route
        exact
        path="/"
        render={() => <Redirect to="/today" />}
    />
</IonRouterOutlet>

This component lets you define your routes/pages for your app. Its children need to be <Route> components with a path and a component prop at least. The path prop tells Ionic what the URL will look like when the route is hit, and the component defines which component will be rendered when that URL/route is hit.

In our code, we have defined two routes for two of our pages: Home and History. Notice that we haven’t imported our newly created History component yet, so let’s go back to the top again and import it right after our Home import:

import Home from './pages/Home';
import History from './pages/History';

The third <Route> here is a default catcher. If the user does not specify a route, it takes them to /today by default using the <Redirect>, which, in turn, renders our Home component.

You can read on about routing in Ionic here.

Note: This is mostly using the react-router-dom package and has nothing to do with Ionic itself, so if you’re familiar with the React ecosystem, this should look familiar to you.

IonTabBar component

<IonTabBar slot="bottom">
    <IonTabButton tab="today" href="/today">
        <IonIcon icon={calendarIcon} />
        <IonLabel>Today</IonLabel>
    </IonTabButton>

    <IonTabButton tab="history" href="/history">
        <IonIcon icon={happyIcon} />
        <IonLabel>History</IonLabel>
    </IonTabButton>
</IonTabBar>

This component creates a tab bar that you can either put at the top or the bottom of your page using the slot prop. Inside the component, we are rendering two IonTabButton components that each contain an icon and text.

Each of the buttons specifies an href prop that pretty much works as a link button. So, when you tap on them, the browser will take you to the designated URL. Here, the href values have to match with the path prop values we defined for our <Route> components.

The icons are rendered using the IonIcon component, where the icon prop specifies which icon to show. We need to import those icons from the ionicons package. Add the following import near the top of the file:

import { calendar as calendarIcon, happy as happyIcon } from 'ionicons/icons';

IonTabs component

Finally, we wrap all of that in the IonTabs component, which needs to be inside the IonReactRouter component for the navigation to work properly.

Let’s check out what we have done so far. Going back to the browser, you should see something like this:

That wasn’t too complicated, right? With the help of a few out-of-the-box tools, we managed to set up a seamless navigation between two pages that plays nicely with browser URLs.

Best invention of the 21st century: Emojis!

OK, maybe that’s a bit of an overexaggeration, but you can’t deny how intertwined our daily lives are with emojis these days. There’s always an emoji for almost anything you want to say. So for this app, we will use emojis to represent our moods. We will use emoji-mart as the picker instead of built-in native/keyboard emojis.

First, of course, install the package: npm install --save emoji-mart. And since we are going to be using it through TypeScript, we will need to run npm i --save-dev @types/emoji-mart to install the types for that package.

Next, we need to include its stylesheet. Let’s add that in the src/App.tsx file where other CSS files are included:

/* Third-party styles */
import "emoji-mart/css/emoji-mart.css";

Now, let’s open the src/pages/Home.tsx file and get rid of the autogenerated content to put our emoji picker in it:

// ... other imports
import { Picker } from "emoji-mart";

// ... component definition
<IonPage>
    <IonContent className="ion-padding">
        <h2>
            Moodular
        </h2>

        <Picker
            title="Pick your mood"
            include={["people"]}
        />
    </IonContent>
</IonPage>

In the Picker component, we are passing the include prop to make sure that only the “people” emoji set shows up in the picker. The package has a number of other emoji sets that you can include/exclude as per your need.

At this point, on the homepage, you should see the emoji picker like below:

Data and state

Heads-up, things will get a bit more muddy now. Scaffolding and layouts are usually the easier side of an app when compared to data and state management — especially when using Ionic since you get all these built-in components to make your app look good.

But Ionic lets you choose your own data management tool. I don’t want this post to be your intro to data science or big data, so I’ll keep it minimal. All our data will be stored in the browser’s localStorage, and for reactively managing state, we will use MobX. Before we get into those, though, let’s think through the behavior we want to implement.

As a user, I would like to select an emoji that represents my current mood, and additionally, I would want to write a brief note regarding my mood and maybe set a date for the entry just in case we want to log a mood from a past date. Once logged, the mood entry should go into a list of my previously logged moods that I can browse through in the History page.

With the above considerations, we can conclude that each mood can be an object with an emoji, a details, and a date property. To give every entry a unique identifier, let’s add an id property to the mood object as well.

This is pretty much how you would model your data if a backend/database was involved here, and staying close to that kind of data structure helps in integrating a backend later on.

Let’s create a MoodService.ts file in the src/pages/ folder. I know that’s not an ideal place for a service file, but we don’t have a lot of modules for this app, so let’s leave it there for now. Add the following code in it:

import { EmojiData } from "emoji-mart";
import { persist } from "mobx-persist";
import { observable, computed, action } from "mobx";

export interface Mood {
    emoji: EmojiData,
    details: string,
    date: Date,
    id: number,
};

export class MoodStore {
    @persist('list')
    @observable
    list: Mood[] = [];

    @computed get entries(): Mood[] {
        const sortFunc = (firstItem: Mood, secondItem: Mood): number => {
            if (firstItem.id > secondItem.id)
                return 1;

            if (firstItem.id < secondItem.id)
                return -1;

            return 0;
        };

        return this.list.slice().sort(sortFunc);
    };

    @action
    save(emoji: EmojiData, details: string, date: Date) {
        this.list.push({emoji, details, date, id: Date.now()});
    };

    @action
    remove(moodId: number) {
        const moodIndex = this.list.findIndex(({ id }) => moodId === id);
        this.list.splice(moodIndex, 1);
    };
};

That’s a lot to take in, but don’t worry — we will break it down into manageable bits and go through them one by one.

Before we do that, though, please go to the MobX website and read its summary, concepts, and principles to get a basic understanding of why this package exists and why it is used. Oh, and don’t forget to install the two new packages we are importing in this file first: npm install --save mobx mobx-persist.

The first thing you will notice is the Mood interface. As planned above, we have the necessary properties defined in this interface, and it will be used as a single mood entity logged by the user. Noteworthy here is the EmojiData type. Each emoji in the emoji-mart package is represented as EmojiData, which is why the emoji data in this interface has that data type.

Observables

export class MoodStore {
    @persist('list')
    @observable
    list: Mood[] = [];

Here, we are creating a MoodStore class, which will be our main state container for the app. It has one observable property, list, which is an array of Mood entries.

Using the @observable decorator, we are telling MobX to keep track of changes made to this property. Using the @persist('list') decorator, we are telling the mobx-persist package to save this array in the browser’s localStorage, and the list parameter tells the persistor that this property is an array.

Decorators are not yet universally supported, so you might need to tell Ionic’s TypeScript compiler to recognize the use of it in your code. Add "experimentalDecorators": true to the compilerOptions property in the tsconfig.json file in the root of the project.

Actions

 @action
    save(emoji: EmojiData, details: string, date: Date) {
        this.list.push({emoji, details, date, id: Date.now()});
    };

    @action
    remove(moodId: number) {
        const moodIndex = this.list.findIndex(({ id }) => moodId === id);
        this.list.splice(moodIndex, 1);
    };

MoodStore also has a couple of actions that are essentially operations on the store’s data. Quite apparent from their names, the save action adds a new mood to the list and the remove action removes a mood from the list.

We are simply using JavaScript array manipulation methods to perform those actions in this case, but eventually, you would hook it up with a server API or a local database for persistence.

The list property, being an @observable, will be reactive, and MobX will automatically update our React components when anything is added/removed/updated to the property when we link the store and the component.

When calling save, we have to pass it emoji, details, and date, then the method will fill in the id property automatically using Date.now(), which returns a Unix timestamp. Not super unique in the grand scheme of things, but since all our data is local right now, this will do.

For remove, we only need to pass in the id of the mood entry, and it will take it out of the list array using the splice() method.

Computed properties

  @computed get entries(): Mood[] {
        const sortFunc = (firstItem: Mood, secondItem: Mood): number => {
            if (firstItem.id > secondItem.id)
                return 1;

            if (firstItem.id < secondItem.id)
                return -1;

            return 0;
        };

        return this.list.slice().sort(sortFunc);
    };

In MobX, we use observables to keep data in raw state. For display purposes, we might need to access and derive different representations of the data in various ways, which is where @computed properties come in. They let you define properties that return a derived/computed version from a store’s raw data.

In this case, we define an entries property, which sorts the observable list array and then sorts it descendingly by its date property. When displaying our previously logged moods in the History page, we will use this property to show moods in reverse chronological order.

Notice that we are doing slice() on the list array because JavaScript sort is mutating, and directly mutating an @observable array can cause all sorts of problems.

This is pretty much the core of the entire data/state management system of our app. Pretty concise, right? I hope the explanation above helped you understand it, but don’t worry if it didn’t. We will slowly start integrating this service into our Home and History components, and then it will become more apparent why things are the way that they are in this file.

Connecting MobX store with components

Right now, our MoodService exists in a vacuum in the sense that none of our pages/components are using the store. Let’s fix that. We need a new package, mobx-react, for that. Install it with npm i --save mobx-react, then open the src/App.tsx file and wrap the IonicReactRouter component like this:

//... other imports 
import { Provider } from "mobx-react";
import { create } from "mobx-persist";

//... other imports
import { MoodStore } from "./pages/MoodService";

//... other imports
const App: React.FC = () => {
    const hydrate = create({});
    const moodStore = new MoodStore();

    hydrate("moodStore", moodStore);

    return (
        <IonApp>
            <Provider moodStore={moodStore}>
                <IonReactRouter>
                //....tabs and other components
                </IonReactRouter>
            </Provider>
        </IonApp>
    );
};

First we are instantiating our MoodStore class. Then we are hydrating the moodStore instance to ensure that, on first load, mobx-persist loads up the previously saved state of the store from localStorage.

It looks a bit complicated with all the functional programming mumbo-jumbo and terms like “hydrating,” but in plain English: if we close our app and reopen it, or close the browser tab and reopen it, all our previously saved moods will still be there in the moodStore instance. The Provider component makes our moodStore instance available as props to the internal components.

This only connects our pages with the MobX store, but we are not using the store in our pages… yet.

Still with me? I hope so. We have done so much, and you deserve a break. Go have a walk, drink a glass of the liquid of your choice, and then come back to continue on with the rest of the post.

… Back? Alright, let’s finish it off.

Saving mood with MobX

Now we get to play more with the magic of React’s and MobX’s state management. We will start with the src/pages/Home.tsx file. When users tap on an emoji, we will show a popup where additional details and a date for the mood entry can be specified. We will use the IonAlert component for this:

   <Picker 
        title="Moodular"
        include={["people"]} 
        onSelect={handleEmojiSelect} 
    />

    <IonAlert
        isOpen={showInputs}
        subHeader="Add more details or set the time/date for your mood"
        onDidDismiss={() => setShowInputs(false)}
        header={`Add Details`}
        inputs={[
            {
                type: "text",
                name: "details",
                placeholder: "Write out how you are feeling..."
            },
            {
                name: "date",
                type: "date",
                max: `{new Date()}`,
                min: "2017-09-09",
                value: new Date(),
                placeholder: "Change date"
            }
        ]}
        buttons={[
            {
                text: "Cancel",
                role: "cancel",
                cssClass: "secondary",
                handler: () => {
                    setShowInputs(false);
                    setEmoji(null);
                }
            },
            {
                text: "Ok",
                handler: data => {
                    moodStore.save(emoji, data.details, data.date)
                }
            }
        ]}
    />

Before we break down this code, let’s import IonAlert from @ionic/react at the top of our file: import { IonContent, IonPage, IonAlert } from '@ionic/react';

The first change in our existing code is the new onSelect prop in the <Picker> component, and it takes a function as it’s value. We are giving it the function handleEmojiSelect, which we haven’t defined yet.

Before we define that, let’s take a quick look at the IonAlert component. Its isOpen prop determines whether the alert box should be on display. We want to show the alert box when an emoji is tapped, right? So, naturally, the value of isOpen should be a state variable that is toggled between true and false based on user interaction.

Also, we won’t be logging the mood entry as soon as the emoji is tapped. We will only log it after getting confirmation from the user through the alert box along with additional details. So, to keep the selected emoji memorized while user fills in other details through the alert box, we will need another state variable.

We will use the useState React Hook to define that variable, and we will define our handleEmojiSelect function to modify those state variables accordingly. Add the following code right above the return statement in the page component:

   const [showInputs, setShowInputs] = useState(false);
    const [emoji, setEmoji] = useState<any>(null);

    const handleEmojiSelect = (selection: EmojiData) => {
        setEmoji(selection);
        setShowInputs(true);
    };

    return (
        <IonPage>

useState needs to be imported from React, so add that to the react import line: import React, { useState } from 'react';. As explained above, the value of isOpen is the showInputs state variable, which is initially set to false, so the alert will not show up on first load.

To contain selected emoji, we create the other state variable aptly named, emoji. Then of course, we define our handleEmojiSelect function to take one parameter — the selected emoji — and then the function stores the emoji in state and toggles the showInputs state to true. At this point, the alert box should show up.

Note that we are defining the type of selection to be EmojiData, but we haven’t imported it yet. Make sure you add that to the import statement from emoji-mart:

import { Picker, EmojiData } from "emoji-mart";

Now let’s take a look at the content of our alert box. We give it a header and a subHeader to show some instruction text for the user. The onDidDismiss component is a function that is called when user closes the alert box. We simply toggle our showInputs state variable back to false in that function.

The most interesting parts of this component are the inputs and buttons props. They allow us to add action buttons and various types of input fields to the alert box. In this case, we are adding two inputs: first is the details text field, and the second is a date picker. Pay attention to the name properties in here — we will need them in a bit.

We have two action buttons to either confirm and save the mood entry or cancel and discard the entry. Naturally, when cancel is clicked, we simply toggle showInputs back to false so that the alert box goes away and then empty out the emoji selection by setting the emoji state variable to null.

The confirm button, however, calls a function with all the input field values encapsulated in the data object, where the object properties match the name we gave to each of the inputs. Following our definitions, we will have data.details and data.date. Within the function, we are using the save action method we defined in our mood store and pass the emoji from state variable and the other parameters from data variable.

However, you will notice that within the Home component, we don’t have access to the moodStore instance, right? Good catch. Remember all the setup we did in our src/App.tsx file with Provider and hydrate and store instances? This is where those come into play.

First, we need to import { inject, observer } from "mobx-react"; at the top of our file and then replace export default Home at the bottom of the file with export default inject("moodStore")(observer(Home));.

This looks confusing, I know, but you can consider it boilerplate code that you will just copy and paste. In plain English, it means: make the Home component an observer of the MobX store, and specifically give it access to a store named moodStore.

MobX knows where to get moodStore from because in our <Provider> component, we passed it a prop named moodStore with the value being an instance of our MoodStore MobX store. With this in place, we now have access to moodStore within our component as a prop.

Since we are using TypeScript, we have to tell the transpiler that the Home component can actually get a prop named moodStore provided to it. We do that by replacing const Home: React.FC = () => { with the following:

import { MoodStore } from "./MoodService";

type HomeProps = {
    moodStore: MoodStore,
};

const Home: React.FC<HomeProps> = ({ moodStore }) => {

That’s it for the Home page. You should now be able to log a mood entry like this:


It’s good and all, but how do we know whether our mood is actually being stored? Well, until we make them appear in our History page, we need to resort to looking under the hood.

Remember we used a library called mobx-persist? That library will take our input — specifically, all the @observable properties (i.e., the list property) of our moodStore — and save it in the browser’s localStorage. The screenshot below should help you figure out how to look into that, and sure enough, you see the mood entry in there:

Showing mood history

You and I, being the smartest programmers of the world, can easily look under the hood of the running machine that is our app and consume raw JSON data as if we were reading a romance novel, but we need to pay more mind to our users, right? So, let’s fill in our History page to show the perviously logged moods, as we’ve planned since the beginning.

Having learned our lesson about TypeScript and React components and the MobX provider, this time, we will start by ensuring that our History page component is all set to receive the moodStore component:

import { inject, observer } from "mobx-react";
import { MoodStore } from "./MoodService";

type HistoryProps = {
    moodStore: MoodStore;
};

const History: React.FC<HistoryProps> = ({ moodStore }) => {
    //... component code
};
export default inject("moodStore")(observer(History));

Now that we have access to our MobX store, we can simply use the computed entries property to access moods. We will use the IonList component to show the moods:

//... other imports

import { Emoji } from "emoji-mart";
import { trash as trashIcon } from "ionicons/icons";

//... previous code

            <IonContent>
                <IonList>
                    {moodStore.entries.map(mood => (
                        <IonItemSliding key={mood.id}>
                            <IonItem>
                                <IonAvatar>
                                    <Emoji emoji={mood.emoji} size={30} />
                                </IonAvatar>
                                <IonLabel>
                                    <h3>{printDate(mood.date)}</h3>
                                    <p>{mood.details || "No Details"}</p>
                                </IonLabel>
                            </IonItem>{" "}
                            <IonItemOptions side="end">
                                <IonItemOption
                                    color="danger"
                                    onClick={() => setRemovingMoodId(mood.id)}
                                >
                                    <IonIcon icon={trashIcon} />
                                </IonItemOption>
                            </IonItemOptions>
                        </IonItemSliding>
                    ))}
                </IonList>
            </IonContent>

Let’s take a closer look at what we are doing here, shall we? Within the IonList component, we are mapping through the entries list and rendering IonItemSliding components for each mood entry. This component takes an IonItem component and an IonItemOptions component as its children.

The IonItem component is what we will see for each mood, and the IonItemOptions component is what we will see when we swipe the items to the left because we set a side="end" prop.

Within the IonItemOptions component, we have one option that displays a button with an icon in it. When the button is tapped, we are executing a setRemovingMoodId function. We will get back to this in a bit.

The IonItem component displays an avatar on the left, which simply shows the emoji and details text, with the date of the mood to the right of the avatar. If there is no details text attached to the entry, we simply show No Details text just so the item doesn’t look empty. To display the date, we are using a helper function, printDate, for two reasons:

  1. Dates are hard! JavaScript has a Date primitive, but when it needs to be saved in a storage that doesn’t support it, it gets converted into a string. In our case, when MobX saves a date in the browser’s localStorage, it converts it into a string. Thus, when we access the date of a mood entry from storage, it’s in string format, but when the user enters it, it’s a Date instance.
  2. Dates are used in various formats across the world, and to show it to users in their own locale, we will use the toLocaleDateString() method built into the date object in JavaScript.

To abstract away this complexity, we define printDate outside of the component, as below:

const printDate = (date: Date | string): string => {
    if (typeof date === "string") {
        date = new Date(date);
    }

    return date.toLocaleDateString();
};

It takes in a date value in either string or Date format. If it’s a string, it creates a Date instance out of it, then it returns the localized version of the date as output of the function.

Another place to define printDate would be within the mood entry instance itself through TypeScript. I will leave that as homework for you, the reader, to figure out.

Removing mood from history

We have already set up a way to remove a mood from history in our MobX store, and we have given each mood entry a swipe to show a remove button. All we need to do is hook those two up with a good UX. I specify good UX because user data is precious, and removing it without confirmation is always a bad UX.

We will try to avoid that. As usual, let’s sprinkle some React state magic with Ionic components’ fairy dust in our code. Add the following state variable at the top of the History component definition and add an IonAlert component within the page:

   const [removingMoodId, setRemovingMoodId] = useState<number>(0);

    return (
        <IonPage>
            <IonAlert
                isOpen={removingMoodId > 0}
                onDidDismiss={() => setRemovingMoodId(0)}
                header={"Remove Mood?"}
                message={`Sure you want to remove mood?`}
                buttons={[
                    {
                        text: "Cancel",
                        role: "cancel",
                        cssClass: "secondary",
                        handler: () => setRemovingMoodId(0)
                    },
                    {
                        text: "Yes, Remove It",
                        handler: () => {
                            moodStore.remove(removingMoodId);
                            setRemovingMoodId(0);
                        }
                    }
                ]}
            />

We are already setting the mood ID to be removed in the removingMoodId state variable when the remove button is tapped. The IonAlert component is only shown when removingMoodId has a number in it and contains some content that asks the user if they are certain that the mood should be removed.

It has two buttons: one to confirm and one to cancel. The cancel simply sets removingMoodId to 0, which hides the alert box. Tapping confirm will trigger two function calls: one that removes the mood entry from the moodStore using our previously created remove action method, and the other resets the removingMoodId state variable to 0 to hide the alert box.

You have worked real hard to get to this point, so give yourself a pat on the back and get yourself ready to see the beauty that we have created:

Epic! Isn’t it?

We have built a well-functioning app that works, but again, let’s think about the users! Apps should be self-explanatory and interactive. We are going to make a few improvements to bring its usability up a notch.

Action feedback

Have you ever tapped a button in an app and wondered what happened because it didn’t do anything, but you have that suspicious feeling that it did and chose not to tell you what? …No? OK, maybe I’m just a bit paranoid, but I like it when apps tell me when it does things — an action feedback, if you will. In our case, let’s add a feedback when the user logs a mood entry.

Let’s get back to the src/pages/Home.tsx file and add the following code right after the Picker component:

  //...previously written code

    const [showMoodLoggedToast, setShowMoodLoggedToast] = useState<boolean>(false);
    const [showInputs, setShowInputs] = useState(false);

    //...previously written code

                <Picker
                    title="Pick your mood"
                    include={["people"]}
                    onSelect={handleEmojiSelect}
                />

                <IonToast
                    duration={2000}
                    isOpen={showMoodLoggedToast}
                    message="Your mood has been logged."
                    onDidDismiss={() => setShowMoodLoggedToast(false)}
                />

    //...previously written code

                        {
                            text: "Ok",
                            handler: data => {
                                moodStore.save(emoji, data.details, data.date);
                                setShowMoodLoggedToast(true);
                            }
                        }

We have added an IonToast component that shows up when the showMoodLoggedToast state variable is true, and then after two seconds (2000ms), it disappears. Then all we have to do is make sure the toast shows up when a new mood is logged by calling setShowMoodLoggedToast(true); after saving a mood entry.

Now, if you add a new mood from the home screen, you will see a small popup at the bottom of the screen that says, “Your mood has been logged.”

Much better, right? See, Ionic has these very handy components right out of the box that just makes implementing a great UX so much easier.

The toast, by default, shows up at the bottom of the screen, but that’s also where our tabs are, and while the toast is on display, we will not be able to navigate between pages anymore. That’s exactly the opposite of good UX, isn’t it?

You can fix that by showing the toast somewhere less invasive, and Ionic makes that really easy. I will leave that for you, the reader, to find out from its documentation.

Another thing I noticed is that the History page looks really empty for first-time users since they won’t have any moods logged just yet. And those are our most valuable users — the ones we want to convert into regular ones. An empty screen won’t cut it for them, would it? Let’s spice it up a bit.

Empty state

I went over to Undraw, which is an absolute gold mine for non-designers like me for finding amazing illustrations for my web and mobile apps. I looked for a fitting illustration that represents both empty state and personal mood at the same time and downloaded the SVG file for it. I put the file in the public/assets/ folder with the name empty-state.svg.

Before we show the image in the History page, let’s add a @computed property in our store to easily determine whether we have an entry in the store. Open the src/pages/MoodService.ts file and add the following code in the MoodService class:

   @computed get hasNoHistory(): boolean {
        return this.list.length < 1;
    };

Then, in the src/pages/History.tsx file, we add a ternary if statement that shows the illustration using the IonImg component if hasNoHistory is true. Otherwise, it shows the list of moods. IonImg will make sure that the image is nicely scaled for the user’s device screen size:

       <IonContent>
                {moodStore.hasNoHistory ? (
                    <IonImg src="/assets/empty-state.svg" />
                ) : (
                    <IonList>
                    //...previously written code
                    </IonList>
                )}

Now go back to the History page in your browser and remove all your mood entries to get a glorious view of the illustration:

That pretty much wraps up our application code. We have a solid feature that works seamlessly and provides a great user experience. However, this is still a web application, for all we know. One of the most appealing features of Ionic is that it lets you create web and mobile application from one codebase. Let’s turn ours into a mobile application.

Capacitor

The Ionic team has been hard at work with their Cordova successor named Capacitor, and I’m really optimistic about its future after seeing their progress so far. I hope you will feel the same way by the end of this post.

We will use Capacitor to turn our web app into Android and iOS applications. Before you read on, please make sure that you have your development environment set up for Android and iOS according to this documentation. Then, from your terminal, within the root of your project, run the following commands:

ionic integrations enable capacitor
npx cap add android
ionic build
npx cap copy
npx cap open android

That’s it. That’s all you need to run the app as a mobile app. Replace android with ios in the above commands to build for iOS. After running the last command in the above list, Ionic will open up Xcode or Android Studio for you with the appropriate app folder.

From there, you can run your app in a simulator or in a connected device, just like you would for any native app. There are plenty of detailed blog posts where you can learn about how to go from here to releasing your app onto the App Store or Google Play, so I won’t get into too much detail about that in this post.

Go live on the web

Being able to create your own app feels magical, but the ultimate gratification comes from being able to show the world what you have built — otherwise, it’s just some code that’s sitting in your hard drive collecting dust.

So let’s show the world what we have built. Ionic, having PWA support out of the box, makes it super easy for our app to behave as an app for our end users once deployed to a publicly accessible server.

We will use Netlify to deploy our app, which is one of my absolute favorite services out there. It allows you to put any client-side application up online with https for free, and even connect with your domain name for free. I can honestly keep on going, but I’d like you to find out. So, if you haven’t already, please go ahead and create an account on Netlify.

Disclaimer: I am in no way affiliated with or paid by Netlify. I am a user of their free tier and I love their tools, which is the only reason I’ve decided to demonstrate how to deploy on its platform. There are many other platforms that provide similar services, and you’re free to use any of those.

Once you have done that, we will take a quick detour and put our code up on GitHub because, firstly, you should always keep your code backed up, and I personally prefer using GitHub for that; and, secondly, because Netlify lets you deploy new updates to your app every time you push new commits to GitHub. We will take advantage of that in a bit.

If you are not familiar with Git/GitHub, there are plenty of resources online that can get you started.

I have created a new repository on GitHub named moodular-ionic, and I’ll add my GitHub repo as a remote to my local Git repo: git remote add origin [email protected]:foysalit/moodular-ionic.git. Now we can synchronize our code with GitHub by running git push -u origin master.

Now let’s get back to Netlify. After logging in, you should see a button on your homepage that says New site from git. Click that or manually go to this URL: https://app.netlify.com/start.


It should take you to a page that looks like the image below:


Click the GitHub button on that page, and authenticate with GitHub to give Netlify access to your GitHub repositories. It should bring up a list containing all your repositories from GitHub.

Select the repository where you have put your moodular app; for me, it’s moodular-ionic. Next, it will ask you to specify which branch you want to use for deploy, what’s your build command, etc. Most of it should be pre-filled for you, but if they aren’t, check out the below image to see my config:


Confirm everything looks good and hit Deploy site. It will then take you to a page that looks like the below image and let you know that Netlify is bringing your code from GitHub and deploying it on their servers. Hang tight…


Once the app is up and running, the yellow text that says Site deploy in progress will go away and a link will appear. Click on that link, et voilà! There’s our beautiful app, loaded on the browser, accessible by anyone.

Notice that the URL is kind of confusing and doesn’t have anything to do with our app. That’s because Netlify autogenerates these links for you. The good thing is that you can change it to your own domain name or, at the very least, you can specify a particular subdomain name under netlify.com. Let’s do that real quick.

Click on the Site settings button, and from the settings page, click on the Change site name button. Within the popup, change the autogenerated name to something you like, something meaningful to our app. Follow the gif below to see how I did it.


Unfortunately, since I have already taken the name moodular, you can’t have it — but I’m quite sure you can come up with a name more creative than that.

Closing remarks

If you are reading this, I assume you have followed through all of the above and are now the proud developer of your own Ionic app. I would encourage you to go ahead and read Ionic’s documentation to find out more about its amazing offerings and take this app further to build up your skills.

Here are a few ideas that can take this app to the next level:

  • Add a more persistent and performant storage layer like SQLite instead of using localStorage
  • Add pagination in the History page so that the page doesn’t have to load every entry on load, which can become a performance issue over time
  • Add a search bar in the History page so that users can find mood entries with fuzzy text/emoji search
  • Add a graphical chart to show map mood history in a more visual way over a period of time that users can define

Angular 9 Tutorial: Learn to Build a CRUD Angular App Quickly

What's new in Bootstrap 5 and when Bootstrap 5 release date?

What’s new in HTML6

How to Build Progressive Web Apps (PWA) using Angular 9

What is new features in Javascript ES2020 ECMAScript 2020

Learning TypeScript with React

Learning TypeScript with React

Learning TypeScript with React

⚡️TL;DR: Understanding what are types, type annotations, why use them and how to use them can help you catch errors during development while also enhancing code quality and readability.

Before diving into it, let me give you a short definition of what is TypeScript (TS) taken from the official TS website.

“TypeScript is a typed superset of JavaScript that compiles to plain JavaScript.”

Now, let me also give you a high level overview of the app’s features and how the React side is structured.

It is a one page app where it has a video player, a list of related talks and a search bar which are all built as separate React components. On page load, it fetches a default list of talks from a JSON file instead of fetching from Youtube to avoid exceeding the daily usage quota of the Youtube API when the page is loaded by many users. The app only communicates with Youtube when searching for talks.

Okay, let's start.

First, the project setup

I generated the project using Create React App with TypeScript

create-react-app my-app-name --typescript

How did I use TypeScript in this app?

Since I’m a TS beginner, I get started by learning the syntax and features of the language first specifically the types, type annotations and interfaces. In this first post, I'm gonna be talking about types and annotations only.

What is a type?

Since TypeScript is a typed language, it means we can specify/annotate the type of the variables, function parameters and object properties.

From my own understanding, a type is a symbol/representation of all the properties and methods that a variable has access to. It means if we added a type of number to a variable, the TS compiler knows that the variable has and can only access all the properties and methods that a number has.

A type is annotated to the variable, parameter or property using this format :type.
For example, let name: string.

There are a lot of available types in TS however, in this app, these are the ones that I've used:

  1. Primitive Types - number, string, boolean
  2. Arrays - by adding [] after the type (e.g. let names: string[])
  3. Video - an interface or a custom type I created to represent a video object's properties

See here for a more comprehensive list of types.

Why do we care about annotating types?

  1. Types are one of the best forms of documentation you can have. This is a very helpful to the next developer who has to read your code which could also be the future you.
  2. It helps the TypeScript compiler help you. If you annotate a variable as a number and a few lines later assigned it a value which is a string, the compiler will give you the following error.
let x: number = 123;
x = '123'; // Error: cannot assign a `string` to a `number`

Even if we remove the :number annotation from variable x, the same error will be given by the compiler because TypeScript is smart*.

*If you declare a variable with a value without specifying the type, TypeScript will figure out the type of the variable based on the initial value assigned to it.

This is called type inference.

"So if type inference is present then why do I have to bother annotating the variables?"

Type inference does not work all the time.

An example scenario is when you delay the initialization of a variable as shown below.

let age;

// a few lines later
age = 12; 
// The compiler will NOT give an error even 
// if we assign a non-numeric value to `age`
age = 'Ana'; 

If you delay the variable initialization without annotating a type to it, the variable will automatically have a type of any associated with it since the TS compiler does not have any idea what kind of properties age will have later on.

Variables with a type of any should be avoided as much as possible. It defeats the purpose of the idea behind the TS type system where we want to catch as many errors as possible during development. Why? Because, the compiler cannot do error checking on types of any.

Remember, use types to help the TS compiler help you.

How is type annotation used in the app?

Aside from using types on the variable declarations, I also used annotations in the function declarations.

A function can be annotated in the following ways:

1. Arrow function

const add = (a:number, b:number):number => {
 return a + b;
}

// or
const add: (a: number, b: number) => number = (a, b) => {
  return a + b;
};

2. Non-arrow function

function add(a:number, b:number):number {
 return a + b;
}

In both of these examples, the compiler will give an error if the function returns a string or any type that is not a number since it is expecting a number.

For fetching the channel's videos, I created a function called fetchChannelVideos which accepts a boolean flag indicating whether to fetch the default videos from the JSON file and a search query string. Both of these are optional parameters (by adding ? after the property name) which are represented as an interface. I will explain later what an interface is but for now let’s take a closer look on how the function is annotated.

interface FetchVideosArguments {
 shouldUseDefaultVideos?: boolean;
 searchQuery?: string;
}

export const fetchChannelVideos: (
 args: FetchVideosArguments
) => Promise < Video[] > = async ({
 shouldUseDefaultVideos = false,
 searchQuery
}) => {};

On the left side of the assignment operator (=),

const fetchChannelVideos: (args: FetchVideosArguments) => Promise <Video[]>

we are annotating the variable fetchChannelVideos that was declared not the function assigned. We are telling the TS compiler that this variable will have a function assigned to it with these types of arguments and return value.

While the right part of the = is the function assigned.

async ({
 shouldUseDefaultVideos = false,
 searchQuery
}) => {};

To annotate the function itself, we have to specify its arguments and their types and the return value as well.

So why didn't I annotate the function to assigned to fetchChannelVideos? Because again, TypeScript is smart.

Seeing that I assigned the function to a variable that was annotated, it is able to infer that the function will have the same argument names and types and the return value as well, otherwise it will give an error if I add or specify different argument names or return a different value.

*The function arguments and return value is inferred

However, if I’m exporting the function directly without assigning it to a variable then I have to annotate it like below.

export async function fetchChannelVideos({
 shouldUseDefaultVideos = false,
 searchQuery
}: FetchVideosArguments): Promise<Video[]> {
 // function body
}

Okay, now that we have an idea about the what, why and how of type annotations, there's one last question.

Where do we use types?

The answer is: everywhere. By using TypeScript, every data in your application will have a type associated with it whether you like it or not.

Closing Thoughts

I have only touched the basics so far but I’ve already seen how helpful it is, seeing the errors the compiler gives while writing the code instead of discovering them later when running the app saved me a lot of time.

What is an interface?

Interfaces are the core way in TypeScript to compose multiple type annotations into a single named annotation.

It’s a way of creating a new type that describes an object’s property names and their types. Remember the types that we covered before (number, boolean, string, etc.)? An interface is the same as those types.

Why should you care about interfaces?

For me, interfaces are one of the best features of the TypeScript language. Let me show you just how awesome it is.

Let's start with the following example.

You have a function called renderVideo which accepts video as a parameter in which you annotated with all the properties that should be present in a video object.

const video = {
  videoId: '',
  title: '',
  description: '',
  thumbnailURL: ''
};

const renderVideo = (video: { videoId: string; title: string; description: string; thumbnailURL: string }): void => {

}

Even though the parameter annotation is quite long, the code above is okay, the function parameter and return value was annotated correctly.

However, imagine if you added more functions which accepts a video.

const video = {
  videoId: '',
  title: '',
  description: '',
  thumbnailURL: ''
};

const renderVideo = (video: { videoId: string; title: string; description: string; thumbnailURL: string }): void => {

}

const getVideoSummary = (video: { videoId: string; title: string; description: string; thumbnailURL: string }): void => {

}

// another function has a `video` parameter
// another one
// and another one
// *shouts* "DJ Khaled"

Aaahhhh. I don't know about you but to me, the code above doesn't look good.

There are a lot of long annotations which are duplicated several times. The code is harder to read as well.

Now imagine again (we're imagining a lot here) that you need to do either one or more of the following changes to a video:

  • add the date it was published as a new property
  • rename thumbnailURL to imageURL
  • change the type of videoId from string to a number

Applying these new changes to the code above means you need to change all the video annotations in a lot of places.

That's a lot of work and we don't want that so how can we solve this?

Simple, by using an interface.

How to create an interface?

We can convert this long video annotation into an interface called Video.

To create an interface, use the interface keyword followed by a name that represents the object, in this case Video.

The name should start with a capital letter just like how we create React components and a set of curly braces where you declare all of the object's properties and methods and its types.

This is how you create the Video interface.

interface Video {
  videoId: string;
  title: string;
  description: string;
  thumbnailURL: string;
}

How do you use an interface?

Since an interface is a custom type, we can annotate it to a variable just like a normal type.

let item: Video;

By using the Video interface, we can rewrite the previous example to this:

interface Video {
  videoId: string;
  title: string;
  description: string;
  thumbnailURL: string;
}

const renderVideo = (video: Video): void => {};
const getVideoSummary = (video: Video): void => {};

// another function that uses a video
// another one
// and another one

const video = {
  videoId: "",
  title: "",
  description: "",
  thumbnailURL: ""
};

renderVideo(video);

This code looks wayyyyy better than the previous one right?

It looks cleaner and in one glance, we can see right away that the parameter is a type of Video.

Most of all, we can add, remove or rename a property or change a property type only in one place, the interface declaration. Awesome!

💡 If you use VS code, which I highly recommend you should, you can see all the property names and types of an interface by holding the ctrl/cmd key and hovering over the interface name.

Pretty cool right?

How are interfaces used in the app?

Aside from using the Video interface, I created another interface to represent how the Youtube API response is structured.

interface YoutubeVideo {
  id: { videoId: string };
  snippet: {
    publishedAt: string;
    title: string;
    description: string;
    thumbnails: { medium: { url: string }; default: { url: string } };
    channelTitle: string;
    channelId: string;
  };
}

interface YoutubeAPIResponse {
  data: { items: YoutubeVideo[] };
}

This interface is used in the fetchChannelVideos function.

export const fetchChannelVideos: (
  args: FetchVideosArguments
) => Promise<Video[]> = async ({
  shouldUseDefaultVideos = false,
  searchQuery
}) => {
 const requestURL = '';
 const response:YoutubeAPIResponse = await axios.get(requestURL);
};

Since an interface is a type, it means the TS compiler also does error-checking on the annotated data.

By creating an interface for the API response structure and annotating it to the variable, we made sure that we don't accidentally access a property that does not exist.

So if we try to access comments, which is not included in the API response, the TypeScript compiler will give us an error.

Writing Reusable Code with Interfaces

Now that we covered the what, why and how of interfaces, let's talk about how we can use it to write more reusable code.

Let's say you have a component that renders comments from a Youtube user and the channel.

First we write it like this:

interface User { name: string; avatar: string; }
interface Channel { name: string; avatar: string; }

const renderUserComment: (user: User, comment: string) => React.ReactNode = (
  user,
  comment
) => { /* return user comment */ };

const user = { name: "Ana", avatar: "ana.png" };
const channel = { name: "Tedx Talks", avatar: "tedxtalks.png" };

const renderChannelComment: (
  channel: Channel,
  comment: string
) => React.ReactNode = (channel, comment) => {/* return channel comment */};

renderUserComment(user, 'hello there');
renderChannelComment(channel, 'hi, thanks for watching');

There are two separate interfaces to represent a user and a channel as well as separate functions to render these comments even though they do the same thing, rendering a comment with the author.

How can we rewrite this to remove the duplicated code?

Although we can use a union type to indicate that the comment author can be a User or a Channel,

const renderComment: (author: User|Channel, comment: string) => React.ReactNode = (
  author,
  comment
) => { /* return comment */ };

In this scenario, it's better to create one interface to represent the two types of users.

Let's rewrite it using a more generic interface called Author.

interface Author { name: string; avatar: string; }

const renderComment: (author: Author, comment: string) => React.ReactNode = (
  author,
  comment
) => { /* return comment */};

const user = { name: "Ana", avatar: "ana.png", github: "analizapandac" };
const channel = { name: "Tedx Talks", avatar: "tedxtalks.png", channelId: "UCsT0YIqwnpJCM-mx7-gSA4Q" };

renderComment(user, 'hello there');
renderComment(channel, 'hi, thanks for watching');

Even though user and channel represents two different types of data, they satisfy the requirements to be considered an author which is a name and avatar so the renderComment function works for both objects.

The examples above are very basic but the point here is that we can use a generic interface to represent different objects which can work with different functions. This is one way to use interfaces to write more reusable code.

Closing Thoughts

Aside from learning about the what, why and how of interfaces, I hope that I have also shown you a glimpse of the power of interfaces and how you can use it to create amazing applications.

There's a lot more to interfaces especially on how to use it together with the other TypeScript features such as classes and write even better code.

I'll continue learning the more advanced TS concepts, creating new exciting projects using them and sharing them with you.

I hope you'll do the same 😊

Project Github Repo

Here's the source code of the app: https://github.com/analizapandac/tedflix

How to Share Code Between React and React Native

How to Share Code Between React and React Native

Learn how to share code between React and React Native to avoid duplicating logic. Sharing Code Between React and React-Native: What Not to Share. This question of sharing code between React and React Native, in fact, one of the hot topics among React and React native developers all over the world. React and React-Native allow a learn once write anywhere paradigm. This is great, because one tech team can build both your web app and native mobile experience. The problem is developers hate writing things twice. There have been a couple of efforts to build a unifying technology to write an application once and have it work on both web and native.

How to Share Code Between React and React Native

Sharing Code Between React and React-Native: What Not to Share - Ben Ellerby

React and React-Native allow a learn once write anywhere paradigm. This is great, because one tech team can build both your web app and native mobile experience. The problem is developers hate writing things twice. There have been a couple of efforts to build a unifying technology to write an application once and have it work on both web and native. Yet this is not always the best approach. There is value in only sharing your business and state logic; keeping your render code separate.

In this talk I will give real examples from my work with MADE.COM, migrating their web and mobile application to React and React-Native with code sharing as a primary objective.

How to Share Code Between React and React Native

Learn how to share code between React and React Native to avoid duplicating logic

Sharing Code Between React and React Native

React and React-Native allow a learn once write anywhere paradigm. This is great, because one tech team can build both your web app and native mobile experience. The problem is developers hate writing things twice. There have been a couple of efforts to build a unifying technology to write an application once and have it work on both web and native. Yet this is not always the best approach. There is value in only sharing your business and state logic; keeping your render code separate.

Building Mobile Apps With Ionic And React for Beginners

Building Mobile Apps With Ionic And React for Beginners

React developers can get the advantages of Ionic to build hybrid mobile apps and progressive web apps. In this tutorial, we’ll be using Ionic and React to build a mobile app from scratch.

React developers can get the advantages of Ionic to build hybrid mobile apps and progressive web apps. In this tutorial, we’ll be using Ionic and React to build a mobile app from scratch.

Ionic has recently added support for React; so now

React developers can get the advantages of Ionic to build hybrid mobile apps and progressive web apps (PWAs). In this post, we’ll show you how to get started using Ionic with React by building a simple demo app from scratch.

Prerequisites

In order to properly follow this tutorial, you’ll need the following prerequisites:

  • recent versions of Node.js and npm installed on your system,

  • working knowledge of TypeScript and React.

You can check that you have the latest Node.js version (v10) installed by running the following command:

$ node -v
Introducing React And Ionic

Let’s start with brief introductions to both React and Ionic.

According to the official website:

“React is a declarative, efficient, and flexible JavaScript library for building user interfaces. It lets you compose complex UIs from small and isolated pieces of code called ‘components’.”

React focuses on building UIs and doesn’t provide any built-in tools for the common tasks required in web development, such as fetching remote data and routing, so you’ll need to use some existing third-party libraries for these tasks.

According to the Ionic website:

“Ionic Framework is the free, open-source mobile UI toolkit for developing high-quality cross-platform apps for native iOS, Android, and the web — all from a single codebase.”

Basically, it’s a set of UI components that you can use with plain JavaScript or any popular front-end framework or library, such as Angular, React or Vue, to build a hybrid mobile app and PWA.

In this tutorial, we’ll see and use some Ionic UI components such as the following:

  • IonMenu: With this, a navigation drawer will slide in from the side of the current view.

  • IonToolbar: These top bars are positioned above or below the content.

  • IonHeader: This parent component holds the toolbar component.

  • IonContent: This component provides a content area, with methods to control the scrollable area and other things. You need only one content component inside a single view.

  • IonList: This component contains items with similar data content, such as images and text. It’s made up of IonItem objects.

  • IonItem: This component may contain text, icons, avatars, images, inputs and any other native or custom element.

  • IonButton: This component provides a clickable element, which can be used in a form or anywhere that needs simple, standard button functionality.

Installing Ionic CLI V5

Ionic’s command line interface (CLI), version 5, has support for creating Ionic projects based on React. So, let’s get started by installing the tool from npm.

Open a CLI, and run the following command:

$ npm install -g ionic

At the time of writing, Ionic’s CLI v5.2.3 is the latest.

Note: According to how you installed Node.js in your system, you may need to add sudo before your command in macOS or Linux or run the command prompt as administrator in Windows if you get any permission errors. You can also simply fix your npm permissions or use a tool such as nvm.

Next, install Cordova Resources (which is used to generate Cordova resources locally) and Native Run (used to deploy app binaries to devices):

$ npm install -g cordova-res native-run

These are required only if you want to test your application on a real mobile device or emulator.

Creating An Ionic And React Project

Now, let’s create a project based on React. Go back to your terminal, make sure you are in your working directory, and run the following command:

$ ionic start myApp --type=react

We use --type=react to generate a project based on React. You’ll next need to choose a starter template from the available ones. Let’s pick sidemenu for a starter template with a side menu and navigation.

After generating the project and installing the dependencies, you can serve your app locally using the following commands:

$ cd ./myApp
$ ionic serve

Your app will be available from the http://localhost:8100 address, and you can actually use your web browser to start playing with it.

Ionic is called a hybrid mobile framework because it makes use of web technologies that were originally designed to create web apps, along with a native container (Cordova or Capacitor), to build mobile apps without using native technologies for the target platforms, such as Java or Kotlin for Android or Swift for iOS.

Because your mobile app is actually a web application, you can do most development by testing in a web browser without using an emulator or a real mobile device, except for testing native device features such as the camera or the SQLite storage, in case you’ve used them in your app. In fact, it’s even possible to use certain techniques to mimic the plugins that provide the native features and do the entire testing during development in your web browser.

Cleaning Our Project

We have the app’s basic structure, with two pages (home and list) and a menu. Let’s remove the list page since it’s just boilerplate code.

First, remove the src/pages/List.tsx file, then open the src/App.tsx file, and remove the entry for the list page from the appPages array:

const appPages: AppPage[] = [
  {
    title: 'Home',
    url: '/home',
    icon: home
  }
];

Also, remove the import of the list page from the file:

import  List  from  './pages/List';

Next, remove

<Route path="/:tab(home)/list" component={List} exact={true} />

from the App component:

const App: React.FunctionComponent = () => (
  <IonApp>
    <IonReactRouter>
      <IonSplitPane contentId="main">
        <Menu appPages={appPages} />
        <IonPage id="main">
          <IonRouterOutlet>
            <Route path="/:tab(home)" component={Home} exact={true} />
            <Route exact path="/" render={() => <Redirect to="/home" />} />
          </IonRouterOutlet>
        </IonPage>
      </IonSplitPane>
    </IonReactRouter>
  </IonApp>
);

export default App;

The App component is the root component that gets rendered by our application. If you open the src/index.tsx file, you’ll find the following code:

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

ReactDOM.render(<App />, document.getElementById('root'));

The render() method is used to render a React element into the DOM in the supplied root element.

Theming The App

Having created, served and cleaned our Ionic project, let’s now see how we can change the colors of the UI so that it looks more professional.

Let’s get started with the side menu. Open the src/components/Menu.tsx file, and add a color attribute with a primary value to the IonToolbar, IonContent, IonList and IonItem UI components:

const Menu: React.FunctionComponent = ({ appPages }) => (
  <IonMenu contentId="main">
    <IonHeader>
      <IonToolbar color="primary">
        <IonTitle>Menu</IonTitle>
      </IonToolbar>
    </IonHeader>
    <IonContent color="primary">
      <IonList style= {{ background : '#3880ff'}} color="primary">
        {appPages.map((appPage, index) => {
          return (
            <IonMenuToggle key={index} auto-hide="false">
              <IonItem color="primary" href={appPage.url}>
                <IonIcon slot="start" icon={appPage.icon} />
                <IonLabel>{appPage.title}</IonLabel>
              </IonItem>
            </IonMenuToggle>
          );
        })}
      </IonList>
    </IonContent>
  </IonMenu>
);

Ionic provides some default colors (primary, secondary, tertiary, success, warning, danger, light, medium and dark) that can be used to change the color of UI components. A color can be applied to an Ionic component in order to change the default colors using the color attribute. See “Colors” for more information.

These colors have default values, but you can customize them via some predefined CSS variables. See “Modifying Colors“.

This is a screenshot of our menu:

Next, let’s change the color of the Home page. Open the src/pages/Home.tsx file, and set the color attribute of the IonToolbar and IonContent components to primary:

const HomePage: React.FunctionComponent = () => {
  return (
    <>
      <IonHeader>
        <IonToolbar color="primary">
          <IonButtons slot="start">
            <IonMenuButton />
          </IonButtons>
          <IonTitle>Home</IonTitle>
        </IonToolbar>
      </IonHeader>
      <IonContent color="primary" >

      </IonContent>
    </>
  );
};

This is a screenshot of the page:

Installing Axios And Consuming A REST API

We’ll see how to install Axios and consume a third-party RESTful API in our application, and we’ll also see how to display the fetched data using Ionic card and list components.

Having themed our application, let’s now see how to fetch data using Axios. We’ll use the third-party API available from NewsAPI.org.

First, we need to grab an API key, so that we can communicate with the API. Go to the registration page, enter your information, and register an account. You’ll be given an API key; note it, and let’s continue.

Head back to your terminal, and run the following command to install Axios:

$ npm install axios --save

Next, open the src/pages/Home.tsx file, and start by importing Axios and IonButton:

import {
  IonButton
} from '@ionic/react';

import axios from 'axios';

Next, define the API_KEY and URL constant variables:

const  API_KEY  =  "<YOUR_API_KEY_HERE>";
const  URL  =  `https://newsapi.org/v2/top-headlines?sources=techcrunch&apiKey=${API_KEY}`;

In the URL variable, we’ll add an endpoint to get the top headlines from our source, TechCrunch. You can use any source you want from the available sources.

Note: Make sure to put your own API key in the API_KEY variable.

Next, define the fetchArticles() method as follows:

const fetchArticles = () => {

  return axios({
    url: URL,
    method: 'get'
  }).then(response => {

    console.log(response);
    return response.data;
  })
};

We simply call the axios() method to send a GET request to the news endpoint, and the result from the method is a promise that needs to be resolved in order to get the news data.

Next, update the HomePage component as follows to call the fetchArticles() method and resolve the returned promise:

const HomePage: React.FunctionComponent = () => {

  const [articles, setArticles] = React.useState([]);
  const items: any[] = [];

  React.useEffect(() => {

    fetchArticles().then(data => setArticles(data.articles));

  }, []);

  return (
    <>
      <IonHeader>
        <IonToolbar color="primary">
          <IonButtons slot="start">
            <IonMenuButton />
          </IonButtons>
          <IonTitle>Home</IonTitle>
        </IonToolbar>
      </IonHeader>
      <IonContent color="primary" >
        <IonList color="primary">

          {
            articles.map(a => {

              return (
                <IonItem>
                  {a['title']}
                  <IonButton href={a['url']} color="primary" slot="end">Read</IonButton>
                </IonItem>
              );
            })
          }

        </IonList>
      </IonContent>
    </>
  );
};

In our function component, we first call the useState() hook of React to create the articles state variable, which will hold the news articles after we fetch them from the API.

useState() returns the state variable, which has the empty array as its initial value and a function that we can use to change the value of the variable. We use destructuring assignment to unpack the values from the returned pair into distinct variables (i.e. articles and setArticles()).

Next, we call the useEffect() hook to perform a side effect in our component. In our case, the side effect is to fetch data from the news API using thefetchArticles()method, which returns a promise. Once the promise is resolved, we call the setArticles() method to assign the news data to the articles variable.

  • Both useState() and useEffect() are built-in React hooks that were introduced in React 16.8; they simply let you use state and other React features without having to write a class. The useEffect() hook is equivalent to calling the componentDidMount, componentDidUpdate and componentWillUnmount lifecycle methods combined in class-based components.

Finally, in the view template, we iterate over the articles array using the map() method, and we display the title of each news article inside an IonItem element of the IonList component, and also a button that takes us to the page of the full article.

This is a screenshot of the page:

You can find the source code in this GitHub repository.

Conclusion

In this tutorial, we have started using both Ionic and React and used them to build a simple mobile application that fetches and displays news data from a third-party API using the Axios client. We have also seen how to use hooks in React — namely, the useState() anduseEffect()hooks — to create state and perform side effects inside React function components. With Ionic, we’ve seen how easy it is to generate a project based on React and how we can theme the application using the color attributes of components.