TO-DO List – CRUD Full Stack in Ionic Type Script React and Parse Back4app

TO-DO List – CRUD Full Stack in Ionic Type Script React and Parse Back4app
  1. About

Demo

An ionic 6 , web application built in typescript and react js framework ,while in terms of the backend back 4app was used as the api .

  • Start By

  • yarn install

  • ADD Api keys from parse dashboard back4app

    1. Select your application or create a new one
  • 1.1 – Make ACLS public . Note this is not recommended for deployment only development

    1. Go to App settings on the left
    1. Select security and keys and get the api keys
REACT_APP_PARSE_ID=
REACT_APP_PARSE_HOST_URL=
REACT_APP_PARSE_JS_KEY=
  • After these simple steps Serve application and Enjoy !

Start By πŸš€

ionic serve
  • Project Built With

Project Requirements

  1. nodejs 16.18.0

Make sure you installed node and node package manager using npm -v and node -v

  1. yarn
  • Install yarn by using

npm install -g yarn
  1. ionic framework

npm i -g @ionic/cli

Setup the project

ionic start todoApp --type=react --capacitor

β€” use yarn instead of npm ionic config set -g yarn true

Packages to install

parse From parse – yarn pkg @parse/react From @parse/react – yarn pkg

& Getting started with the Parse React hook for real time updates using Parse

yarn add @parse/react parse
Project Structure and files to add
- public
- /assets               // images
-  /icons               // favicon.ico for example
- index.html            // the html rendered webpage
- src                   //root folder
- /components           // where all the components reside
-  /CreateToDo          //1. create new folder inside ./src/components/ call it CreateToDo
-  /CreateToDo.tsx      //2.  create new file inside /src/components/CreateToDo call it CreateToDo.tsx 
- /pages                //where all pages reside
-  /EditToDo            //3.create new folder inside ./src/pages/ call it EditToDo
-  /EditToDo.tsx        //4. create new file inside ./src/pages/EditToDo call it EditToDo.tsx 
- /theme                // Where ionic app.css styles reside
-   /variables.css      // ionic default css variables for dark or light mode
- App.tsx               // Where the application component resides, the ionic router and also initializeParse Client
- index.tsx             // Where the application renders in the index.html 
- .env // Where all the Api Keys are going to be saftely stored for production
  1. CREATE [x]
// ADD IMPORTS 
import React, { useState, useEffect } from "react";
import {
  IonCol,
  IonLabel,
  IonInput,
  IonTextarea,
  IonButton,
  IonIcon,
  IonGrid,
  IonRow,
  IonItem,
  IonText,
} from "@ionic/react";

import { add,paperPlaneOutline } from "ionicons/icons";

const Parse = require("parse");

// Export a default function 

export default function CreateToDo() {

    return (
        <>
    )
    }
 //ADD STATE VAR AND STATE ACTION AND ASSIGN PROPERTIES
  const [newToDoObject, setNewToDoObject] = useState({
    title: "",
    description: "",
    task: "",
    isCompleted: false,
    createdAt: new Date(),
    updatedAt: new Date(),
  });
//ADD async arrow function to handle creating the new to object{}

 const createNewToDoObject = async () => {
    const newToDo = new Parse.Object("ToDo", newToDoObject);
    newToDo.set(newToDoObject);

    try {
      const newToDoObject = await newToDo.save();

      const newToDoObjJSON = JSON.stringify(newToDoObject);

      alert("The New To Do Object Has been Created >>>>! " + newToDoObjJSON);
    } catch (error: any) {
      alert("Errro was found in createNewToDoObject " + error);
    }
  };

//Hanlde ToDoChg 

const handleToDoCHG= (event: any)=> {

setNewToDoObject((previous : any)=> ({
  ...previous,
  [event.target.name]: event.target.value,
}));

//html5

};
  • Make sure to match the html5 property name with the properties passed to the object
  • Also add onIonChange={handleToDoCHG} in each input to handle the users input
//ADD html5 name property and handleToDoCHG to handle the user inputs change 



  Create ToDo 
  

    

      

      

 


Final File CreateToDo.tsx

//CreateToDo.tsx
import React, { useState, useEffect } from "react";
import {
  IonCol,
  IonLabel,
  IonInput,
  IonTextarea,
  IonButton,
  IonIcon,
  IonGrid,
  IonRow,
  IonItem,
  IonText,
} from "@ionic/react";

import { add, paperPlaneOutline } from "ionicons/icons";

const Parse = require("parse");

export default function CreateToDo() {
  //STATE VAR AND STATE ACTION AND ASSIGN PROPERTIES
  const [newToDoObject, setNewToDoObject] = useState({
    title: "",
    description: "",
    task: "",
    isCompleted: false,
    createdAt: new Date(),
    updatedAt: new Date(),
  });

  const createNewToDoObject = async () => {
    const newToDo = new Parse.Object("ToDo", newToDoObject);
    newToDo.set(newToDoObject);

    try {
      const newToDoObject = await newToDo.save();

      const newToDoObjJSON = JSON.stringify(newToDoObject);

      alert("The New To Do Object Has been Created >>>>! " + newToDoObjJSON);
    } catch (error: any) {
      alert("Errro was found in createNewToDoObject " + error);
    }
  };

  //Hanlde ToDoChg

  const handleToDoCHG = (event: any) => {
    setNewToDoObject((previous: any) => ({
      ...previous,
      [event.target.name]: event.target.value,
    }));

    //html5
  };

  return (
    <>
      


          Create ToDo 
        


          


              
                Title
              
              
            
          

          


              
                Task
              
              
            
          

          


              
                Description
              
              
            
          

          
            
              {" "}
              
            
          
        
      
    
  );
}
  1. READ [x]
  • For this part you can assign a new component in ./src/component/EditToDo/EdiToDo.tsx
//2-A. SET STATE VAR And SetStateAction
  var [toDos, setToDos] = useState([
    {
      objectId: " ",
      title: "",
      description: "",
      task: "",
      isCompleted: Boolean(),
      createdAt: new Date(),
      updatedAt: new Date(),
    },
  ]);
 //2-B. extending the Parse object
  const ToDo: Parse.Object[] = Parse.Object.extend("ToDo"); // extend todo

  const parsequery: Parse.Query = new Parse.Query(ToDo);
  //2-C. ASYNC Function to handle reading tasks with useCallback hook to handle each task instead of going in an infinte loop
  const readTasks = useCallback(async function (): Promise
 {
    try {
      const results: Parse.Object[] = await parsequery.find();

      const mappedData = [];

      for (const object of results) {
        const objId: string = object.id;
        const title: string = object.get("title");
        const decription: string = object.get("description");
        const task: string = object.get("task");
        const isCompleted: boolean = object.get("isCompleted");
        const createdAt: Date = object.get("createdAt");
        const updatedAt: Date = object.get("updatedAt");

        let resultsFix = {
          objectId: objId, //string
          title: title, //string
          description: decription,
          task: task,
          isCompleted: isCompleted, //boolean
          createdAt: createdAt, //date
          updatedAt: updatedAt, //date
        };

        mappedData.push(resultsFix);
      }
      setToDos(mappedData);
      return true;
    } catch (error: any) {
      console.warn("Error has been found in readTasks " + error);
      return false;
    }
  }, []);

  console.log(toDos);
  // 2-D. useEffect
  useEffect(() => {
    readTasks();
    //uncomment these lines after addint the refreshTasks async arrow function
    //refreshTasks();
  }, [readTasks, /*refreshTasks*/]);
  1. UPDATE [X]
    //UPDATE TODO

        const completeTask = async () => {
          try {
            const object = await parsequery.get(objId);
            object.set("isCompleted", true);
            object.set("objectId", objId);
            object.save();
          } catch (error: any) {
            console.warn("Error has been found in completeTask" + error);
          }
        };
  1. DELETE [X]
   //DELETE TODO
        const deleteToDo = async () => {
          try {
            const singleObject: Parse.Object = await parsequery.get(objId);

            const response: any = await singleObject.destroy();

            if (response) {
              alert(`${objId} To Do Has Been Deleted`);
            } else {
              alert(`Error: Nothing was Delted`);
            }

            return true;
          } catch (error: any) {
            console.warn("Error has been found in deleteToDo" + error);
          }
        };
  1. Refresh Tasks
 /*-------------< TODO REFRESH TASKS START >---------*/
  const refreshTasks = useCallback(
    async function () {
      var query = new Parse.Query("ToDo");
      query
        .find()
        .then((results: Parse.Object) => {
          //DEBUG
          //Stringified Value of Results
          //const resultsStr = JSON.stringify(results);
          //console.log("Results of ToDo parse Object is >>>" + resultsStr);
          //
        })
        .then(() => {
          query.count().then((ToDoCount: Number) => {
            console.log("Number of tasks is = " + ToDoCount);
          });
        })
        .catch((error: any) => {
          // error is an instance of parse.error.
          console.log(error);
        });
      //REFRESH TASKS TO REMOVE THE DELETED ONES ID
      readTasks();
      return true;
    },
    [readTasks]
  );
  /*-------------< TODO REFRESH TASKS END >---------*/

Final File in ./src/components/EditToDo/EditToDo.tsx

import React from "react";
import {
  IonButton,
  IonCard,
  IonCardContent,
  IonCardHeader,
  IonCardSubtitle,
  IonCol,
  IonIcon,
  IonItem,
  IonText,
  IonCheckbox,
  IonBadge,
  IonRippleEffect,
  IonRow,
  IonGrid,
} from "@ionic/react";

import { close, returnDownBack } from "ionicons/icons";

import { FC, ReactElement, useCallback, useEffect, useState } from "react";

const Parse = require("parse");

const EditToDo: FC<{}> = (): ReactElement => {
  //1. STATE VAR And SetStateAction
  var [toDos, setToDos] = useState([
    {
      objectId: " ",
      title: "",
      description: "",
      task: "",
      isCompleted: Boolean(),
      createdAt: new Date(),
      updatedAt: new Date(),
    },
  ]);

  // extending the Parse object
  const ToDo: Parse.Object[] = Parse.Object.extend("ToDo"); // extend todo

  const parsequery: Parse.Query = new Parse.Query(ToDo);

  //2. ASYNC Function to handle reading tasks with useCallback hook to handle each task instead of going in an infinte loop
  const readTasks = useCallback(async function (): Promise
 {
    try {
      const results: Parse.Object[] = await parsequery.find();

      const mappedData = [];

      for (const object of results) {
        const objId: string = object.id;
        const title: string = object.get("title");
        const decription: string = object.get("description");
        const task: string = object.get("task");
        const isCompleted: boolean = object.get("isCompleted");
        const createdAt: Date = object.get("createdAt");
        const updatedAt: Date = object.get("updatedAt");

        let resultsFix = {
          objectId: objId, //string
          title: title, //string
          description: decription,
          task: task,
          isCompleted: isCompleted, //boolean
          createdAt: createdAt, //date
          updatedAt: updatedAt, //date
        };

        mappedData.push(resultsFix);
      }
      setToDos(mappedData);
      return true;
    } catch (error: any) {
      console.warn("Error has been found in readTasks " + error);
      return false;
    }
  }, []);

  console.log(toDos);

  /*-------------< TODO REFRESH TASKS START >---------*/
  const refreshTasks = useCallback(
    async function () {
      var query = new Parse.Query("ToDo");
      query
        .find()
        .then((results: Parse.Object) => {
          //DEBUG
          //Stringified Value of Results
          //const resultsStr = JSON.stringify(results);
          //console.log("Results of ToDo parse Object is >>>" + resultsStr);
          //
        })
        .then(() => {
          query.count().then((ToDoCount: Number) => {
            console.log("Number of tasks is = " + ToDoCount);
          });
        })
        .catch((error: any) => {
          // error is an instance of parse.error.
          console.log(error);
        });
      //REFRESH TASKS TO REMOVE THE DELETED ONES ID
      readTasks();
      return true;
    },
    [readTasks]
  );
  /*-------------< TODO REFRESH TASKS END >---------*/

  // 3. useEffect
  useEffect(() => {
    readTasks();
    refreshTasks();
  }, [readTasks, refreshTasks]);

  return (
    <>


        
          
            
          
        

        
          {toDos?.length}
        
      

      {toDos?.map((todo: any, index: any) => {
        // MAP OVER THE TODOS AND RETURN THE INFO

        //GET ID

        var objId: string = todo?.objectId;
        //console.log(objId);

        //DELETE TODO
        const deleteToDo = async () => {
          try {
            const singleObject: Parse.Object = await parsequery.get(objId);

            const response: any = await singleObject.destroy();

            if (response) {
              alert(`${objId} To Do Has Been Deleted`);
            } else {
              alert(`Error: Nothing was Delted`);
            }

            return true;
          } catch (error: any) {
            console.warn("Error has been found in deleteToDo" + error);
          }
        };

        //UPDATE TODO

        const completeTask = async () => {
          try {
            const object = await parsequery.get(objId);
            object.set("isCompleted", true);
            object.set("objectId", objId);
            object.save();
          } catch (error: any) {
            console.warn("Error has been found in completeTask" + error);
          }
        };

        return (
          
{[todo?.title?.toLocaleUpperCase() || " "]}
{" "}
Task :{[todo?.task?.toLocaleLowerCase() || " "]}
Description
{[todo?.description?.toLocaleLowerCase() || " "]}
Task Completed CreatedAt updatedAt
{todo?.task} {" "} {" "} {todo?.isCompleted.toLocaleString()} {todo.createdAt?.toDateString()} {todo.updatedAt?.toDateString()}
); })} ); }; export default EditToDo;
References
  1. Signing up in Parser – Back4App Docs

  2. Logging Page in Parser – Back4App Docs

  3. How TO – Responsive Text W3Schools

  4. User Password Reset for React Parse – Back4App Docs

  5. Theming Basics Ionic-framework Colors

  6. Theming Basics Ionic-framework Colors customziation

  7. aaronksaunders-ionic-react-tabs-side-auth

  8. Stringify a JavaScript Array

  9. GitHubMapBoxLanguage

  10. Map-Box Ar Example

  11. CodePen HomeChange a map’s language

  12. Parse~ ParseQuery

  13. use-react-memo-wisely/

  14. React.memo

  15. Migrating from npm

  16. Colors – Ionic

  17. Parse JS Guide

  18. Building Your Own Hooks

  19. react-chat-app – Back4App Docs

  20. React CRUD tutorial – Back4App Docs

  21. Ionic – Inputs

  22. Ionic – IonCheckBox

  23. Ionic – ion-radio

  24. this operator js – MDN Docs

  25. Ionic -ion-grid

  26. Does not provide a valid apple-touch-icon

  27. Ionic -React Navigation

  28. ReactJs – useCallback hook

  29. Using Yarn Instead of Npm for Ionic #10647

Omar Zeinhom . AKA ANDGOEDU 2022-2023

Back to top