Building a CRUD Mobile App with Ionic Framework and React
Tags: ionic, typescript, react
Published: 2023-06-20 17:51:49

---
title: Building a CRUD Mobile App with Ionic Framework and React
published: true
date: 2023-05-23 18:54:52 UTC
tags: #ionic #typescript #react
canonical_url: https://carlosmv.hashnode.dev/building-a-crud-mobile-app-with-ionic-framework-and-react
---

Ionic and React are two popular frameworks for building mobile apps. Ionic is a UI framework that provides a consistent look and feels across different platforms, while React is a JavaScript framework that makes it easy to build complex user interfaces.

In this article, we will show you how to build a mobile app for Android using Ionic and React. We will start by creating a new project and then we will add some basic components to the app. And finally using a emulator to simulate how the app will work on a Android device.

### Ionic

Ionic is an open-source UI toolkit for building mobile apps using web technologies: HTML, CSS, and Javascript. It offers integration for React, Vue and Angular.

### Requirements

- NodeJS installed.

- React knowledge.

- Android Studio, download it [here](https://developer.android.com/studio).

- Android SDK installed (Instructions [here](https://developer.android.com/about/versions/14/setup-sdk))

### Installation

We need to install the Ionic CLI tool. Ionic apps are created and developed primarily through the Ionic command-line utility. The Ionic CLI is the preferred method of installation, as it offers a wide range of dev tools and help options along the way.

Install the Ionic CLI with npm:

```
 npm install -g @ionic/cli

```

Let's set up our project using the ionic start command:

```
ionic start my-crud-app blank --type=react --capacitor

```

After the CLI installed all the dependencies, we run `ionic serve` to start the app on our browser.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1679075089376/d08a3fad-aaa1-4e69-a326-c1be0ebea4d4.png?auto=compress,format&format=webp)

We go to [`localhost:8100`](http://localhost:8100), we should be able to see this page:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1679075249270/8c46fb47-c051-4f5b-8178-a9b5e4b16081.png?auto=compress,format&format=webp)

### Adding a Task

We create a new folder, `src/components`. And a new file `src/components/Task.tsx` .

In this file, we are going to write all the code to perform CRUD operations.

```
import React, { useState, useEffect } from "react";
import {
  IonApp,
  IonHeader,
  IonToolbar,
  IonTitle,
  IonContent,
  IonList,
  IonItem,
  IonLabel,
  IonInput,
  IonCheckbox,
  IonButton,
  IonIcon,
  InputChangeEventDetail
} from "@ionic/react";
import { add, trash, create, checkmark } from "ionicons/icons";

type Task = {
  id: number;
  task: string;
  completed: boolean;
};

const Tasks: React.FC = () => {
  const [tasks, setTasks] = useState<Task[]>([]);
  const [newTask, setNewTask] = useState<string>("");

  useEffect(() => {
    const savedTasks = localStorage.getItem("tasks");
    if (savedTasks) {
      setTasks(JSON.parse(savedTasks));
    }
  }, []);

  useEffect(() => {
    localStorage.setItem("tasks", JSON.stringify(tasks));
  }, [tasks]);

  const handleTaskInput = (event: CustomEvent<InputChangeEventDetail>) => {
    setNewTask((event.target as HTMLInputElement).value);
  };

  const handleNewTask = () => {
    const task: Task = {
      id: Date.now(),
      task: newTask,
      completed: false
    };
    setTasks([...tasks, task]);
    setNewTask("");
  };

  return (
    <IonApp>
      <IonHeader>
        <IonToolbar>
          <IonTitle>Ionic React CRUD</IonTitle>
        </IonToolbar>
      </IonHeader>
      <IonContent>
        <IonList>
          {tasks.map((task) => (
            <IonItem key={task.id}>
              <IonLabel>
                <h2>{task.task}</h2>
              </IonLabel>
              <div slot="end">
              </div>
            </IonItem>
          ))}
        </IonList>
        <IonItem>
          <IonInput
            placeholder="Add Task"
            value={newTask}
            onIonChange={handleTaskInput}
          />
          <IonButton color="success" onClick={handleNewTask}>
            <IonIcon icon={add} />
            <IonLabel>Add</IonLabel>
          </IonButton>
        </IonItem>
      </IonContent>
    </IonApp>
  );
};

export default Tasks;

```

Here we define a component called "Tasks" which lists tasks and allows the user to add new ones.

Upon import, the code first sets up local state variables for tasks and newTask using the `useState` hook. It then sets up two `useEffect` hooks. The first `useEffect` hook retrieves the tasks from local storage upon the component mount, if present. The second `useEffect` hook saves tasks to local storage upon changes to the tasks state.

The `handleTaskInput` function sets the new task value as the user types into the input field, and the `handleNewTask` function creates a new task object, adds it to the tasks array, and resets the input field. The return statement includes the Ionic components which display the task list and input form.

**App.tsx**

Now we go to `src/App.tsx` to create a route to the component we just created.

```
import { Redirect, Route } from 'react-router-dom';
import { IonApp, IonRouterOutlet, setupIonicReact } from '@ionic/react';
import { IonReactRouter } from '@ionic/react-router';
import Tasks from './components/Tasks';

/* Core CSS required for Ionic components to work properly */
import '@ionic/react/css/core.css';

/* Basic CSS for apps built with Ionic */
import '@ionic/react/css/normalize.css';
import '@ionic/react/css/structure.css';
import '@ionic/react/css/typography.css';

/* Optional CSS utils that can be commented out */
import '@ionic/react/css/padding.css';
import '@ionic/react/css/float-elements.css';
import '@ionic/react/css/text-alignment.css';
import '@ionic/react/css/text-transformation.css';
import '@ionic/react/css/flex-utils.css';
import '@ionic/react/css/display.css';

/* Theme variables */
import './theme/variables.css';

setupIonicReact();

const App: React.FC = () => (
  <IonApp>
    <IonReactRouter>
      <IonRouterOutlet>
        <Route path="/" component={Tasks} exact={true} />

      </IonRouterOutlet>
    </IonReactRouter>
  </IonApp>
);

export default App;

```

The main component is defined as `App` and uses a functional component with no props (`React.FC = () =>`). Inside the component, there's a `<IonReactRouter>` component that enables React Router for the app, and inside it there is an `<IonRouterOutlet>` component, which serves as the container for rendering child components based on the current route.

The `<Route>` component is used to define each route and its corresponding component. Here, the "/" route is defined with the `Tasks` component as the component to render when the route matches. "exact" attribute is set to true to only match the "/" route exactly instead of matching any route that starts with "/" (such as "/about" or "/contact").

If we go to our browser an visit `localhost:8100`, we will have this view:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1684787433807/9e4881a9-0181-4064-a4d3-9a6f85ff6352.png)

### Update a Task

```
const Tasks: React.FC = () => {
  ...
  const handleTaskEdit = (id: number, taskText: string) => {
    const updatedTasks = tasks.map((task) => {
      if (task.id === id) {
        return { ...task, task: taskText };
      }
      return task;
    });
    setTasks(updatedTasks);
  };

  const handleTaskToggle = (id: number) => {
    const updatedTasks = tasks.map((task) => {
      if (task.id === id) {
        return { ...task, completed: !task.completed };
      }
      return task;
    });
    setTasks(updatedTasks);
  };

  return (
   ...
  );
};

export default Tasks;

```

This code defines two event handler functions `handleTaskEdit` and `handleTaskToggle`, which update the state of tasks in a react component.

`handleTaskEdit` takes two parameters: `id` of type `number` and `taskText` of type `string`. It uses the `map` method on the `tasks` array to find the task with the matching id and return a new object that combines the existing task object with the updated `taskText`. The new array of tasks is then set as the state of the component using the `setTasks` function from the React state hook.

`handleTaskToggle` takes a single parameter `id` of type `number`. It also uses the `map` method on the `tasks` array to find the task object with the matching `id` and return a new object that combines the existing task object with the `completed` property toggled to the opposite boolean value. The new array of tasks is then set as the state of the component using the `setTasks` function.

```
 return (
    <IonApp>
      ...
      <IonContent>
        <IonList>
          ...
                <IonCheckbox
                  checked={task.completed}
                  onIonChange={() => handleTaskToggle(task.id)}
                />
              </IonLabel>
              <div slot="end">
                <IonButton
                  color="primary"
                  onClick={() =>
                    handleTaskEdit(
                      task.id,
                      window.prompt("Update Task", task.task) || ""
                    )
                  }
                >
                  <IonIcon icon={create} />
                </IonButton>
              </div>
            </IonItem>
          ))}
        </IonList>
        ...
      </IonContent>
    </IonApp>
  );

```

The `checked` prop of the `IonCheckbox` is set to the `completed` property of each task object, and the `onIonChange` prop calls the `handleTaskToggle` function with the task's `id` when the checkbox state changes.

The `IonButton` component is used to set up an edit button. When the button is clicked, the `handleTaskEdit` function is called with the task's `id` and a prompt to update the task text.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1684787549727/c3e143b7-3d19-4afd-afe9-2f815337e657.png)

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1684787586242/dcb8ba33-57ef-4240-b933-fecfd3bdf2ba.png)

### Delete a Task

```
const handleTaskDelete = (id: number) => {
    const updatedTasks = tasks.filter((task) => task.id !== id);
    setTasks(updatedTasks);
  };

```

Here we define a function called `handleTaskDelete` which takes a single argument `id` of type `number`.

Inside the function, it uses the `filter()` method to create a new array called `updatedTasks` that contains all `tasks` except for the one with the `id` passed to the function.

Finally, it calls the `setTasks` function with `updatedTasks` as the argument. This function is likely part of a React component, and calling it will trigger a re-render of the component with the updated task list. The end result is that this function removes a task from the `tasks` list and triggers a re-render to reflect the updated list in the UI.

```
 return (
    <IonApp>
      <IonHeader>
        <IonToolbar>
          <IonTitle>Ionic React CRUD</IonTitle>
        </IonToolbar>
      </IonHeader>
      <IonContent>
        <IonList>
              ...
              <div slot="end">
                <IonButton
                  color="danger"
                  onClick={() => handleTaskDelete(task.id)}
                >
                  <IonIcon icon={trash} />
                </IonButton>
                ...
              </div>
            </IonItem>
          ))}
        </IonList>
        ..
      </IonContent>
    </IonApp>
  );

```

The content of the app is contained within the `IonContent` component, which contains an `IonList` component. In the `IonList`, there can be multiple `IonItem` components that represent individual tasks.

Within each `IonItem`, there is a `div` with a `slot` attribute of `"end"`. Within this `div`, there is an `IonButton` component that has a `color` attribute set to `"danger"` to represent a delete action.

When the button is clicked, it triggers the `handleTaskDelete` function (which we defined earlier and is likely declared in the same file) with the `id` of the task as an argument. This removes the task from the `tasks` list and triggers a re-render of the component to reflect the updated list.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1684787648359/1357ddb4-1d06-441d-a257-afef4a0971de.png)

### Complete component code

```
import React, { useState, useEffect } from "react";
import {
  IonApp,
  IonHeader,
  IonToolbar,
  IonTitle,
  IonContent,
  IonList,
  IonItem,
  IonLabel,
  IonInput,
  IonCheckbox,
  IonButton,
  IonIcon,
  InputChangeEventDetail
} from "@ionic/react";
import { add, trash, create, checkmark } from "ionicons/icons";

type Task = {
  id: number;
  task: string;
  completed: boolean;
};

const Tasks: React.FC = () => {
  const [tasks, setTasks] = useState<Task[]>([]);
  const [newTask, setNewTask] = useState<string>("");

  useEffect(() => {
    const savedTasks = localStorage.getItem("tasks");
    if (savedTasks) {
      setTasks(JSON.parse(savedTasks));
    }
  }, []);

  useEffect(() => {
    localStorage.setItem("tasks", JSON.stringify(tasks));
  }, [tasks]);

  const handleTaskInput = (event: CustomEvent<InputChangeEventDetail>) => {
    setNewTask((event.target as HTMLInputElement).value);
  };

  const handleNewTask = () => {
    const task: Task = {
      id: Date.now(),
      task: newTask,
      completed: false
    };
    setTasks([...tasks, task]);
    setNewTask("");
  };

  const handleTaskDelete = (id: number) => {
    const updatedTasks = tasks.filter((task) => task.id !== id);
    setTasks(updatedTasks);
  };

  const handleTaskEdit = (id: number, taskText: string) => {
    const updatedTasks = tasks.map((task) => {
      if (task.id === id) {
        return { ...task, task: taskText };
      }
      return task;
    });
    setTasks(updatedTasks);
  };

  const handleTaskToggle = (id: number) => {
    const updatedTasks = tasks.map((task) => {
      if (task.id === id) {
        return { ...task, completed: !task.completed };
      }
      return task;
    });
    setTasks(updatedTasks);
  };

  return (
    <IonApp>
      <IonHeader>
        <IonToolbar>
          <IonTitle>Ionic React CRUD</IonTitle>
        </IonToolbar>
      </IonHeader>
      <IonContent>
        <IonList>
          {tasks.map((task) => (
            <IonItem key={task.id}>
              <IonLabel>
                <h2>{task.task}</h2>
                <IonCheckbox
                  checked={task.completed}
                  onIonChange={() => handleTaskToggle(task.id)}
                />
              </IonLabel>
              <div slot="end">
                <IonButton
                  color="danger"
                  onClick={() => handleTaskDelete(task.id)}
                >
                  <IonIcon icon={trash} />
                </IonButton>
                <IonButton
                  color="primary"
                  onClick={() =>
                    handleTaskEdit(
                      task.id,
                      window.prompt("Update Task", task.task) || ""
                    )
                  }
                >
                  <IonIcon icon={create} />
                </IonButton>
              </div>
            </IonItem>
          ))}
        </IonList>
        <IonItem>
          <IonInput
            placeholder="Add Task"
            value={newTask}
            onIonChange={handleTaskInput}
          />
          <IonButton color="success" onClick={handleNewTask}>
            <IonIcon icon={add} />
            <IonLabel>Add</IonLabel>
          </IonButton>
        </IonItem>
      </IonContent>
    </IonApp>
  );
};

export default Tasks;

```

### Running the app in the Android emulator

To run the app in an Android emulator, we need to have installed the Android SDK.

We run the following command in our command line:

```
ionic capacitor run android

```

Ionic will ask you the device target. This message will not appear if we don't have the Android SDK installed.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1681829872438/9d1dbd9c-6243-403d-9173-844f061205db.png?auto=compress,format&format=webp)

Then, an Android device will appear on your screen.

But sometimes it can appear an error message like this:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1684798041592/2806e35f-18f1-44e6-ba2e-255cd539543d.png)

In that case, what works for me was to open the project with Android Studio. And the IDE will install some Gradle files. After the installation is completed. We can run again the `ionic capacitor run android` command or click on the Run button in Android Studio.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1684798306307/864a17b1-3307-47fb-9307-a7953fa85abe.png)

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1684800344570/47e542f2-ab5b-4e36-8327-bb64ba2db104.png)

### Conclusion

If you are interested in building mobile apps, then Ionic and React are great choices. With these frameworks, you can create beautiful, responsive apps that can be used on any device. Building with Ionic offers some benefits like cross-platform compatibility, so we can build apps that run on both iOS and Android devices. Ionic components are reusable, so we can use them in multiple apps.

Thank you for taking the time to read this article.

If you have any recommendations about other packages, architectures, how to improve my code, my English, or anything; please leave a comment or contact me through [**Twitter**](https://twitter.com/Carlos_marcv), or [**LinkedIn**](https://www.linkedin.com/in/carlos-marcano-a2135a134/).

The source code is [here](https://github.com/carlosm27/todoAppIonic).

### Resources

- [Ionic Documentation](https://ionicframework.com/docs).

- [Install Android Studio](https://developer.android.com/studio/install).

- [Set up the Android 14 SDK](https://developer.android.com/about/versions/14/setup-sdk).

- [React Router documentation.](https://reactrouter.com/en/main)

- [How To Make a To-do App with Ionic + React](https://medium.com/@benchedms/how-to-make-a-to-do-app-with-ionic-react-d92ece061284).

- [Creating a CRUD To-do App with Ionic 4.](https://www.freecodecamp.org/news/creating-a-crud-to-do-app-using-ionic-4/)

- [IONIC + React App - Fetch API Data with Custom Hook Example](https://www.webune.com/forums/aapyez.html).

- [How to Perform CRUD Operations using React, React Hooks, and Axios](https://www.freecodecamp.org/news/how-to-perform-crud-operations-using-react/).

- [How to Consume REST APIs in React.](https://www.freecodecamp.org/news/how-to-consume-rest-apis-in-react/)

- [Fast API + React App With HarperDB.](https://medium.com/@dennisivy/fast-api-react-crud-app-with-harperdb-5834af537c23)

- [Updating Arrays in State](https://react.dev/learn/updating-arrays-in-state#).