Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
October 21, 2022 06:20 pm GMT

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=
  • 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 <div id="root" ></div>  - .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 addonIonChange={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>    </>  );}
  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<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*/]);
  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<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
  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


Original Link: https://dev.to/omarashzeinhom/to-do-list-crud-full-stack-in-ionic-type-script-react-and-parse-back4app-j8a

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To