Skip to content

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

Notifications You must be signed in to change notification settings

andgo-edu/todolist-back4app-react-ts-ionic-6-CRUD

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

12 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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

Setup BackEnd Schema

// Step 1 Add Parse API Keys into App.tsx
//  Require Parse for tsx
const Parse = require('parse');

// Step 1 Your Parse initialization configuration goes here
const PARSE_APPLICATION_ID = ' ';
const PARSE_HOST_URL = 'https://parseapi.back4app.com/';
const PARSE_JAVASCRIPT_KEY = ' ';
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 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.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>
    </>
  );
}
  1. READ []
//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);
  // 3. useEffect
  useEffect(() => {
    readTasks();
    //uncomment these lines after addint the refreshTasks async arrow function
    //refreshTasks();
  }, [readTasks, /*refreshTasks*/]);
  1. UPDATE []
    //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 []
   //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

Omar Zeinhom . AKA ANDGOEDU 2022-2023

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

Resources

Stars

Watchers

Forks

Packages

No packages published

Contributors 2

  •  
  •