An Interest In:
Web News this Week
- April 23, 2024
- April 22, 2024
- April 21, 2024
- April 20, 2024
- April 19, 2024
- April 18, 2024
- April 17, 2024
October 21, 2022 06:20 pm GMT
Original Link: https://dev.to/omarashzeinhom/to-do-list-crud-full-stack-in-ionic-type-script-react-and-parse-back4app-j8a
TO-DO List - CRUD Full Stack in Ionic Type Script React and Parse Back4app
About
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
- Select your application or create a new one
1.1 - Make ACLS public . Note this is not recommended for deployment only development
- Go to App settings on the left
- 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
Make sure you installed node and node package manager using
npm -v
and
node -v
- Install yarn by using
npm install -g yarn
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 <div id="root" ></div> - .env // Where all the Api Keys are going to be saftely stored for production
- 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 <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>
Final File CreateToDo.tsx
//CreateToDo.tsximport 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> </> );}
- 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<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);
// 2-D. useEffect useEffect(() => { readTasks(); //uncomment these lines after addint the refreshTasks async arrow function //refreshTasks(); }, [readTasks, /*refreshTasks*/]);
- 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); } };
- 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); } };
- 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<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;
References
- Signing up in Parser - Back4App Docs
- Logging Page in Parser - Back4App Docs
- How TO - Responsive Text W3Schools
- User Password Reset for React Parse - Back4App Docs
- Theming Basics Ionic-framework Colors
- Theming Basics Ionic-framework Colors customziation
- aaronksaunders-ionic-react-tabs-side-auth
- Stringify a JavaScript Array
- GitHubMapBoxLanguage
- Map-Box Ar Example
- CodePen HomeChange a map's language
- Parse~ ParseQuery
- use-react-memo-wisely/
- React.memo
- Migrating from npm
- Colors - Ionic
- Parse JS Guide
- Building Your Own Hooks
- react-chat-app - Back4App Docs
- React CRUD tutorial - Back4App Docs
- Ionic - Inputs
- Ionic - IonCheckBox
- Ionic - ion-radio
- this operator js - MDN Docs
- Ionic -ion-grid
- Does not provide a valid apple-touch-icon
- Ionic -React Navigation
- ReactJs - useCallback hook
- Using Yarn Instead of Npm for Ionic #10647
2.
Omar Zeinhom . AKA ANDGOEDU 2022-2023
Original Link: https://dev.to/omarashzeinhom/to-do-list-crud-full-stack-in-ionic-type-script-react-and-parse-back4app-j8a
Share this article:
Tweet
View Full Article
Dev To
An online community for sharing and discovering great ideas, having debates, and making friendsMore About this Source Visit Dev To