Building a full mobile app with TypeScript, Ionic, React, and MobX
Tags: ionic, react, typescript, mobx
Published: 2019-12-28 23:09:59

---
title: Building a full mobile app with TypeScript, Ionic, React, and MobX
published: true
date: 2019-12-18 17:00:39 UTC
tags: ionic,react,typescript,mobx
canonical_url: https://blog.logrocket.com/full-mobile-app-typescript-ionic-react-mobx/
cover_image: https://thepracticaldev.s3.amazonaws.com/i/0kab6yj38q084jjelq6b.jpg
---
**Written by [Foysal Ahamed](https://blog.logrocket.com/author/foysalahamed/)**✏️

## 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.

If you’re familiar with my blogging style, you know that I am a firm believer in the “learn by building” approach. I like to get my hands dirty, make mistakes, and figure things out on my own instead of memorizing the documentation before using a new tool. If that sounds like a good approach to you, then please, do read on.

[![LogRocket Free Trial Banner](https://i0.wp.com/blog.logrocket.com/wp-content/uploads/2017/03/f760c-1gpjapknnuyhu8esa3z0jga.png?resize=1200%2C280&ssl=1)](https://logrocket.com/signup/)

## 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](https://github.com/foysalit/moodular-ionic) that contains the entire app
- [Here’s the app itself](https://moodular.netlify.com) 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:

![Completed Moodular App Preview](https://i1.wp.com/www2.logrocket.com/rs/740-LKM-263/images/modular-app-preview-3.gif?resize=730%2C662&ssl=1)

## 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 ionic@latest` 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.

![Init Output](https://i1.wp.com/blog.logrocket.com/wp-content/uploads/2019/12/init-output.png?resize=730%2C581&ssl=1)

Let’s do what Ionic told us to do:

```jsx
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:

```jsx
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:

```jsx
   <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:

```jsx
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

```jsx
<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:

```jsx
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](https://ionicframework.com/docs/api/router).

> **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

```jsx
<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:

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

> Read more about [Ionicons](https://ionicons.com/) and [ion-tab-bar](https://ionicframework.com/docs/api/tab-bar).

### `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:

![App Navigation Preview](https://i0.wp.com/blog.logrocket.com/wp-content/uploads/2019/12/navigation-preview.gif?resize=730%2C663&ssl=1)

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](https://github.com/missive/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:

```jsx
/* 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:

```jsx
// ... 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:

![Emoji Picker Preview](https://i1.wp.com/blog.logrocket.com/wp-content/uploads/2019/12/picker-preview.png?resize=411%2C730&ssl=1)

## 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](https://mobx.js.org/). 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:

```jsx
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](https://mobx.js.org/) 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

```jsx
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

```jsx
   @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

```jsx
    @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:

```jsx
//... 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](https://ionicframework.com/docs/api/alert) for this:

```jsx
    <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:

```jsx
    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`:

```jsx
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:

```jsx
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:

![Saving Mood Entry](https://i1.wp.com/blog.logrocket.com/wp-content/uploads/2019/12/saving-mood-entry.gif?resize=730%2C663&ssl=1)

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:

![Inspecting The Mood Entry](https://i0.wp.com/blog.logrocket.com/wp-content/uploads/2019/12/inspect-mood-entry.png?resize=730%2C665&ssl=1)

## 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:

```jsx
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](https://ionicframework.com/docs/api/list) to show the moods:

```jsx
//... 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:

```jsx
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:

```jsx
    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:

![Remove Mood Action](https://i1.wp.com/blog.logrocket.com/wp-content/uploads/2019/12/remove-mood-action.gif?resize=730%2C663&ssl=1)

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:

```jsx
    //...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.”

![Mood Save Toast](https://i0.wp.com/blog.logrocket.com/wp-content/uploads/2019/12/mood-save-toast.gif?resize=730%2C663&ssl=1)

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](https://ionicframework.com/docs/api/toast).

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](https://dev.to/scottw/-undraw-10ge-temp-slug-8229813), 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:

```jsx
    @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:

```jsx
            <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:

![Empty Mood History Preview](https://i0.wp.com/blog.logrocket.com/wp-content/uploads/2019/12/empty-state-preview.png?resize=386%2C687&ssl=1)

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](https://capacitor.ionicframework.com/), 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](https://capacitor.ionicframework.com/docs/getting-started/dependencies). Then, from your terminal, within the root of your project, run the following commands:

```jsx
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](https://netlify.com) 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](https://app.netlify.com/signup).

> **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 git@github.com: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](https://app.netlify.com/start).

![Netlify Home](https://i0.wp.com/blog.logrocket.com/wp-content/uploads/2019/12/netlify-home.png?resize=730%2C135&ssl=1)

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

![Connecting Netlify To GitHub](https://i2.wp.com/blog.logrocket.com/wp-content/uploads/2019/12/netlify-connect-github.png?resize=730%2C403&ssl=1)

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:

![Netlify Deploy Config](https://i0.wp.com/blog.logrocket.com/wp-content/uploads/2019/12/netlify-deploy-config.png?resize=730%2C695&ssl=1)

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…

![Netlify Deploying State](https://i0.wp.com/blog.logrocket.com/wp-content/uploads/2019/12/netlify-deploying-state.png?resize=730%2C422&ssl=1)

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.

![Changing Site Name On Netlify](https://i0.wp.com/blog.logrocket.com/wp-content/uploads/2019/12/netlify-site-rename.gif?resize=730%2C582&ssl=1)

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](https://ionicframework.com/docs) 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

If you complete any or all of the above tasks, [give me a shout out on Twitter](https://twitter.com/foysalit) and I would be more than delighted to do a quick review of your code.

* * * 
## Full visibility into production React apps 

Debugging React applications can be difficult, especially when users experience issues that are difficult to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, tracking slow network requests and component load time, [try LogRocket.](https://www2.logrocket.com/react-frontend-monitoring)

![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/won9y5igpth32mfqsvyk.png)

[LogRocket](https://www2.logrocket.com/react-frontend-monitoring) is like a DVR for web apps, recording literally everything that happens on your React app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting metrics like client CPU load, client memory usage, and more.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your React apps — [start monitoring for free.](https://www2.logrocket.com/react-frontend-monitoring)
* * *
The post [Building a full mobile app with TypeScript, Ionic, React, and MobX](https://blog.logrocket.com/full-mobile-app-typescript-ionic-react-mobx/) appeared first on [LogRocket Blog](https://blog.logrocket.com).