DEV Community

Cover image for TO-DO List - CRUD Full Stack in Ionic Type Script React and Parse Back4app
And Go Web Solutions | AGWS
And Go Web Solutions | AGWS

Posted on

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

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= 
Enter fullscreen mode Exit fullscreen mode
  • After these simple steps Serve application and Enjoy !

Start By 🚀

ionic serve 
Enter fullscreen mode Exit fullscreen mode
  • 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 
Enter fullscreen mode Exit fullscreen mode
  1. ionic framework
 npm i -g @ionic/cli 
Enter fullscreen mode Exit fullscreen mode

Setup the project

ionic start todoApp --type=react --capacitor 
Enter fullscreen mode Exit fullscreen mode

-- 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 
Enter fullscreen mode Exit fullscreen mode
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 <div id="root" ></div> - .env // Where all the Api Keys are going to be saftely stored for production 
Enter fullscreen mode Exit fullscreen mode
  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 ( <></>  ) } 
Enter fullscreen mode Exit fullscreen mode
 //ADD STATE VAR AND STATE ACTION AND ASSIGN PROPERTIES const [newToDoObject, setNewToDoObject] = useState({ title: "", description: "", task: "", isCompleted: false, createdAt: new Date(), updatedAt: new Date(), }); 
Enter fullscreen mode Exit fullscreen mode
//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); } }; 
Enter fullscreen mode Exit fullscreen mode
 //Hanlde ToDoChg  const handleToDoCHG= (event: any)=> { setNewToDoObject((previous : any)=> ({ ...previous, [event.target.name]: event.target.value, })); //html5 }; 
Enter fullscreen mode Exit fullscreen mode
  • 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  <IonGrid fixed={true}> <IonText> Create ToDo <IonIcon icon={paperPlaneOutline}/> </IonText> <IonInput name="title" onIonChange={handleToDoCHG} placeholder="Enter Title here..." maxlength={25}/> <IonInput name="task" onIonChange={handleToDoCHG} placeholder="Enter Task here..." maxlength={25} /> <IonTextarea name="description" onIonChange={handleToDoCHG} style={{resize: "none"}} placeholder="Enter Description here..." maxlength={100}/> <IonButton onClick={createNewToDoObject} expand="block" color={"success"}> <IonIcon icon={add} /> </IonButton> </IonGrid> 
Enter fullscreen mode Exit fullscreen mode

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 ( <> <IonGrid fixed={true}> <IonText> Create ToDo <IonIcon icon={paperPlaneOutline} /> </IonText> <IonRow> <IonCol size="6"> <IonItem> <IonLabel color={"success"} position="stacked"> Title </IonLabel> <IonInput name="title" onIonChange={handleToDoCHG} placeholder="Enter Title here..." maxlength={25} /> </IonItem> </IonCol> <IonCol size="6"> <IonItem> <IonLabel color={"success"} position="stacked"> Task </IonLabel> <IonInput name="task" onIonChange={handleToDoCHG} placeholder="Enter Task here..." maxlength={25} /> </IonItem> </IonCol> <IonCol size="10"> <IonItem> <IonLabel color={"success"} position="stacked"> Description </IonLabel> <IonTextarea name="description" onIonChange={handleToDoCHG} style={{ resize: "none" }} placeholder="Enter Description here..." maxlength={100} /> </IonItem> </IonCol> <IonCol size="2"> <IonButton onClick={createNewToDoObject} expand="block" color={"success"} > {" "} <IonIcon icon={add} /> </IonButton> </IonCol> </IonRow> </IonGrid> </> ); } 
Enter fullscreen mode Exit fullscreen mode
  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(), }, ]); 
Enter fullscreen mode Exit fullscreen mode
 //2-B. extending the Parse object const ToDo: Parse.Object[] = Parse.Object.extend("ToDo"); // extend todo const parsequery: Parse.Query = new Parse.Query(ToDo); 
Enter fullscreen mode Exit fullscreen mode
 //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<Boolean> { 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); 
Enter fullscreen mode Exit fullscreen mode
 // 2-D. useEffect useEffect(() => { readTasks(); //uncomment these lines after addint the refreshTasks async arrow function //refreshTasks(); }, [readTasks, /*refreshTasks*/]); 
Enter fullscreen mode Exit fullscreen mode
  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); } }; 
Enter fullscreen mode Exit fullscreen mode
  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); } }; 
Enter fullscreen mode Exit fullscreen mode
  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 >---------*/ 
Enter fullscreen mode Exit fullscreen mode

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<Boolean> { 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 ( <> <IonRow> <IonCol size="10"> <IonButton onClick={refreshTasks} color="secondary" expand="block"> <IonIcon icon={returnDownBack} /> </IonButton> </IonCol> <IonCol size="2"> <IonBadge color={"medium"}>{toDos?.length}</IonBadge> </IonCol> </IonRow> {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 ( <div key={todo + index}> <IonGrid fixed={true}> <IonRippleEffect></IonRippleEffect> <IonCard color={todo.isCompleted === true ? "success" : "medium"}> <IonCardHeader color={todo?.isCompleted === true ? "light" : "warning"} > <IonRow> <IonCol size="9"> <IonText color={todo?.isCompleted === true ? "dark" : "light"} > <h5>{[todo?.title?.toLocaleUpperCase() || " "]}</h5> </IonText> </IonCol> <IonCol size="3"> <IonButton color="danger" expand="block" onClick={deleteToDo} > <IonIcon icon={close} />{" "} </IonButton> </IonCol> </IonRow> </IonCardHeader> <IonItem color={todo?.isCompleted === true ? "success" : "medium"} > <IonText color={"light"}> Task :{[todo?.task?.toLocaleLowerCase() || " "]} </IonText> </IonItem> <IonCardSubtitle className="ion-text-center"> <h5 className="ion-text-white"> <strong>Description</strong> </h5> <em>{[todo?.description?.toLocaleLowerCase() || " "]}</em> </IonCardSubtitle> <IonCardContent> <IonRow> <IonCol size="10"> <table> <thead> <tr> <th>Task</th> <th>Completed</th> <th>CreatedAt</th> <th>updatedAt</th> </tr> </thead> <tbody> <tr> <td> {todo?.task}</td> <td> {" "} <IonCheckbox color="medium" // eslint-disable-next-line react/jsx-no-duplicate-props onClick={completeTask} disabled={todo?.isCompleted === true} />{" "} {todo?.isCompleted.toLocaleString()} </td> <td> {todo.createdAt?.toDateString()}</td> <td> {todo.updatedAt?.toDateString()}</td> </tr> </tbody> </table> </IonCol> </IonRow> </IonCardContent> </IonCard> </IonGrid> </div> ); })} </> ); }; export default EditToDo; 
Enter fullscreen mode Exit fullscreen mode
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

2.

Omar Zeinhom . AKA ANDGOEDU 2022-2023

Top comments (0)