{"id":9255,"date":"2022-11-25T11:54:56","date_gmt":"2022-11-25T11:54:56","guid":{"rendered":"https:\/\/codevoweb.com\/?p=9255"},"modified":"2023-05-16T10:50:20","modified_gmt":"2023-05-16T10:50:20","slug":"build-a-reactjs-crud-app-with-javascript-fetch-api","status":"publish","type":"post","link":"https:\/\/codevoweb.com\/build-a-reactjs-crud-app-with-javascript-fetch-api\/","title":{"rendered":"Build a React.js CRUD App with JavaScript Fetch API"},"content":{"rendered":"\n<p>In this tutorial, you&#8217;ll learn how to build a React.js CRUD application with JavaScript Fetch API, tailwind CSS, and React-Hook-Form. This app will leverage Fetch API to perform CRUD (CREATE, READ, UPDATE, DELETE) operations against a RESTful API via HTTP protocol.<\/p>\n\n\n\n<p>To make the React Fetch API app communicate with a RESTful API, follow the steps provided in the &#8220;<strong>Run the Deno CRUD API Locally<\/strong>&#8221; section to quickly spin up a CRUD API built with Deno and TypeScript.<\/p>\n\n\n\n<span id=\"ezoic-pub-video-placeholder-107\"><\/span>\n\n\n\n<p>More practice:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"\/build-a-reactjs-crud-app-using-a-restful-api\/\">Build a React.js CRUD App using a RESTful API<\/a><\/li>\n\n\n\n<li><a href=\"\/build-a-fullstack-trpc-crud-app-with-nextjs\">Build a Full Stack tRPC CRUD App with Next.js<\/a><\/li>\n\n\n\n<li><a href=\"\/build-a-fullstack-trpc-crud-app-with-typescript\">Build a FullStack tRPC CRUD App with TypeScript<\/a><\/li>\n\n\n\n<li><a href=\"\/node-express-typeorm-postgresql-rest-api\">Node.js, Express, TypeORM, PostgreSQL: CRUD Rest API<\/a><\/li>\n\n\n\n<li><a href=\"\/nextjs-full-stack-app-with-react-query-and-graphql-codegen\">Next.js Full-Stack App with React Query, and GraphQL-CodeGen<\/a><\/li>\n\n\n\n<li><a href=\"\/fullstack-trpc-crud-application-with-nodejs-and-reactjs\">Build Full-Stack tRPC CRUD Application with Node.js, and React.js<\/a><\/li>\n\n\n\n<li><a href=\"\/graphql-crud-api-nextjs-mongodb-typegraphql\">GraphQL CRUD API with Next.js, MongoDB, and TypeGraphQL<\/a><\/li>\n<\/ul>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"850\" height=\"478\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/11\/Build-a-React.js-CRUD-App-with-JavaScript-Fetch-API.webp\" alt=\"Build a React.js CRUD App with JavaScript Fetch API\" class=\"wp-image-9278\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/11\/Build-a-React.js-CRUD-App-with-JavaScript-Fetch-API.webp 850w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/11\/Build-a-React.js-CRUD-App-with-JavaScript-Fetch-API-300x169.webp 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/11\/Build-a-React.js-CRUD-App-with-JavaScript-Fetch-API-768x432.webp 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/11\/Build-a-React.js-CRUD-App-with-JavaScript-Fetch-API-100x56.webp 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/11\/Build-a-React.js-CRUD-App-with-JavaScript-Fetch-API-700x394.webp 700w\" sizes=\"auto, (max-width: 850px) 100vw, 850px\" \/><\/figure>\n\n\n<style>.kb-table-of-content-nav.kb-table-of-content-id_39288a-8c .kb-table-of-content-wrap{padding-top:10px;padding-right:10px;padding-bottom:10px;padding-left:10px;border-top:1px solid #abb8c3;border-right:1px solid #abb8c3;border-bottom:1px solid #abb8c3;border-left:1px solid #abb8c3;}.kb-table-of-content-nav.kb-table-of-content-id_39288a-8c .kb-table-of-contents-title-wrap{padding-top:0px;padding-right:0px;padding-bottom:0px;padding-left:0px;}.kb-table-of-content-nav.kb-table-of-content-id_39288a-8c .kb-table-of-contents-title-wrap{color:#ffffff;}.kb-table-of-content-nav.kb-table-of-content-id_39288a-8c .kb-table-of-contents-title{color:#ffffff;font-weight:regular;font-style:normal;}.kb-table-of-content-nav.kb-table-of-content-id_39288a-8c .kb-table-of-content-wrap .kb-table-of-content-list{color:#ffffff;font-weight:regular;font-style:normal;margin-top:10px;margin-right:0px;margin-bottom:0px;margin-left:-5px;}@media all and (max-width: 1024px){.kb-table-of-content-nav.kb-table-of-content-id_39288a-8c .kb-table-of-content-wrap{border-top:1px solid #abb8c3;border-right:1px solid #abb8c3;border-bottom:1px solid #abb8c3;border-left:1px solid #abb8c3;}}@media all and (max-width: 767px){.kb-table-of-content-nav.kb-table-of-content-id_39288a-8c .kb-table-of-content-wrap{border-top:1px solid #abb8c3;border-right:1px solid #abb8c3;border-bottom:1px solid #abb8c3;border-left:1px solid #abb8c3;}}<\/style>\n\n\n<h2 class=\"wp-block-heading\">Prerequisites<\/h2>\n\n\n\n<p>Before proceeding with this tutorial, these prerequisites are needed to make the tutorial easier to follow.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>You should have basic knowledge of JavaScript, TypeScript, and React.js<\/li>\n\n\n\n<li><strong>Optional:<\/strong> Have Deno installed if you&#8217;ll be running the backend API<\/li>\n\n\n\n<li>You should have basic knowledge of RESTful architecture and how to interact with a CRUD API.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Run the Deno CRUD API Locally<\/h2>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Make sure you have the latest version of Deno installed on your machine. Visit <a href=\"https:\/\/deno.land\/manual\/getting_started\/installation\" target=\"_blank\" rel=\"noreferrer noopener\">https:\/\/deno.land\/manual\/getting_started\/installation<\/a> to install the right Deno binary for your operating system. If you already have it installed, run  <code>deno upgrade<\/code> to get the newest version of Deno.<\/li>\n\n\n\n<li>Download or clone the Deno CRUD project from <a href=\"https:\/\/github.com\/wpcodevo\/deno-crud-app\" target=\"_blank\" rel=\"noreferrer noopener\">https:\/\/github.com\/wpcodevo\/deno-crud-app<\/a> and open it with an IDE.<\/li>\n\n\n\n<li>Run <code>deno run -A src\/server.ts<\/code> to install the required dependencies and start the Deno HTTP server on port <strong>8000<\/strong>.<\/li>\n\n\n\n<li>Open any API testing software and test the Deno API or set up the frontend app to interact with the Deno API.<\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading\">Run the React Fetch API App Locally<\/h2>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Visit <a href=\"https:\/\/github.com\/wpcodevo\/react-crud-fetchapi-app\" target=\"_blank\" rel=\"noreferrer noopener\">https:\/\/github.com\/wpcodevo\/react-crud-fetchapi-app<\/a> to download or clone the React.js Fetch API CRUD project and open it with an IDE or text editor.<\/li>\n\n\n\n<li>Run <code>yarn<\/code> or <code>yarn install<\/code> in the terminal of the root directory to install all the project&#8217;s dependencies.<\/li>\n\n\n\n<li>Start the app on port <strong>3000<\/strong> with <code>yarn dev<\/code>.<\/li>\n\n\n\n<li>Open <strong>http:\/\/localhost:3000\/<\/strong> in a new tab to test the React Fetch API CRUD app against the Deno CRUD API. <strong>Note:<\/strong> opening the app on <code>http:\/\/127.0.0.1:3000<\/code> will result in a site can&#8217;t be reached or CORS errors.<\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading\">Setup the React.js Project<\/h2>\n\n\n\n<p>Typically, you might create a new React.js project with&nbsp;<a href=\"https:\/\/create-react-app.dev\/\" target=\"_blank\" rel=\"noreferrer noopener\">Create React App<\/a>, but the project scaffolding process can take a lot of time since over <strong>140 MB<\/strong> of dependencies has to be installed first. Although&nbsp;<a href=\"https:\/\/create-react-app.dev\/\" target=\"_blank\" rel=\"noreferrer noopener\">Create React App<\/a>&nbsp;works well, as the project size increases, its performance decreases.<\/p>\n\n\n\n<p><a href=\"https:\/\/vitejs.dev\/\" target=\"_blank\" rel=\"noreferrer noopener\">Vite<\/a>&nbsp;on the other hand is a lightweight tool that installs <strong>31 MB<\/strong> of dependencies to scaffold a project. This is a game changer and will save you a lot of time in starting a new project.<\/p>\n\n\n\n<p>So, we&#8217;ll use Vite to bootstrap the React project and configure it to use TypeScript for type-checking and tailwind CSS for styling.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Bootstrap React Project with Vite<\/h3>\n\n\n\n<p>To begin, go to the location where you would like to create the project and run this command to start the project scaffolding process.<\/p>\n\n\n\n<pre class=\"line-numbers language-shell\"><code>\nyarn create vite react-crud-fetchapi-app\n# or\nnpm create vite react-crud-fetchapi-app\n<\/code><\/pre>\n\n\n\n<p>This will create a folder with the name <code>react-crud-fetchapi-app<\/code> and download the <code>vite-create<\/code> binary from the NPM repository before prompting you to select your framework of choice. Select React as the framework and choose TypeScript as the variant.<\/p>\n\n\n\n<p>Once the project has been generated, open it with an IDE or text editor and run <code>yarn install<\/code> or <code>npm install<\/code> depending on the package manager you used to install all the necessary dependencies.<\/p>\n\n\n\n<p>After that, open the <strong>package.json<\/strong> file and change the <strong>dev<\/strong> script to:<\/p>\n\n\n\n<p><strong>package.json<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-json\"><code>\n{\n\"dev\": \"vite --host localhost --port 3000\"\n}\n<\/code><\/pre>\n\n\n\n<p>This will tell Vite to start the development server on port <strong>3000<\/strong> instead of the default port <strong>5173<\/strong>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Set up Tailwind CSS<\/h3>\n\n\n\n<p>Now that the project is ready, install these dependencies to help us integrate Tailwind CSS into the project.<\/p>\n\n\n\n<pre class=\"line-numbers language-shell\"><code>\nyarn add -D tailwindcss postcss autoprefixer\n# or\nnpm install -D tailwindcss postcss autoprefixer\n<\/code><\/pre>\n\n\n\n<p>Create the <code>postcss.config.cjs<\/code> and <code>tailwind.config.cjs<\/code> files with this command:<\/p>\n\n\n\n<pre class=\"line-numbers language-shell\"><code>\nyarn tailwindcss init -p\n# or\nnpx tailwindcss init -p\n<\/code><\/pre>\n\n\n\n<p>After the files have been generated, open the <code>tailwind.config.cjs<\/code> file and replace its content with the following tailwind CSS configurations:<\/p>\n\n\n\n<p><strong>tailwind.config.cjs<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-js\"><code>\n\/** @type {import('tailwindcss').Config} *\/\nmodule.exports = {\n  content: [\n    \".\/index.html\",\n    \".\/src\/**\/*.{vue,js,ts,jsx,tsx}\",\n  ],\n  theme: {\n    extend: {\n      colors: {\n        'ct-dark-600': '#222',\n        'ct-dark-200': '#575757',\n        'ct-dark-100': '#6d6d6d',\n        'ct-blue-600': '#01b274',\n        'ct-blue-700': '#019862',\n        'ct-yellow-600': '#f9d13e',\n      },\n      fontFamily: {\n        Poppins: ['Poppins, sans-serif'],\n      },\n      container: {\n        center: true,\n        padding: '1rem',\n        screens: {\n          lg: '1125px',\n          xl: '1125px',\n          '2xl': '1125px',\n          '3xl': '1500px'\n        },\n      },\n    },\n  },\n  plugins: [],\n};\n\n<\/code><\/pre>\n\n\n\n<p>In the above configurations, we provided the paths to the template files, added some custom colors, and specified <strong>Poppins<\/strong> as a custom font. Now open the <code>src\/index.css<\/code> file and add the import directive for the Poppins font, tailwind CSS directives, and these CSS styles.<\/p>\n\n\n\n<p><strong>src\/index.css<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-css\"><code>\n@import url('https:\/\/fonts.googleapis.com\/css2?family=Poppins:wght@300;400;500;600;700&amp;display=swap');\n\n#nprogress .bar {\n    background: #ffbb00 !important;\n    height: 3px !important;\n}\n\n#nprogress .spinner .spinner-icon {\n    width: 22px;\n    height: 22px;\n    border-top-color: #ffbb00 !important;\n    border-left-color: #ffbb00 !important;\n}\n\n#nprogress .spinner {\n    top: 15px;\n    left: 15px !important;\n}\n\n@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\nhtml{\n    font-family: 'Poppins', sans-serif;\n}\n\nbody{\n    background-color: #06de92;\n}\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Add Zustand State Manager<\/h2>\n\n\n\n<p>React&#8217;s client-side or server-side state management has evolved a lot during the past few years. In earlier times, states were managed by sharing data between components using props, but this approach became inefficient, especially when sharing states between multiple nested components. <\/p>\n\n\n\n<p>To address this issue, state management libraries like <a href=\"https:\/\/redux.js.org\/\" target=\"_blank\" rel=\"noreferrer noopener\">Redux<\/a>, Flux, <a href=\"https:\/\/xstate.js.org\/\" target=\"_blank\" rel=\"noreferrer noopener\">XState<\/a>, <a href=\"https:\/\/mobx.js.org\/README.html\" target=\"_blank\" rel=\"noreferrer noopener\">MobX<\/a>, <a href=\"https:\/\/github.com\/pmndrs\/zustand\" target=\"_blank\" rel=\"noreferrer noopener\">Zustand<\/a>, <a href=\"https:\/\/jotai.org\/\" target=\"_blank\" rel=\"noreferrer noopener\">Joi<\/a>, etc were developed; their primary aim is to store and manage the state in a central store.<\/p>\n\n\n\n<p>There are lots of popular state management libraries in React, but in this article, we&#8217;ll use Zustand because it is the lightest state management library available right now.  <\/p>\n\n\n\n<p>So, install the Zustand library with this command:<\/p>\n\n\n\n<pre class=\"line-numbers language-shell\"><code>\nyarn add zustand\n# or\nnpm i zustand\n<\/code><\/pre>\n\n\n\n<p>Before defining the Zustand store, create a <code>src\/types.ts<\/code> file and add the following TypeScript types. The <strong>INote<\/strong> type describes the fields that will be included in the Note item the API will return to the React app and we&#8217;ll use it to provide typings in the Zustand store. <\/p>\n\n\n\n<p>To make TypeScript happy, we&#8217;ll use the remaining types to type the responses returned by Fetch API.<\/p>\n\n\n\n<p><strong>src\/types.ts<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-ts\"><code>\nexport type INote = {\n  id: string;\n  title: string;\n  content: string;\n  createdAt: Date;\n  updatedAt: Date;\n};\n\nexport type IGenericResponse = {\n  status: string;\n  message: string;\n};\n\nexport type INoteResponse = {\n  status: string;\n  data: {\n    note: INote;\n  };\n};\n\nexport type INotesResponse = {\n  status: string;\n  results: number;\n  notes: INote[];\n};\n<\/code><\/pre>\n\n\n\n<p>We are now ready to create the store with some default states and actions to modify the values of the states. To create the store, we&#8217;ll leverage the <strong>Create API<\/strong> exposed by Zustand, and the actions will be created with named functions that return the <code>set()<\/code> method. The <strong>set()<\/strong> method has access to the current state in the store and it can be used to change the value of the state it references.<\/p>\n\n\n\n<p><strong>src\/store\/index.ts<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-ts\"><code>\nimport create from \"zustand\";\nimport { INote } from \"..\/types\";\n\ntype Store = {\n  notes: INote[] | [];\n  setNotes: (notes: INote[]) =&gt; void;\n  createNote: (note: INote) =&gt; void;\n  updateNote: (note: INote) =&gt; void;\n  deleteNote: (noteId: string) =&gt; void;\n};\n\nconst useStore = create&lt;Store&gt;((set) =&gt; ({\n  notes: [],\n  setNotes: (notes) =&gt; set((state) =&gt; ({ notes })),\n  createNote: (note) =&gt; set((state) =&gt; ({ notes: [...state.notes, note] })),\n  deleteNote: (noteId) =&gt;\n    set((state) =&gt; ({\n      notes: state.notes.filter((item) =&gt; item.id != noteId),\n    })),\n  updateNote: (note) =&gt;\n    set((state) =&gt; ({\n      notes: state.notes.map((item) =&gt; {\n        if (item.id === note.id) {\n          return Object.assign(item, note);\n        }\n        return item;\n      }),\n    })),\n}));\n\nexport default useStore;\n<\/code><\/pre>\n\n\n\n<p>Above, we created a store named <code>useStore<\/code> to track the state of the note items returned by the API and set its initial value to an empty array. To modify the <strong>notes<\/strong> state in the store, we&#8217;ll create four actions:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>setNotes<\/code> &#8211; This action will replace the empty <strong>notes<\/strong> array with the list of note items returned by Fetch API.<\/li>\n\n\n\n<li><code>createNote<\/code> &#8211; This action will add the newly-created note item to the store.<\/li>\n\n\n\n<li><code>updateNote<\/code> &#8211; This action will update the fields of a note item in the store.<\/li>\n\n\n\n<li><code>deleteNote<\/code> &#8211; This action will delete a note item from the store.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Create Reusable Components<\/h2>\n\n\n\n<p>Now that we&#8217;ve defined the Zustand store, let&#8217;s create some reusable React components and style them with tailwind CSS. In this section, you&#8217;ll create Spinner, LoadingButton, and Modal popup components with React and Tailwind CSS.<\/p>\n\n\n\n<p>First, install the <a href=\"https:\/\/www.npmjs.com\/package\/tailwind-merge\" target=\"_blank\" rel=\"noreferrer noopener\">tailwind-merge<\/a> package that we&#8217;ll use to merge tailwind CSS classes without worrying about conflicts between the classes.<\/p>\n\n\n\n<pre class=\"line-numbers language-shell\"><code>\nyarn add tailwind-merge\n# or \nnpm i tailwind-merge\n<\/code><\/pre>\n\n\n\n<p>Instead of using an image as the spinner, we&#8217;ll leverage the power of tailwind CSS and React to create the Spinner component with an SVG. To make the Spinner component customizable, we&#8217;ll pass a couple of props to it. <\/p>\n\n\n\n<p>This component will be made visible by the <strong>LoadingButton<\/strong> component and other components to indicate that a request is being processed by the backend API.<\/p>\n\n\n\n<p>To create the Spinner component, navigate to the <strong>src<\/strong> folder and create a <strong>components<\/strong> folder. Within the <strong>components<\/strong> folder, create a <code>Spinner.tsx<\/code> file and add the following code.<\/p>\n\n\n\n<p><strong>src\/components\/Spinner.tsx<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-tsx\"><code>\nimport React from 'react';\nimport { twMerge } from 'tailwind-merge';\ntype SpinnerProps = {\n  width?: number;\n  height?: number;\n  color?: string;\n  bgColor?: string;\n};\nconst Spinner: React.FC&lt;SpinnerProps&gt; = ({\n  width = 5,\n  height = 5,\n  color,\n  bgColor,\n}) =&gt; {\n  return (\n    &lt;svg\n      role='status'\n      className={twMerge(\n        'w-5 h-5 mr-2 text-gray-200 animate-spin dark:text-gray-600 fill-blue-600',\n        `w-${width} h-${height} ${color} ${bgColor}`\n      )}\n      viewBox='0 0 100 101'\n      fill='none'\n      xmlns='http:\/\/www.w3.org\/2000\/svg'\n    &gt;\n      &lt;path\n        d='M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z'\n        fill='currentColor'\n      \/&gt;\n      &lt;path\n        d='M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z'\n        fill='currentFill'\n      \/&gt;\n    &lt;\/svg&gt;\n  );\n};\n\nexport default Spinner;\n<\/code><\/pre>\n\n\n\n<p>Now that we&#8217;ve created the <strong>Spinner<\/strong> component, let&#8217;s create a <strong>LoadingButton<\/strong> component to use it. The <strong>LoadingButton<\/strong> component also accepts props to make the button customizable.<\/p>\n\n\n\n<p>The Spinner component will be hidden by default but when a request is in flight and the loading state is <code>true<\/code>, it will be made visible to indicate that the request is being handled by the backend API.<\/p>\n\n\n\n<p>To create the <strong>LoadingButton<\/strong> component, navigate to the <strong>components<\/strong> folder within the <strong>src<\/strong> directory and create a <code>LoadingButton.tsx<\/code> file. After that, open the <code>LoadingButton.tsx<\/code> file and add these code snippets.<\/p>\n\n\n\n<p><strong>src\/components\/LoadingButton.tsx<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-tsx\"><code>\nimport React from \"react\";\nimport { twMerge } from \"tailwind-merge\";\nimport Spinner from \".\/Spinner\";\n\ntype LoadingButtonProps = {\n  loading: boolean;\n  btnColor?: string;\n  textColor?: string;\n  children: React.ReactNode;\n};\n\nexport const LoadingButton: React.FC&lt;LoadingButtonProps&gt; = ({\n  textColor = \"text-white\",\n  btnColor = \"bg-ct-blue-700\",\n  children,\n  loading = false,\n}) =&gt; {\n  return (\n    &lt;button\n      type=\"submit\"\n      className={twMerge(\n        `w-full py-3 font-semibold ${btnColor} rounded-lg outline-none border-none flex justify-center`,\n        `${loading &amp;&amp; \"bg-[#ccc]\"}`\n      )}\n    &gt;\n      {loading ? (\n        &lt;div className=\"flex items-center gap-3\"&gt;\n          &lt;Spinner \/&gt;\n          &lt;span className=\"text-white inline-block\"&gt;Loading...&lt;\/span&gt;\n        &lt;\/div&gt;\n      ) : (\n        &lt;span className={`text-lg font-normal ${textColor}`}&gt;{children}&lt;\/span&gt;\n      )}\n    &lt;\/button&gt;\n  );\n};\n<\/code><\/pre>\n\n\n\n<p>Now it&#8217;s time to create a modal popup with React portal. We could have used <strong>Z-index<\/strong> and some complex CSS code to render the modal in the root DOM tree but this method is inefficient since we have to temper with the DOM hierarchy.<\/p>\n\n\n\n<p>So, the best approach is to use React portals to render the modal component outside the root DOM tree.<\/p>\n\n\n\n<p>To create the modal popup component, go into the <code>src\/components<\/code> folder and create a <code>note.modal.tsx<\/code> file. Then, open the newly-created file and add the following TSX code.<\/p>\n\n\n\n<p><strong>src\/components\/note.modal.tsx<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-tsx\"><code>\nimport ReactDom from \"react-dom\";\nimport React, { FC } from \"react\";\n\ntype INoteModal = {\n  openNoteModal: boolean;\n  setOpenNoteModal: (open: boolean) =&gt; void;\n  children: React.ReactNode;\n};\n\nconst NoteModal: FC&lt;INoteModal&gt; = ({\n  openNoteModal,\n  setOpenNoteModal,\n  children,\n}) =&gt; {\n  if (!openNoteModal) return null;\n  return ReactDom.createPortal(\n    &lt;&gt;\n      &lt;div\n        className=\"fixed inset-0 bg-[rgba(0,0,0,.5)] z-[1000]\"\n        onClick={() =&gt; setOpenNoteModal(false)}\n      &gt;&lt;\/div&gt;\n      &lt;div className=\"max-w-lg w-full rounded-md fixed top-0 lg:top-[10%] left-1\/2 -translate-x-1\/2 bg-white z-[1001] p-6\"&gt;\n        {children}\n      &lt;\/div&gt;\n    &lt;\/&gt;,\n    document.getElementById(\"note-modal\") as HTMLElement\n  );\n};\n\nexport default NoteModal;\n<\/code><\/pre>\n\n\n\n<p>Since we want the modal popup component to render out of the root HTML element, open the <code>index.html<\/code> file and add the React portal element below the root div.<\/p>\n\n\n\n<pre class=\"line-numbers language-html\"><code>\n    &lt;!-- Add the portal below the root div --&gt;\n    &lt;div id=\"note-modal\"&gt;&lt;\/div&gt;\n<\/code><\/pre>\n\n\n\n<p>Also, since the project depends on two external CSS stylesheets, open the <code>index.html<\/code> file and replace its content with the following HTML code. This will import the <a href=\"https:\/\/unpkg.com\/boxicons@2.1.4\/css\/boxicons.min.css\" target=\"_blank\" rel=\"noreferrer noopener\">Nprogress<\/a> and <a href=\"https:\/\/boxicons.com\/usage#import-css\" target=\"_blank\" rel=\"noreferrer noopener\">Boxicons<\/a> stylesheets into the project via the link tags.<\/p>\n\n\n\n<p><strong>index.html<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-html\"><code>\n&lt;!DOCTYPE html&gt;\n&lt;html lang=\"en\"&gt;\n  &lt;head&gt;\n    &lt;meta charset=\"UTF-8\" \/&gt;\n    &lt;link rel=\"icon\" type=\"image\/svg+xml\" href=\"\/vite.svg\" \/&gt;\n    &lt;meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" \/&gt;\n    &lt;title&gt;Vite + React + TS&lt;\/title&gt;\n    &lt;link\n      href=\"https:\/\/unpkg.com\/boxicons@2.1.4\/css\/boxicons.min.css\"\n      rel=\"stylesheet\"\n    \/&gt;\n    &lt;link\n      href=\"https:\/\/unpkg.com\/nprogress@0.2.0\/nprogress.css\"\n      rel=\"stylesheet\"\n    \/&gt;\n  &lt;\/head&gt;\n  &lt;body&gt;\n    &lt;div id=\"root\"&gt;&lt;\/div&gt;\n    &lt;div id=\"note-modal\"&gt;&lt;\/div&gt;\n    &lt;script type=\"module\" src=\"\/src\/main.tsx\"&gt;&lt;\/script&gt;\n  &lt;\/body&gt;\n&lt;\/html&gt;\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Add the CRUD Components<\/h2>\n\n\n\n<p>In this section, you&#8217;ll build four React.js components that leverage Fetch API to perform CRUD operations against the backend API. Before we can start implementing the CRUD operations, install these dependencies with Yarn or NPM.<\/p>\n\n\n\n<pre class=\"line-numbers language-shell\"><code>\nyarn add zod react-toastify react-hook-form nprogress date-fns @hookform\/resolvers &amp;&amp; yarn add -D @types\/nprogress\n# or\nnpm install zod react-toastify react-hook-form nprogress date-fns @hookform\/resolvers &amp;&amp; npm install -D @types\/nprogress\n<\/code><\/pre>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code><a href=\"https:\/\/www.npmjs.com\/package\/zod\" target=\"_blank\" rel=\"noreferrer noopener\">zod<\/a><\/code> &#8211; A schema validation library with static type inference<\/li>\n\n\n\n<li><code><a href=\"https:\/\/www.npmjs.com\/package\/react-toastify\" target=\"_blank\" rel=\"noreferrer noopener\">react-toastify<\/a><\/code> &#8211; An alert notification library<\/li>\n\n\n\n<li><code><a href=\"https:\/\/www.npmjs.com\/package\/react-hook-form\" target=\"_blank\" rel=\"noreferrer noopener\">react-hook-form<\/a><\/code> &#8211; A form validation library that supports schema validation libraries like Zod, Yub, Joi, and others.<\/li>\n\n\n\n<li><code><a href=\"https:\/\/www.npmjs.com\/package\/nprogress\" target=\"_blank\" rel=\"noreferrer noopener\">nprogress<\/a><\/code> &#8211; A thin progress bar<\/li>\n\n\n\n<li><code><a href=\"https:\/\/www.npmjs.com\/package\/date-fns\" target=\"_blank\" rel=\"noreferrer noopener\">date-fns<\/a><\/code> &#8211; A library for manipulating dates in JavaScript<\/li>\n\n\n\n<li><code><a href=\"https:\/\/www.npmjs.com\/package\/@hookform\/resolvers\" target=\"_blank\" rel=\"noreferrer noopener\">@hookform\/resolvers<\/a><\/code> &#8211; A React-hook-form validation resolver.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Implement the CREATE Operation<\/h3>\n\n\n\n<p>At this point, we&#8217;re now ready to implement the first CRUD operation. To do that, go into the <strong>components<\/strong> directory and create a <strong>notes<\/strong> folder. Within the <strong>notes<\/strong> folder, create a <code>create.note.tsx<\/code> file and add the following code.<\/p>\n\n\n\n<p>The <strong>CreateNote<\/strong> component will be used to add a new note item to the database. It contains a form built with React Hook Form library. The form validation rules are defined with the Zod schema validation library and passed to React-Hook-Form <code>useForm&lt;T&gt;()<\/code> hook via the <code>@hookform\/resolvers\/zod<\/code> package.<\/p>\n\n\n\n<p>When the form is submitted and is valid, the <code>createNote()<\/code> function will be evoked to submit the form data to the backend API with Fetch API.<\/p>\n\n\n\n<p><strong>src\/components\/notes\/create.note.tsx<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-tsx\"><code>\nimport { FC, useState } from &quot;react&quot;;\nimport { SubmitHandler, useForm } from &quot;react-hook-form&quot;;\nimport { twMerge } from &quot;tailwind-merge&quot;;\nimport { object, string, TypeOf } from &quot;zod&quot;;\nimport { zodResolver } from &quot;@hookform\/resolvers\/zod&quot;;\nimport { LoadingButton } from &quot;..\/LoadingButton&quot;;\nimport { toast } from &quot;react-toastify&quot;;\nimport NProgress from &quot;nprogress&quot;;\nimport useStore from &quot;..\/..\/store&quot;;\nimport { INoteResponse } from &quot;..\/..\/types&quot;;\n\nconst BASE_URL = &quot;http:\/\/localhost:8000\/api&quot;;\n\ntype ICreateNoteProps = {\n  setOpenNoteModal: (open: boolean) =&gt; void;\n};\n\nconst createNoteSchema = object({\n  title: string().min(1, &quot;Title is required&quot;),\n  content: string().min(1, &quot;Content is required&quot;),\n});\n\nexport type CreateNoteInput = TypeOf&lt;typeof createNoteSchema&gt;;\n\nconst CreateNote: FC&lt;ICreateNoteProps&gt; = ({ setOpenNoteModal }) =&gt; {\n  const [loading, setLoading] = useState(false);\n  const noteStore = useStore();\n  const methods = useForm&lt;CreateNoteInput&gt;({\n    resolver: zodResolver(createNoteSchema),\n  });\n\n  const {\n    register,\n    handleSubmit,\n    formState: { errors },\n  } = methods;\n\n  const createNote = async (note: CreateNoteInput) =&gt; {\n    try {\n      NProgress.start();\n      setLoading(true);\n      const response = await fetch(`${BASE_URL}\/notes\/`, {\n        method: &quot;POST&quot;,\n        mode: &quot;cors&quot;,\n        cache: &quot;no-cache&quot;,\n        headers: {\n          &quot;Content-Type&quot;: &quot;application\/json&quot;,\n        },\n        body: JSON.stringify(note),\n      });\n      if (!response.ok) {\n        const error = await response.json();\n        throw error ? error : &quot;Something bad happended&quot;;\n      }\n\n      const data = (await response.json()) as INoteResponse;\n      noteStore.createNote(data.data.note);\n\n      setLoading(false);\n      setOpenNoteModal(false);\n      NProgress.done();\n      toast(&quot;Note created successfully&quot;, {\n        type: &quot;success&quot;,\n        position: &quot;top-right&quot;,\n      });\n    } catch (error: any) {\n      setLoading(false);\n      setOpenNoteModal(false);\n      NProgress.done();\n      const resMessage = error.message || error.detail || error.toString();\n      toast.error(resMessage, {\n        position: &quot;top-right&quot;,\n      });\n    }\n  };\n\n  const onSubmitHandler: SubmitHandler&lt;CreateNoteInput&gt; = async (data) =&gt; {\n    createNote(data);\n  };\n  return (\n    &lt;section&gt;\n      &lt;div className=&quot;flex justify-between items-center mb-3 pb-3 border-b border-gray-200&quot;&gt;\n        &lt;h2 className=&quot;text-2xl text-ct-dark-600 font-semibold&quot;&gt;Create Note&lt;\/h2&gt;\n        &lt;div\n          onClick={() =&gt; setOpenNoteModal(false)}\n          className=&quot;text-2xl text-gray-400 hover:bg-gray-200 hover:text-gray-900 rounded-lg p-1.5 ml-auto inline-flex items-center cursor-pointer&quot;\n        &gt;\n          &lt;i className=&quot;bx bx-x&quot;&gt;&lt;\/i&gt;\n        &lt;\/div&gt;\n      &lt;\/div&gt;\n      &lt;form className=&quot;w-full&quot; onSubmit={handleSubmit(onSubmitHandler)}&gt;\n        &lt;div className=&quot;mb-2&quot;&gt;\n          &lt;label className=&quot;block text-gray-700 text-lg mb-2&quot; htmlFor=&quot;title&quot;&gt;\n            Title\n          &lt;\/label&gt;\n          &lt;input\n            className={twMerge(\n              `appearance-none border border-gray-400 rounded w-full py-3 px-3 text-gray-700 mb-2  leading-tight focus:outline-none`,\n              `${errors[&quot;title&quot;] &amp;&amp; &quot;border-red-500&quot;}`\n            )}\n            {...methods.register(&quot;title&quot;)}\n          \/&gt;\n          &lt;p\n            className={twMerge(\n              `text-red-500 text-xs italic mb-2 invisible`,\n              `${errors[&quot;title&quot;] &amp;&amp; &quot;visible&quot;}`\n            )}\n          &gt;\n            {errors[&quot;title&quot;]?.message as string}\n          &lt;\/p&gt;\n        &lt;\/div&gt;\n        &lt;div className=&quot;mb-2&quot;&gt;\n          &lt;label className=&quot;block text-gray-700 text-lg mb-2&quot; htmlFor=&quot;title&quot;&gt;\n            Content\n          &lt;\/label&gt;\n          &lt;textarea\n            className={twMerge(\n              `appearance-none border border-gray-400 rounded w-full py-3 px-3 text-gray-700 mb-2 leading-tight focus:outline-none`,\n              `${errors.content &amp;&amp; &quot;border-red-500&quot;}`\n            )}\n            rows={6}\n            {...register(&quot;content&quot;)}\n          \/&gt;\n          &lt;p\n            className={twMerge(\n              `text-red-500 text-xs italic mb-2`,\n              `${errors.content ? &quot;visible&quot; : &quot;invisible&quot;}`\n            )}\n          &gt;\n            {errors.content &amp;&amp; (errors.content.message as string)}\n          &lt;\/p&gt;\n        &lt;\/div&gt;\n        &lt;LoadingButton loading={loading}&gt;Create Note&lt;\/LoadingButton&gt;\n      &lt;\/form&gt;\n    &lt;\/section&gt;\n  );\n};\n\nexport default CreateNote;\n<\/code><\/pre>\n\n\n\n<p>The returned TSX template contains the form with title and content input fields and validation messages. The control of the form input fields is handled by React Hook Form via the methods returned by evoking the<code>methods.register(name_of_field)<\/code> function.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Implement the UPDATE Operation<\/h3>\n\n\n\n<p>Let&#8217;s create a React component to implement the <strong>UPDATE<\/strong> CRUD operation. To do this, go into the <strong>components\/notes<\/strong> folder located in the <strong>src<\/strong> directory and create a <code>update.note.tsx<\/code> file. After that, open the <code>update.note.tsx<\/code> file and add the code below.<\/p>\n\n\n\n<p>The <strong>UpdateNote<\/strong> component has a form and a <strong>LoadingButton<\/strong> for submitting the form data. The form validation is handled by React-Hook-Form and the validation rules are defined by the Zod schema validation library.<\/p>\n\n\n\n<p>When the <strong>UpdateNote<\/strong> component is mounted into the DOM, the <code>.reset()<\/code> method of React-Hook-Form will be evoked by the <code>useEffect()<\/code> hook to populate the fields of the form with the note item passed as a prop.<\/p>\n\n\n\n<p><strong>src\/components\/notes\/update.note.tsx<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-tsx\"><code>\nimport { FC, useEffect, useState } from &quot;react&quot;;\nimport { SubmitHandler, useForm } from &quot;react-hook-form&quot;;\nimport { twMerge } from &quot;tailwind-merge&quot;;\nimport { object, string, TypeOf } from &quot;zod&quot;;\nimport { zodResolver } from &quot;@hookform\/resolvers\/zod&quot;;\nimport { LoadingButton } from &quot;..\/LoadingButton&quot;;\nimport { toast } from &quot;react-toastify&quot;;\nimport NProgress from &quot;nprogress&quot;;\nimport { INote, INoteResponse } from &quot;..\/..\/types&quot;;\nimport useStore from &quot;..\/..\/store&quot;;\n\nconst BASE_URL = &quot;http:\/\/localhost:8000\/api&quot;;\n\ntype IUpdateNoteProps = {\n  note: INote;\n  setOpenNoteModal: (open: boolean) =&gt; void;\n};\n\nconst updateNoteSchema = object({\n  title: string().min(1, &quot;Title is required&quot;),\n  content: string().min(1, &quot;Content is required&quot;),\n});\n\nexport type UpdateNoteInput = TypeOf&lt;typeof updateNoteSchema&gt;;\n\nconst UpdateNote: FC&lt;IUpdateNoteProps&gt; = ({ note, setOpenNoteModal }) =&gt; {\n  const [loading, setLoading] = useState(false);\n  const noteStore = useStore();\n  const methods = useForm&lt;UpdateNoteInput&gt;({\n    resolver: zodResolver(updateNoteSchema),\n  });\n\n  const {\n    register,\n    handleSubmit,\n    formState: { errors },\n  } = methods;\n\n  useEffect(() =&gt; {\n    if (note) {\n      methods.reset(note);\n    }\n    \/\/ eslint-disable-next-line react-hooks\/exhaustive-deps\n  }, []);\n\n  const updateNote = async ({\n    noteId,\n    note,\n  }: {\n    noteId: string;\n    note: UpdateNoteInput;\n  }) =&gt; {\n    try {\n      NProgress.start();\n      setLoading(true);\n      const response = await fetch(`${BASE_URL}\/notes\/${noteId}`, {\n        method: &quot;PATCH&quot;,\n        mode: &quot;cors&quot;,\n        cache: &quot;no-cache&quot;,\n        headers: {\n          &quot;Content-Type&quot;: &quot;application\/json&quot;,\n        },\n        body: JSON.stringify(note),\n      });\n      if (!response.ok) {\n        const error = await response.json();\n        throw error ? error : &quot;Something bad happended&quot;;\n      }\n      const data = (await response.json()) as INoteResponse;\n      noteStore.updateNote(data.data.note);\n      setLoading(false);\n      setOpenNoteModal(false);\n      NProgress.done();\n      toast(&quot;Note updated successfully&quot;, {\n        type: &quot;success&quot;,\n        position: &quot;top-right&quot;,\n      });\n    } catch (error: any) {\n      setLoading(false);\n      setOpenNoteModal(false);\n      NProgress.done();\n      const resMessage = error.message || error.detail || error.toString();\n      toast.error(resMessage, {\n        position: &quot;top-right&quot;,\n      });\n    }\n  };\n\n  const onSubmitHandler: SubmitHandler&lt;UpdateNoteInput&gt; = async (data) =&gt; {\n    updateNote({ noteId: note.id, note: data });\n  };\n  return (\n    &lt;section&gt;\n      &lt;div className=&quot;flex justify-between items-center mb-4&quot;&gt;\n        &lt;h2 className=&quot;text-2xl text-ct-dark-600 font-semibold&quot;&gt;Update Note&lt;\/h2&gt;\n        &lt;div\n          onClick={() =&gt; setOpenNoteModal(false)}\n          className=&quot;text-2xl text-gray-400 hover:bg-gray-200 hover:text-gray-900 rounded-lg p-1.5 ml-auto inline-flex items-center cursor-pointer&quot;\n        &gt;\n          &lt;i className=&quot;bx bx-x&quot;&gt;&lt;\/i&gt;\n        &lt;\/div&gt;\n      &lt;\/div&gt;{&quot; &quot;}\n      &lt;form className=&quot;w-full&quot; onSubmit={handleSubmit(onSubmitHandler)}&gt;\n        &lt;div className=&quot;mb-2&quot;&gt;\n          &lt;label className=&quot;block text-gray-700 text-lg mb-2&quot; htmlFor=&quot;title&quot;&gt;\n            Title\n          &lt;\/label&gt;\n          &lt;input\n            className={twMerge(\n              `appearance-none border border-gray-400 rounded w-full py-3 px-3 text-gray-700 mb-2 leading-tight focus:outline-none`,\n              `${errors[&quot;title&quot;] &amp;&amp; &quot;border-red-500&quot;}`\n            )}\n            {...methods.register(&quot;title&quot;)}\n          \/&gt;\n          &lt;p\n            className={twMerge(\n              `text-red-500 text-xs italic mb-2 invisible`,\n              `${errors[&quot;title&quot;] &amp;&amp; &quot;visible&quot;}`\n            )}\n          &gt;\n            {errors[&quot;title&quot;]?.message as string}\n          &lt;\/p&gt;\n        &lt;\/div&gt;\n        &lt;div className=&quot;mb-2&quot;&gt;\n          &lt;label className=&quot;block text-gray-700 text-lg mb-2&quot; htmlFor=&quot;title&quot;&gt;\n            Content\n          &lt;\/label&gt;\n          &lt;textarea\n            className={twMerge(\n              `appearance-none border rounded w-full py-3 px-3 text-gray-700 mb-2 leading-tight focus:outline-none`,\n              `${errors.content ? &quot;border-red-500&quot; : &quot;border-gray-400&quot;}`\n            )}\n            rows={6}\n            {...register(&quot;content&quot;)}\n          \/&gt;\n          &lt;p\n            className={twMerge(\n              `text-red-500 text-xs italic mb-2`,\n              `${errors.content ? &quot;visible&quot; : &quot;invisible&quot;}`\n            )}\n          &gt;\n            {errors.content &amp;&amp; (errors.content.message as string)}\n          &lt;\/p&gt;\n        &lt;\/div&gt;\n        &lt;LoadingButton loading={loading}&gt;Update Note&lt;\/LoadingButton&gt;\n      &lt;\/form&gt;\n    &lt;\/section&gt;\n  );\n};\n\nexport default UpdateNote;\n<\/code><\/pre>\n\n\n\n<p>After the form has been submitted and there are no validation errors, the <code>updateNote()<\/code> function will be evoked which will also call fetch API to submit the form data to the backend API.<\/p>\n\n\n\n<p>Once the backend API returns the modified data, the <code>updateNote()<\/code> action will be evoked to update that note item with the modified fields in the Zustand store.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Implement the DELETE Operation<\/h3>\n\n\n\n<p>Now that we&#8217;re able to perform the <strong>CREATE<\/strong> and <strong>UPDATE<\/strong> CRUD operations, let&#8217;s create a React component to implement the <strong>DELETE<\/strong> operation. To do that, navigate to the <strong>components\/notes<\/strong> folder in the <strong>src<\/strong> directory and create a <code>note.component.tsx<\/code> file. After that, open the <code>note.component.tsx<\/code> file and add the code snippets below. <\/p>\n\n\n\n<p>The <strong>NoteItem<\/strong> component has a button for displaying the <strong>Edit<\/strong> and <strong>Delete<\/strong> menus. When the <strong>Delete<\/strong> button is clicked, the user will be prompted to confirm the action before the <code>deleteNote()<\/code> function will be evoked to make a <strong>DELETE<\/strong> request to the backend API.<\/p>\n\n\n\n<p>The <strong>Edit<\/strong> button on the other hand will display the <strong>Update Note<\/strong> modal where the user can modify the title or content of the note item.<\/p>\n\n\n\n<p><strong>src\/components\/notes\/note.component.tsx<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-tsx\"><code>\nimport { FC, useEffect, useState } from &quot;react&quot;;\nimport { format, parseISO } from &quot;date-fns&quot;;\nimport { twMerge } from &quot;tailwind-merge&quot;;\nimport NoteModal from &quot;..\/note.modal&quot;;\nimport UpdateNote from &quot;.\/update.note&quot;;\nimport { toast } from &quot;react-toastify&quot;;\nimport NProgress from &quot;nprogress&quot;;\nimport { INote } from &quot;..\/..\/types&quot;;\nimport useStore from &quot;..\/..\/store&quot;;\n\nconst BASE_URL = &quot;http:\/\/localhost:8000\/api&quot;;\n\ntype NoteItemProps = {\n  note: INote;\n};\n\nconst NoteItem: FC&lt;NoteItemProps&gt; = ({ note }) =&gt; {\n  const noteStore = useStore();\n  const [openSettings, setOpenSettings] = useState(false);\n  const [openNoteModal, setOpenNoteModal] = useState(false);\n\n  useEffect(() =&gt; {\n    const handleOutsideClick = (event: MouseEvent) =&gt; {\n      const target = event.target as HTMLElement;\n      const dropdown = document.getElementById(`settings-dropdown-${note.id}`);\n\n      if (dropdown &amp;&amp; !dropdown.contains(target)) {\n        setOpenSettings(false);\n      }\n    };\n    document.addEventListener(&quot;mousedown&quot;, handleOutsideClick);\n\n    return () =&gt; {\n      document.removeEventListener(&quot;mousedown&quot;, handleOutsideClick);\n    };\n  }, [note.id]);\n\n  const deleteNote = async (noteId: string) =&gt; {\n    try {\n      NProgress.start();\n      const response = await fetch(`${BASE_URL}\/notes\/${noteId}`, {\n        method: &quot;DELETE&quot;,\n        mode: &quot;cors&quot;,\n      });\n      if (!response.ok) {\n        const error = await response.json();\n        throw error ? error : &quot;Something bad happended&quot;;\n      }\n\n      noteStore.deleteNote(noteId);\n      setOpenNoteModal(false);\n      NProgress.done();\n      toast(&quot;Note deleted successfully&quot;, {\n        type: &quot;warning&quot;,\n        position: &quot;top-right&quot;,\n      });\n    } catch (error: any) {\n      setOpenNoteModal(false);\n      const resMessage = error.message || error.detail || error.toString();\n      toast.error(resMessage, {\n        position: &quot;top-right&quot;,\n      });\n      NProgress.done();\n    }\n  };\n\n  const onDeleteHandler = (noteId: string) =&gt; {\n    if (window.confirm(&quot;Are you sure&quot;)) {\n      deleteNote(noteId);\n    }\n  };\n\n  return (\n    &lt;&gt;\n      &lt;div className=&quot;p-4 bg-white rounded-lg border border-gray-200 shadow-md flex flex-col justify-between overflow-hidden&quot;&gt;\n        &lt;div className=&quot;details&quot;&gt;\n          &lt;h4 className=&quot;mb-2 pb-2 text-2xl font-semibold tracking-tight text-gray-900&quot;&gt;\n            {note.title.length &gt; 40\n              ? note.title.substring(0, 40) + &quot;...&quot;\n              : note.title}\n          &lt;\/h4&gt;\n          &lt;p className=&quot;mb-3 font-normal text-ct-dark-200&quot;&gt;\n            {note.content.length &gt; 210\n              ? note.content.substring(0, 210) + &quot;...&quot;\n              : note.content}\n          &lt;\/p&gt;\n        &lt;\/div&gt;\n        &lt;div className=&quot;relative border-t border-slate-300 flex justify-between items-center&quot;&gt;\n          &lt;span className=&quot;text-ct-dark-100 text-sm&quot;&gt;\n            {format(parseISO(String(note.createdAt)), &quot;PPP&quot;)}\n          &lt;\/span&gt;\n          &lt;div\n            onClick={() =&gt; setOpenSettings(!openSettings)}\n            className=&quot;text-ct-dark-100 text-lg cursor-pointer&quot;\n          &gt;\n            &lt;i className=&quot;bx bx-dots-horizontal-rounded&quot;&gt;&lt;\/i&gt;\n          &lt;\/div&gt;\n          &lt;div\n            id={`settings-dropdown-${note.id}`}\n            className={twMerge(\n              `absolute right-0 bottom-3 z-10 w-28 text-base list-none bg-white rounded divide-y divide-gray-100 shadow`,\n              `${openSettings ? &quot;block&quot; : &quot;hidden&quot;}`\n            )}\n          &gt;\n            &lt;ul className=&quot;py-1&quot; aria-labelledby=&quot;dropdownButton&quot;&gt;\n              &lt;li\n                onClick={() =&gt; {\n                  setOpenSettings(false);\n                  setOpenNoteModal(true);\n                }}\n                className=&quot;py-2 px-4 text-sm text-gray-700 hover:bg-gray-100 cursor-pointer&quot;\n              &gt;\n                &lt;i className=&quot;bx bx-pencil&quot;&gt;&lt;\/i&gt; Edit\n              &lt;\/li&gt;\n              &lt;li\n                onClick={() =&gt; {\n                  setOpenSettings(false);\n                  onDeleteHandler(note.id);\n                }}\n                className=&quot;py-2 px-4 text-sm text-red-600 hover:bg-gray-100 cursor-pointer&quot;\n              &gt;\n                &lt;i className=&quot;bx bx-trash&quot;&gt;&lt;\/i&gt; Delete\n              &lt;\/li&gt;\n            &lt;\/ul&gt;\n          &lt;\/div&gt;\n        &lt;\/div&gt;\n      &lt;\/div&gt;\n      &lt;NoteModal\n        openNoteModal={openNoteModal}\n        setOpenNoteModal={setOpenNoteModal}\n      &gt;\n        &lt;UpdateNote note={note} setOpenNoteModal={setOpenNoteModal} \/&gt;\n      &lt;\/NoteModal&gt;\n    &lt;\/&gt;\n  );\n};\n\nexport default NoteItem;\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Implement the READ Operation<\/h3>\n\n\n\n<p>The <strong>App<\/strong> component displays a list of note items and a button for creating notes. The <code>useEffect()<\/code> hook is used to get a paginated list of notes from the backend API when the component is mounted in the DOM.<\/p>\n\n\n\n<p>When the <code>useEffect()<\/code> hook evokes the <code>getNotes()<\/code> function, Fetch API will fire a <strong>GET<\/strong> request to the <strong>http:\/\/localhost:8000\/api\/notes<\/strong> endpoint to retrieve the first 10 records from the database.<\/p>\n\n\n\n<p>After the API returns the results, the <code>setNotes()<\/code> action will be called to add the list of notes to the Zustand store.<\/p>\n\n\n\n<p><strong>src\/App.tsx<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-tsx\"><code>\nimport \"react-toastify\/dist\/ReactToastify.css\";\nimport { useEffect, useState } from \"react\";\nimport { toast, ToastContainer } from \"react-toastify\";\nimport NoteModal from \".\/components\/note.modal\";\nimport CreateNote from \".\/components\/notes\/create.note\";\nimport NoteItem from \".\/components\/notes\/note.component\";\nimport NProgress from \"nprogress\";\nimport { INotesResponse } from \".\/types\";\nimport useStore from \".\/store\";\n\nconst BASE_URL = \"http:\/\/localhost:8000\/api\";\n\nfunction AppContent() {\n  const [openNoteModal, setOpenNoteModal] = useState(false);\n  const { notes, setNotes } = useStore();\n\n  const getNotes = async ({ page, limit }: { page: number; limit: number }) =&gt; {\n    try {\n      NProgress.start();\n      const response = await fetch(\n        `${BASE_URL}\/notes?page=${page}&amp;limit=${limit}`,\n        {\n          method: \"GET\",\n          mode: \"cors\",\n        }\n      );\n      if (!response.ok) {\n        const error = await response.json();\n        throw error ? error : \"Something bad happended\";\n      }\n      const data = (await response.json()) as INotesResponse;\n      setNotes(data.notes);\n\n      NProgress.done();\n    } catch (error: any) {\n      const resMessage = error.message || error.detail || error.toString();\n      toast.error(resMessage, {\n        position: \"top-right\",\n      });\n      NProgress.done();\n    }\n  };\n\n  useEffect(() =&gt; {\n    getNotes({ page: 1, limit: 10 });\n  }, []);\n\n  return (\n    &lt;div className=\"2xl:max-w-[90rem] max-w-[68rem] mx-auto\"&gt;\n      &lt;div className=\"m-8 grid grid-cols-[repeat(auto-fill,_320px)] gap-7 grid-rows-[1fr]\"&gt;\n        &lt;div className=\"p-4 min-h-[18rem] bg-white rounded-lg border border-gray-200 shadow-md flex flex-col items-center justify-center\"&gt;\n          &lt;div\n            onClick={() =&gt; setOpenNoteModal(true)}\n            className=\"flex items-center justify-center h-20 w-20 border-2 border-dashed border-ct-blue-600 rounded-full text-ct-blue-600 text-5xl cursor-pointer\"\n          &gt;\n            &lt;i className=\"bx bx-plus\"&gt;&lt;\/i&gt;\n          &lt;\/div&gt;\n          &lt;h4\n            onClick={() =&gt; setOpenNoteModal(true)}\n            className=\"text-lg font-medium text-ct-blue-600 mt-5 cursor-pointer\"\n          &gt;\n            Add new note\n          &lt;\/h4&gt;\n        &lt;\/div&gt;\n        {\/* Note Items *\/}\n\n        {notes?.map((note) =&gt; (\n          &lt;NoteItem key={note.id} note={note} \/&gt;\n        ))}\n\n        {\/* Create Note Modal *\/}\n        &lt;NoteModal\n          openNoteModal={openNoteModal}\n          setOpenNoteModal={setOpenNoteModal}\n        &gt;\n          &lt;CreateNote setOpenNoteModal={setOpenNoteModal} \/&gt;\n        &lt;\/NoteModal&gt;\n      &lt;\/div&gt;\n    &lt;\/div&gt;\n  );\n}\n\nfunction App() {\n  return (\n    &lt;&gt;\n      &lt;AppContent \/&gt;\n      &lt;ToastContainer \/&gt;\n    &lt;\/&gt;\n  );\n}\n\nexport default App;\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Test the CRUD App<\/h2>\n\n\n\n<p>Oops, quite a lot of code. At this point, we are now ready to test the React Fetch API CRUD app with the Deno backend API. Before starting the Vite development server, make sure the Deno API is running.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">CREATE CRUD Functionality<\/h3>\n\n\n\n<p>To add a new record to the database, click on the plus (<strong>+<\/strong>) icon to display the Create Note modal popup. Provide the title and content of your note and click on the <strong>Create Note<\/strong> button.<\/p>\n\n\n\n<p>Upon clicking the <strong>Create Note<\/strong> button, Fetch API will fire an HTTP <strong>POST<\/strong> request with the form data included in the request body to the <code>\/api\/notes<\/code> endpoint.<\/p>\n\n\n\n<p>The Deno API will validate the request body against a validation schema, add the record to the database, and return the newly-created note item to React.<\/p>\n\n\n\n<p>Once the request resolves successfully, the <code>createNote()<\/code> action will be evoked to add the new note item to the Zustand store. This will cause React to re-render the DOM to display the newly-created note item in the UI.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"895\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/11\/reactjs-crud-app-with-fetch-api-create-operation-1024x895.webp\" alt=\"reactjs crud app with fetch api create operation\" class=\"wp-image-9272\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/11\/reactjs-crud-app-with-fetch-api-create-operation-1024x895.webp 1024w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/11\/reactjs-crud-app-with-fetch-api-create-operation-300x262.webp 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/11\/reactjs-crud-app-with-fetch-api-create-operation-768x672.webp 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/11\/reactjs-crud-app-with-fetch-api-create-operation-100x87.webp 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/11\/reactjs-crud-app-with-fetch-api-create-operation-515x450.webp 515w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/11\/reactjs-crud-app-with-fetch-api-create-operation.webp 1235w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">READ CRUD Functionality<\/h3>\n\n\n\n<p>When you visit the root route, Fetch API will make a <strong>GET<\/strong> request to retrieve the first 10 note items from the API. The <code>setNotes()<\/code> action will be called to update the Zustand store with the list of note items returned by the API.<\/p>\n\n\n\n<p>After the store has been updated, React will re-render the DOM to display the list of note items in the UI.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"894\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/11\/reactjs-crud-app-with-fetch-api-read-operation-1024x894.webp\" alt=\"reactjs crud app with fetch api read operation\" class=\"wp-image-9270\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/11\/reactjs-crud-app-with-fetch-api-read-operation-1024x894.webp 1024w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/11\/reactjs-crud-app-with-fetch-api-read-operation-300x262.webp 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/11\/reactjs-crud-app-with-fetch-api-read-operation-768x671.webp 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/11\/reactjs-crud-app-with-fetch-api-read-operation-100x87.webp 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/11\/reactjs-crud-app-with-fetch-api-read-operation-515x450.webp 515w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/11\/reactjs-crud-app-with-fetch-api-read-operation.webp 1237w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">UPDATE CRUD Functionality<\/h3>\n\n\n\n<p>To modify the title or content of a note item, click on the three dots (&#8230;) opposite the date element and click on the <strong>Edit<\/strong> button. Edit the title or content and click on the Update Note button.<\/p>\n\n\n\n<p>When you click on the <strong>Update Note<\/strong> button, Fetch API will make a <strong>PATCH<\/strong> request with the modified data included in the request body to the <code>\/api\/notes\/:noteId<\/code> endpoint.<\/p>\n\n\n\n<p>The backend API will then update the record that matches the provided ID in the database and return the modified record to React.<\/p>\n\n\n\n<p>Once the request resolves successfully, the <code>updateNote()<\/code> action will be evoked to update the store with the modified note item. After that, React will re-render the DOM to reflect the changes in the UI.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"893\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/11\/reactjs-crud-app-with-fetch-api-update-operation-1024x893.webp\" alt=\"reactjs crud app with fetch api update operation\" class=\"wp-image-9269\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/11\/reactjs-crud-app-with-fetch-api-update-operation-1024x893.webp 1024w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/11\/reactjs-crud-app-with-fetch-api-update-operation-300x262.webp 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/11\/reactjs-crud-app-with-fetch-api-update-operation-768x670.webp 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/11\/reactjs-crud-app-with-fetch-api-update-operation-100x87.webp 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/11\/reactjs-crud-app-with-fetch-api-update-operation-516x450.webp 516w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/11\/reactjs-crud-app-with-fetch-api-update-operation.webp 1238w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">DELETE CRUD Functionality<\/h3>\n\n\n\n<p>To delete a note item, click on the three dots again and click on the Delete button. You&#8217;ll be prompted to confirm your action or cancel it. When you click on the <strong>ok<\/strong> button, Fetch API will fire a <strong>DELETE<\/strong> request with the ID of that item to the <code>\/api\/notes\/:noteId<\/code> endpoint.<\/p>\n\n\n\n<p>The backend API will then delete the record that matches that ID from the database and return a success message to the React app.<\/p>\n\n\n\n<p>When the request resolves successfully, the <code>deleteNote()<\/code> action will be evoked to remove that note item from the Zustand store. After that, React will re-render the DOM to remove that note item from the UI.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"895\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/11\/reactjs-crud-app-with-fetch-api-delete-operation-1024x895.webp\" alt=\"reactjs crud app with fetch api delete operation\" class=\"wp-image-9271\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/11\/reactjs-crud-app-with-fetch-api-delete-operation-1024x895.webp 1024w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/11\/reactjs-crud-app-with-fetch-api-delete-operation-300x262.webp 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/11\/reactjs-crud-app-with-fetch-api-delete-operation-768x671.webp 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/11\/reactjs-crud-app-with-fetch-api-delete-operation-100x87.webp 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/11\/reactjs-crud-app-with-fetch-api-delete-operation-515x450.webp 515w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/11\/reactjs-crud-app-with-fetch-api-delete-operation.webp 1236w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Conclusion<\/h2>\n\n\n\n<p>Congrats on reaching the end. In this article, you learned how to create a React.js CRUD app with Fetch API. Also, you learned how to manage React state with the Zustand library.<\/p>\n\n\n\n<p>You can find the complete source code of the React Fetch API project on <a href=\"https:\/\/github.com\/wpcodevo\/react-crud-fetchapi-app\" target=\"_blank\" rel=\"noreferrer noopener\">GitHub<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>In this tutorial, you&#8217;ll learn how to build a React.js CRUD application with JavaScript Fetch API, tailwind CSS, and React-Hook-Form. This app will leverage Fetch&#8230;<\/p>\n","protected":false},"author":1,"featured_media":9278,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[54],"tags":[56,55],"class_list":["post-9255","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-react","tag-react","tag-reactjs"],"acf":[],"_links":{"self":[{"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/posts\/9255","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/comments?post=9255"}],"version-history":[{"count":4,"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/posts\/9255\/revisions"}],"predecessor-version":[{"id":11496,"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/posts\/9255\/revisions\/11496"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/media\/9278"}],"wp:attachment":[{"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/media?parent=9255"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/categories?post=9255"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/tags?post=9255"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}