<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ Appwrite - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ Appwrite - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Fri, 29 May 2026 23:04:22 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/appwrite/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ Build a Sticky Notes App with React and Appwrite ]]>
                </title>
                <description>
                    <![CDATA[ Are you ready to dive into an exciting project that combines the latest in frontend and backend technologies? Imagine having a personalized notes application with dynamic features like draggable notes, autosave, and customizable colors. Not only will... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/build-a-sticky-notes-app-with-react-and-appwrite/</link>
                <guid isPermaLink="false">66a1aab18766a9932025645e</guid>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Appwrite ]]>
                    </category>
                
                    <category>
                        <![CDATA[ youtube ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Beau Carnes ]]>
                </dc:creator>
                <pubDate>Thu, 25 Jul 2024 01:30:25 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1721870980147/977bfdd5-fdef-4cfc-bfa3-97c614faa8b9.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Are you ready to dive into an exciting project that combines the latest in frontend and backend technologies? Imagine having a personalized notes application with dynamic features like draggable notes, autosave, and customizable colors. Not only will you use these cool features, but you will also learn how to build them from scratch using React and Appwrite.</p>
<p>We just published a course on the <a target="_blank" href="http://freeCodeCamp.org">freeCodeCamp.org</a> YouTube channel that will teach you all about building a fullstack notes application with React JS and Appwrite. This comprehensive course walks you through the entire process of creating a production-ready notes app, complete with a robust set of features that make it both functional and fun to use. Whether you're a beginner looking to expand your skills or an experienced developer seeking to refine your expertise, this course is designed to cater to all levels.</p>
<h3 id="heading-course-features">Course Features</h3>
<p>The frontend of our notes application is built using React JS, a popular JavaScript library for building user interfaces. React allows you to create dynamic and responsive web applications with ease. In this course, you will learn how to leverage React’s component-based architecture to build interactive and efficient user interfaces.</p>
<p>On the backend, you will utilize Appwrite, an open-source backend-as-a-service (BaaS) platform that simplifies backend development. Appwrite provides a range of backend services such as databases, authentication, and storage, allowing you to focus on building your application without worrying about infrastructure management.</p>
<p>Here are some key features of the application:</p>
<ol>
<li><p><strong>Production Database</strong>:</p>
<ul>
<li>All note data is stored on a live production-ready database provided by Appwrite. This ensures your notes are securely saved and easily accessible from anywhere.</li>
</ul>
</li>
<li><p><strong>Draggable Notes</strong>:</p>
<ul>
<li>Implement draggable notes functionality that allows users to drag and drop notes anywhere on the screen. This feature enhances the user experience by offering flexibility in organizing notes.</li>
</ul>
</li>
<li><p><strong>Autosave Changes</strong>:</p>
<ul>
<li>Changes to note content and position are automatically saved. This feature ensures that users don’t lose any changes and eliminates the need for manual saving.</li>
</ul>
</li>
<li><p><strong>Color Picker</strong>:</p>
<ul>
<li>Add a color picker that enables users to change note colors at any time. This customization feature allows users to visually distinguish and organize their notes better.</li>
</ul>
</li>
</ol>
<h3 id="heading-conclusion">Conclusion</h3>
<p>By the end of this course, you will have a fully functional notes application that you can use personally or showcase as a part of your portfolio. Additionally, you will gain valuable experience in working with React and Appwrite, equipping you with the skills to tackle other fullstack projects.</p>
<p>Ready to build the coolest notes app ever? Watch the full <a target="_blank" href="https://youtu.be/yBThHM2pBbE">course on the freeCodeCamp.org YouTube channel</a> (2-hour watch).</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/yBThHM2pBbE" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build an AI-enhanced Task App with React and Appwrite ]]>
                </title>
                <description>
                    <![CDATA[ In this article, you'll build a task manager application that has some artificial intelligence capabilities and is voice-enabled, sortable, and searchable.  As an extra, the application will have dark mode support that respects the users' system pref... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-build-a-task-app/</link>
                <guid isPermaLink="false">66b999a9d9d170feecefbbc5</guid>
                
                    <category>
                        <![CDATA[ AI ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Appwrite ]]>
                    </category>
                
                    <category>
                        <![CDATA[ crud ]]>
                    </category>
                
                    <category>
                        <![CDATA[ projects ]]>
                    </category>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Fatuma Abdullahi ]]>
                </dc:creator>
                <pubDate>Wed, 13 Mar 2024 09:21:45 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2024/03/Group-3--20-.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In this article, you'll build a task manager application that has some artificial intelligence capabilities and is voice-enabled, sortable, and searchable. </p>
<p>As an extra, the application will have dark mode support that respects the users' system preferences.</p>
<p>The application will be able to create, read, update and delete (CRUD) tasks as well as the ability to view a given task.  </p>
<p>You'll build this application using Appwrite as a backend, React on the frontend, Typescript for type safety and Tailwind CSS for styling.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
    <li><a href="#prerequisites">Prerequisites</a></li>
    <li><a href="#what-is-appwrite">What is Appwrite?</a></li>
    <li><a href="#how-to-set-up-the-appwrite-backend">How to Set Up the Appwrite Backend</a></li>
    <li><a href="#how-to-set-up-the-react-frontend">How to Set up the React Frontend</a></li>
    <li><a href="#how-to-connect-to-the-appwrite-project">How to Connect to the Appwrite Project</a></li>
    <li><a href="#how-to-build-the-task-manager-application">How to Build the Task Manager Application</a>
        <ul>
            <li><a href="#how-to-set-up-routing-with-react-router-v6">How to Set up Routing with React Router V6</a></li>
            <li><a href="#how-to-create-the-form-component">How to Create the Form Component</a></li>
            <li><a href="#how-to-set-up-form-to-create-task">How to Set up Form to Create Task</a></li>
            <li><a href="#how-to-make-the-tasks-editable">How to Make the Tasks Editable</a></li>
            <li><a href="#how-to-enable-viewing-of-tasks">How to Enable Viewing of Tasks</a></li>
            <li><a href="#how-to-auto-generate-descriptions-with-vercel-s-ai-sdk">How to Auto Generate Descriptions with Vercel's AI SDK</a></li>
            <li><a href="#voice-enable-the-application-with-the-react-speech-recognition-package">Voice-enable the Application with the React Speech Recognition Package</a></li>
            <li><a href="#how-to-add-search-functionality-to-the-application">How to Add Search Functionality to the Application</a></li>
            <li><a href="#how-to-add-ability-to-sort-tasks-via-due-date-and-priorityadd-ability-to-sort-tasks-via-due-date-and-priority">How to Add Ability to Sort Tasks via Due Date and Priority</a></li>
            <li><a href="#bonus-add-dark-mode-support">Bonus: Add Dark Mode Support</a></li>

        </ul>
    </li>
    <li><a href="#notes">Notes</a></li>
    <li><a href="#limitations">Limitations</a></li>
</ul>



<h2 id="heading-prerequisitesheading-prerequisites-1"><a class="post-section-overview" href="#heading-prerequisites-1">Prerequisites</a></h2>
<p>You will need the following to be able to build along with this article:</p>
<ul>
<li>Basic programming knowledge</li>
<li>Basic understanding of React, Typescript and Tailwind</li>
<li><a target="_blank" href="https://appwrite.io/">An Appwrite account</a></li>
<li>And a text editor to code along</li>
</ul>
<h2 id="heading-what-is-appwrite">What is Appwrite?</h2>
<p>Appwrite is an <a target="_blank" href="https://opensource.com/resources/what-open-source">open source</a> Backend-as-a-Service (BaaS) platform. A BaaS is a cloud service that packages backend tasks that are typically needed for most applications. </p>
<p>Appwrite offers both a managed database, authentication, functions and storage services and the ability to self-host the entire platform on your own. </p>
<p>Appwrite recently announced a host of new features that makes developers building on their platform lives more straightforward. You can read on that <a target="_blank" href="https://appwrite.io/init">here</a>. </p>
<h2 id="heading-how-to-set-up-the-appwrite-backend">How to Set Up the Appwrite Backend</h2>
<p>Before starting to build the application and interacting with Appwrite, you'll need an Appwrite account and to set up the project.</p>
<p>Once you have the account ready, you will need to create an organization, then create a project within that organization. You can name the project "Tasks App" or any other name you see fit. </p>
<p><strong>Note:</strong> Appwrite cloud restricts you to one organization per account on the hobby/free plan. If you already had an organization, you can go straight to creating a project within your existing organization.</p>
<p>In your Tasks App project, add a web platform and follow the prompts. For the hostname, add "localhost" for now. This is to allow the frontend to bypass <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS">CORS</a> when interacting with the Appwrite backend.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/03/Screenshot-2024-03-08-at-14.44.11.png" alt="Image" width="600" height="400" loading="lazy">
<em>A picture of the Appwrite Console showing the "Add a platform" section</em></p>
<p>Copy the installation instructions as you complete setting up the web platform for the project. Keep these safe, you will need them when setting up the frontend. </p>
<p>You should now be in the Appwrite cloud console. Click on the "Databases" on the left sidebar. Then click on the pink "Create database" button. Name your database and leave the autogenerated ID as is.  </p>
<p>Now, click on the "Create collection" button, name your collection "tasks" and leave the autogenerated ID as is. Now, click on the grey "Create attribute" button as shown below:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/03/Screenshot-2024-03-08-at-15.00.42.png" alt="Image" width="600" height="400" loading="lazy">
<em>A picture of the Appwrite Console showing the "Create attribute" button</em></p>
<p>Add the following attributes: </p>
<ul>
<li>title of type String, give it a size of 49 and make it required</li>
<li>description of type String, give it a size of 200</li>
<li>due_date of type Datetime, make it required</li>
<li>done of type Boolean, give it a default of False</li>
<li>priority of type String, give it a size of 10</li>
</ul>
<p>Finally, you need to set permissions in order for your React frontend to interact with Appwrite services. In this case, allow any one to have access. This is not ideal for production and you can read more about Appwrite permissions <a target="_blank" href="https://appwrite.io/docs/advanced/platform/permissions">here</a>.</p>
<p>Go to the console, click on databases, then your task database and then your tasks collection, then click on settings and scroll down to permissions. Add permissions for "Any" role and give them full CRUD access.</p>
<p>You are now ready to start setting up the frontend and to connect it to the Appwrite project you just completed prepping.</p>
<h2 id="heading-how-to-set-up-the-react-frontend">How to Set up the React Frontend</h2>
<p>Open your text editor to your preferred location. Then open the integrated terminal and run the following command to create a Vite-based application: </p>
<pre><code class="lang-terminal">
//taskwrite is the name of the application
npm create vite@latest taskwrite
</code></pre>
<p>Choose React and then plain Typescript when prompted. This will create a React application with Typescript already set up for you. </p>
<p>Change folders into the newly created "taskwrite" one by running <code>cd taskwrite</code> from the terminal. Run the following command in the same terminal window to add Tailwind to the application:</p>
<pre><code class="lang-terminal">
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
</code></pre>
<p>Then in your <strong>tailwind.config.js</strong> file which is in the root of the Taskwrite application, replace the "content" key with <code>content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],</code>. The file should look like this:</p>
<pre><code class="lang-javascript">
<span class="hljs-comment">/** <span class="hljs-doctag">@type <span class="hljs-type">{import('tailwindcss').Config}</span> </span>*/</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> {
    <span class="hljs-attr">content</span>: [<span class="hljs-string">"./index.html"</span>, <span class="hljs-string">"./src/**/*.{js,ts,jsx,tsx}"</span>],
    <span class="hljs-attr">theme</span>: {
        <span class="hljs-attr">extend</span>: {},
    },
    <span class="hljs-attr">plugins</span>: [],
};
</code></pre>
<p>This tells Tailwind that it should look for its classes in the index.html file at the root and in files within the <strong>src</strong> folder that end with either <code>.js</code>, <code>.ts</code>, <code>.jsx</code> or <code>.tsx</code> extensions.</p>
<p>Then open the <strong>src</strong> folder and delete the "App.css" file. Open the <strong>index.css</strong> file and replace its contents with the following: </p>
<pre><code class="lang-css">
<span class="hljs-keyword">@import</span> url(<span class="hljs-string">'https://fonts.googleapis.com/css2?family=Inter:wght@100..900&amp;family=Quicksand:wght@300..700&amp;display=swap'</span>);
<span class="hljs-keyword">@tailwind</span> base;
<span class="hljs-keyword">@tailwind</span> components;
<span class="hljs-keyword">@tailwind</span> utilities;

<span class="hljs-keyword">@layer</span> base {
    <span class="hljs-selector-pseudo">:root</span>{
        <span class="hljs-attribute">--base-bg</span>: <span class="hljs-number">#ffffff</span>;
        <span class="hljs-attribute">--btn-bg-primary</span>: <span class="hljs-number">#be185d</span>;
        <span class="hljs-attribute">--btn-bg-primary-hover</span>: <span class="hljs-number">#9d174d</span>;
        <span class="hljs-attribute">--btn-icon-main</span>: <span class="hljs-number">#1e293b</span>;
        <span class="hljs-attribute">--btn-bg-ok</span>: <span class="hljs-number">#4ade80</span>;
        <span class="hljs-attribute">--btn-bg-light-ok</span>: <span class="hljs-number">#bbf7d0</span>;
        <span class="hljs-attribute">--btn-bg-light</span>: <span class="hljs-number">#e5e7eb</span>;
        <span class="hljs-attribute">--low-priority</span>: <span class="hljs-number">#facc15</span>;
        <span class="hljs-attribute">--medium-priority</span>: <span class="hljs-number">#fb923c</span>;
        <span class="hljs-attribute">--high-priority</span>: <span class="hljs-number">#f87171</span>;
        <span class="hljs-attribute">--text-error</span>: <span class="hljs-number">#dc2626</span>;
        <span class="hljs-attribute">--text-ok</span>: <span class="hljs-number">#16a34a</span>;
        <span class="hljs-attribute">--text-main</span>: <span class="hljs-number">#262626</span>;
        <span class="hljs-attribute">--border-container</span>: <span class="hljs-number">#9ca3af</span>;
        <span class="hljs-attribute">--border-input</span>: <span class="hljs-number">#1e293b</span>;
        <span class="hljs-attribute">--border-error</span>: <span class="hljs-number">#dc2626</span>;
    }

    <span class="hljs-selector-tag">body</span>{
        <span class="hljs-attribute">background-color</span>: <span class="hljs-built_in">var</span>(--base-bg);
        <span class="hljs-attribute">color</span>: <span class="hljs-built_in">var</span>(--text-main);    
    }

    <span class="hljs-selector-id">#date</span><span class="hljs-selector-pseudo">::-webkit-calendar-picker-indicator</span> {
        <span class="hljs-attribute">background-color</span>: <span class="hljs-built_in">var</span>(--btn-bg-light); 
    }
}
</code></pre>
<p>This adds some custom css variables to the application. The variables map to Tailwind colors. </p>
<p>Next, paste the following into the <strong>tailwind.config.js</strong> at the root of the application:</p>
<pre><code class="lang-typescript">
<span class="hljs-comment">/** @type {import('tailwindcss').Config} */</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> {
    content: [<span class="hljs-string">"./index.html"</span>, <span class="hljs-string">"./src/**/*.{js,ts,jsx,tsx}"</span>],
    theme: {
        extend: {
            textColor: {
                error: <span class="hljs-string">"var(--text-error)"</span>,
                ok: <span class="hljs-string">"var(--text-ok)"</span>,
                main: <span class="hljs-string">"var(--text-main)"</span>,
                iconColor: <span class="hljs-string">"var(--btn-icon-main)"</span>,
            },
            backgroundColor: {
                base: <span class="hljs-string">"var(--base-bg)"</span>,
                primary: <span class="hljs-string">"var(--btn-bg-primary)"</span>,
                primaryHover: <span class="hljs-string">"var(--btn-bg-primary-hover)"</span>,
                ok: <span class="hljs-string">"var(--btn-bg-ok)"</span>,
                lightOk: <span class="hljs-string">"var(--btn-bg-light-ok)"</span>,
                light: <span class="hljs-string">"var(--btn-bg-light)"</span>,
                lowPriority: <span class="hljs-string">"var(--low-priority)"</span>,
                mediumPriority: <span class="hljs-string">"var(--medium-priority)"</span>,
                highPriority: <span class="hljs-string">"var(--high-priority)"</span>,
            },
            borderColor: {
                container: <span class="hljs-string">"var(--border-container)"</span>,
                input: <span class="hljs-string">"var(--border-input)"</span>,
                error: <span class="hljs-string">"var(--border-error)"</span>,
            },
        },
    },
    plugins: [],
};
</code></pre>
<p>This ties the CSS variables to the tailwind config and makes them available to use in our application.</p>
<p>Now Taskwrite is set up with React, Typescript and Tailwind.</p>
<h2 id="heading-how-to-connect-to-the-appwrite-project">How to Connect to the Appwrite Project</h2>
<p>Firstly, you need to add the Appwrite dependency to the React application. Run the following command in the terminal window to do that: <code>npm i appwrite</code>.</p>
<p>Next thing is to set up the Appwrite keys we need as environment variables. In the <strong>.gitignore</strong> file at the root of the application, add <code>*.env</code> at the top of the file then save. This will ensure that the <strong>.env</strong> file you'll create is not added to version control. </p>
<p>Now, create a <strong>.env</strong> file at the root of the React application and paste the following variables in it:</p>
<pre><code class="lang-env">
//replace the right hand side of the equal sign with the correct values from your Appwrite project.
VITE_APPWRITE_URL=YOUR-APPWRITE-API-ENDPOINT
VITE_APPWRITE_PROJ_ID=YOUR-APPWRITE-PROJECT-ID
</code></pre>
<p>You can get the necessary values in your Appwrite console. Click on the settings tab at the bottom of the left sidebar and copy API credentials. </p>
<p>Next, create a utils folder in the <strong>src</strong> folder of the React application. Add a file called <strong>appwrite.ts</strong> within it and paste the following config information:</p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">import</span> { Client, Databases } <span class="hljs-keyword">from</span> <span class="hljs-string">"appwrite"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> client = <span class="hljs-keyword">new</span> Client();

client
    .setEndpoint(<span class="hljs-keyword">import</span>.meta.env.VITE_APPWRITE_URL)
    .setProject(<span class="hljs-keyword">import</span>.meta.env.VITE_APPWRITE_PROJ_ID);

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> databases = <span class="hljs-keyword">new</span> Databases(client);

<span class="hljs-keyword">export</span> { ID } <span class="hljs-keyword">from</span> <span class="hljs-string">"appwrite"</span>;
</code></pre>
<p>You are ready to test that the React application is connected to the Appwrite project. Replace everything in the <strong>App.tsx</strong> file within the <strong>src</strong> folder with the following code:</p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">import</span> { client } <span class="hljs-keyword">from</span> <span class="hljs-string">"./utils/appwrite"</span>;

<span class="hljs-keyword">const</span> App = <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Appwrite"</span>, client);
    <span class="hljs-keyword">return</span> &lt;div className=<span class="hljs-string">"text-purple-500 text-center font-bold text-      5xl"</span>&gt;App&lt;/div&gt;;
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> App;
</code></pre>
<p>Then open an integrated terminal window and run the following command: <code>npm run dev</code>. This will run your React application at this URL: <a target="_blank" href="http://localhost:5173/">http://localhost:5173/</a>. Open the URL in a browser window and open the browser console. </p>
<p>You should see a large purple text "App" in the center of the screen and the Appwrite client logged in the console like so: </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/03/Screenshot-2024-03-08-at-16.36.36.png" alt="Image" width="600" height="400" loading="lazy">
<em>The web app running in the browser</em></p>
<p>Now you need to grab the database ID and the collection ID from the Appwrite console. Click on the databases tab on the left sidebar, hover on the database ID value and click to copy it. </p>
<p>Go back to your <strong>.env</strong> file and add an entry like so:</p>
<pre><code class="lang-env">
//replace the right hand side of the equal sign with the correct values from your Appwrite project.
VITE_APPWRITE_URL=YOUR-APPWRITE-API-ENDPOINT
VITE_APPWRITE_PROJ_ID=YOUR-APPWRITE-PROJECT-ID
//new entry below
VITE_APPWRITE_DB_ID=YOUR-APPWRITE-DB-ID
</code></pre>
<p>Lastly, go back to the console and click through the database to get to the collections. Hover and copy collection ID like before, then add it just below the database ID in your env file like so: </p>
<pre><code class="lang-env">
VITE_APPWRITE_DB_ID=YOUR-APPWRITE-DB-ID
VITE_APPWRITE_COLLECTION_ID=YOUR-APPWRITE-COLLECTION-ID
</code></pre>
<p>With that, the set up part of building Taskwrite is complete.</p>
<h2 id="heading-how-to-build-the-task-manager-application">How to Build the Task Manager Application</h2>
<p>To make it easier to work with Typescript, you will need to add interfaces that correspond with the shape of the Appwrite database response. </p>
<p>In your <strong>src</strong> folder, create a folder called <strong>models</strong> and in it, create a file called <strong>interface.ts</strong>. Paste the following in the file:</p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">import</span> { Models } <span class="hljs-keyword">from</span> <span class="hljs-string">"appwrite"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> IPayload {
    title: <span class="hljs-built_in">string</span>;
    description: <span class="hljs-built_in">string</span>;
    due_date: <span class="hljs-built_in">Date</span>;
    priority?: <span class="hljs-built_in">string</span>;
    done?: <span class="hljs-built_in">boolean</span>;
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> ITask <span class="hljs-keyword">extends</span> Models.Document {
    title: <span class="hljs-built_in">string</span>;
    description: <span class="hljs-built_in">string</span>;
    due_date: <span class="hljs-built_in">Date</span>;
    priority?: <span class="hljs-built_in">string</span>;
    done: <span class="hljs-built_in">boolean</span>;
}
</code></pre>
<p>Here, you are defining an interface called "IPayload" with the same attributes as the task we defined in the Appwrite project. Then you are defining another interface called "ITask" that extends the built in base Model from Appwrite. </p>
<p>This means that ITask has both the attributes of the task we defined before and the built in base attributes that Appwrite collections come with.</p>
<p>Next, in your <strong>utils</strong> folder add a file called <strong>db.ts</strong> and paste the following in it:</p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">import</span> { ID, databases } <span class="hljs-keyword">from</span> <span class="hljs-string">"./appwrite"</span>;
<span class="hljs-keyword">import</span> { IPayload } <span class="hljs-keyword">from</span> <span class="hljs-string">"../models/interface"</span>;

<span class="hljs-keyword">const</span> dbID: <span class="hljs-built_in">string</span> = <span class="hljs-keyword">import</span>.meta.env.VITE_APPWRITE_DB_ID;
<span class="hljs-keyword">const</span> collectionID: <span class="hljs-built_in">string</span> = <span class="hljs-keyword">import</span>.meta.env.VITE_APPWRITE_COLLECTION_ID;

<span class="hljs-keyword">const</span> createDocument = <span class="hljs-keyword">async</span> (payload: IPayload) =&gt; {
    <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> databases.createDocument(dbID, collectionID, ID.unique(), {
        ...payload,
    });

    <span class="hljs-keyword">return</span> res;
};

<span class="hljs-keyword">const</span> readDocuments = <span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> databases.listDocuments(dbID, collectionID);

    <span class="hljs-keyword">return</span> res;
};

<span class="hljs-keyword">const</span> updateDocument = <span class="hljs-keyword">async</span> (payload: IPayload, id: <span class="hljs-built_in">string</span>) =&gt; {
    <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> databases.updateDocument(dbID, collectionID, id, {
        ...payload,
    });

    <span class="hljs-keyword">return</span> res;
};
<span class="hljs-keyword">const</span> deleteDocument = <span class="hljs-keyword">async</span> (id: <span class="hljs-built_in">string</span>) =&gt; {
    <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> databases.deleteDocument(dbID, collectionID, id);

    <span class="hljs-keyword">return</span> res;
};

<span class="hljs-keyword">export</span> { createDocument, readDocuments, updateDocument, deleteDocument };
</code></pre>
<p>This file defines four functions corresponding with the CRUD operations. The naming of the functions map to which operation it performs. For all functions, you pass the collection and database IDs so that Appwrite knows which resources to operate on.</p>
<p>To create a task on the Appwrite database, you pass an object with the shape of a task to the function and ask it to create a unique ID for each new task it creates. </p>
<p>To update a task, you pass it a task object similar to create but we also pass it the unique ID of the task to be updated.</p>
<p>To read all tasks from the Appwrite, you call the "listDocuments" function and to delete a task you pass the ID corresponding to the task to be deleted.</p>
<h3 id="heading-how-to-set-up-routing-with-react-router-v6">How to Set Up Routing with React Router V6</h3>
<p>The Taskwrite application will have two routes and a navigation menu to help with that. To add navigation, open an integrated terminal and run the following command to install the React Router library: <code>npm i react-router-dom</code>.</p>
<p>Now, go to the <strong>main.tsx</strong> file in the <strong>src</strong> folder and paste the following in it: </p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> ReactDOM <span class="hljs-keyword">from</span> <span class="hljs-string">"react-dom/client"</span>;
<span class="hljs-keyword">import</span> { BrowserRouter } <span class="hljs-keyword">from</span> <span class="hljs-string">"react-router-dom"</span>;
<span class="hljs-keyword">import</span> App <span class="hljs-keyword">from</span> <span class="hljs-string">"./App.tsx"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"./index.css"</span>;

ReactDOM.createRoot(<span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">"root"</span>)!).render(
    &lt;React.StrictMode&gt;
        &lt;BrowserRouter&gt;
            &lt;App /&gt;
        &lt;/BrowserRouter&gt;
    &lt;/React.StrictMode&gt;
);
</code></pre>
<p>Then go to the <strong>App.tsx</strong> file in <strong>src</strong> folder and paste the following in it:</p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">import</span> <span class="hljs-string">"./index.css"</span>;
<span class="hljs-keyword">import</span> { Route, Routes } <span class="hljs-keyword">from</span> <span class="hljs-string">"react-router-dom"</span>;
<span class="hljs-keyword">import</span> Task <span class="hljs-keyword">from</span> <span class="hljs-string">"./routes/Task"</span>;
<span class="hljs-keyword">import</span> Index <span class="hljs-keyword">from</span> <span class="hljs-string">"./routes/Index"</span>;
<span class="hljs-keyword">import</span> Navbar <span class="hljs-keyword">from</span> <span class="hljs-string">"./components/Navbar"</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">App</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">return</span> (
        &lt;&gt;
            &lt;Navbar/&gt;
            &lt;Routes&gt;
                &lt;Route path=<span class="hljs-string">"/"</span> element={&lt;Index /&gt;} /&gt;
                &lt;Route path=<span class="hljs-string">"/tasks"</span> element={&lt;Task /&gt;} /&gt;
            &lt;/Routes&gt;
        &lt;/&gt;
    );
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> App;
</code></pre>
<p>You now need to create the referenced component above. In the <strong>src</strong> folder, create a folder called <strong>routes</strong> and within it create two files called <strong>Index.tsx</strong> and <strong>Task.tsx</strong>.  </p>
<p>In <strong>Index.tsx</strong>, paste the following: </p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">const</span> Index = <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">return</span> (
        &lt;main className=<span class="hljs-string">"container mx-auto"</span>&gt;
            &lt;section className=<span class="hljs-string">"max-w-5xl mx-auto m-12 p-16"</span>&gt;
                &lt;h1 className=<span class="hljs-string">"text-4xl md:text-7xl font-bold text-center py-3 mb-16"</span>&gt;
                    AI-enhanced, Voice-enabled, Searchable Task Manager
                &lt;/h1&gt;
            &lt;/section&gt;
        &lt;/main&gt;
    );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Index;
</code></pre>
<p>And in <strong>Task.tsx</strong>, paste the following:</p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">const</span> Task = <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">return</span> (
        &lt;main className=<span class="hljs-string">"container mx-auto"</span>&gt;
            &lt;section className=<span class="hljs-string">"max-w-5xl mx-auto m-12 p-16"</span>&gt;
                &lt;h1 className=<span class="hljs-string">"text-4xl md:text-7xl font-bold text-center py-3 mb-16"</span>&gt;
                    Your Tasks
                &lt;/h1&gt;
            &lt;/section&gt;
        &lt;/main&gt;
    );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Task;
</code></pre>
<p>Now, create a components folder in the <strong>src</strong> folder and add a file within it called <strong>Navbar.tsx</strong>. Paste the following in that file: </p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">import</span> { Link, useNavigate } <span class="hljs-keyword">from</span> <span class="hljs-string">"react-router-dom"</span>;
<span class="hljs-keyword">import</span> { PencilIcon } <span class="hljs-keyword">from</span> <span class="hljs-string">"@heroicons/react/24/solid"</span>;
<span class="hljs-keyword">import</span> Button <span class="hljs-keyword">from</span> <span class="hljs-string">"./Button"</span>;

<span class="hljs-keyword">const</span> Navbar = <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> navigate = useNavigate();

    <span class="hljs-keyword">return</span> (
        &lt;nav className=<span class="hljs-string">"py-4 border-b-2 border-container shadow-md shadow-gray-400 w-full fixed top-0 bg-base"</span>&gt;
            &lt;ul className=<span class="hljs-string">"flex items-center justify-between  w-11/12 mx-auto"</span>&gt;
                &lt;Link to=<span class="hljs-string">"/"</span>&gt;
                    &lt;Button
                        content={{
                            text: <span class="hljs-string">"Taskwrite"</span>,
                            icon: PencilIcon,
                        }}
                        textClasses=<span class="hljs-string">"font-semibold text-main"</span>
                        iconClasses=<span class="hljs-string">"text-main"</span>
                    /&gt;
                &lt;/Link&gt;
                &lt;/Link&gt;
                &lt;div className=<span class="hljs-string">"flex items-center justify-between gap-6"</span>&gt;
                    &lt;Link
                        to=<span class="hljs-string">"/tasks"</span>
                        className=<span class="hljs-string">"font-semibold hover:scale-105 transition duration-300 ease-in-out"</span>
                    &gt;
                        View Tasks
                    &lt;/Link&gt;
                &lt;/div&gt;
            &lt;/ul&gt;
        &lt;/nav&gt;
    );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Navbar;
</code></pre>
<p>This file contains navigation menu that switches between the two pages. You will need to create the <code>Button</code> component referenced above and add the Hero icons package.   </p>
<p>In an integrated terminal, run the following to add Hero icons: <code>npm i @heroicons/react</code> . Next, add a new file called <strong>Button.tsx</strong> in the components folder. Paste the following within that file:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { ReactNode } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;

<span class="hljs-keyword">interface</span> ButtonProps {
    extraBtnClasses?: <span class="hljs-built_in">string</span>;
    textColor?: <span class="hljs-built_in">string</span>;
    handleClick?: <span class="hljs-function">(<span class="hljs-params">e: React.MouseEvent&lt;HTMLButtonElement&gt;</span>) =&gt;</span> <span class="hljs-built_in">void</span>;
    title?: <span class="hljs-built_in">string</span>;
    disable?: <span class="hljs-built_in">boolean</span>;
    <span class="hljs-keyword">type</span>?: <span class="hljs-string">"button"</span> | <span class="hljs-string">"submit"</span> | <span class="hljs-string">"reset"</span>;
    children: ReactNode;
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Button</span>(<span class="hljs-params">{
    extraBtnClasses,
    textColor,
    handleClick,
    title,
    disable,
    <span class="hljs-keyword">type</span> = "button",
    children,
}: ButtonProps</span>) </span>{
    <span class="hljs-keyword">const</span> handleClickProp = <span class="hljs-keyword">type</span> === <span class="hljs-string">"submit"</span> ? <span class="hljs-literal">undefined</span> : handleClick;

    <span class="hljs-keyword">return</span> (
        &lt;button
            <span class="hljs-keyword">type</span>={<span class="hljs-keyword">type</span>}
            title={title ?? <span class="hljs-string">""</span>}
            onClick={handleClickProp}
            disabled={disable}
            className={<span class="hljs-string">`flex gap-2 items-center text-iconColor <span class="hljs-subst">${extraBtnClasses}</span> <span class="hljs-subst">${
                textColor ?? <span class="hljs-string">""</span>
            }</span> rounded-md px-2 py-1 hover:scale-105 transition duration-300 ease-in-out`</span>}
        &gt;
            {children}
        &lt;/button&gt;
    );
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Button;
</code></pre>
<p>This file describes a shared button component and defines the props that it will accept.</p>
<p>Go back and fix any import errors and re-run the application by running <code>npm run dev</code>, you should see something like this: </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/03/Screenshot-2024-03-08-at-18.23.45.png" alt="Image" width="600" height="400" loading="lazy">
<em>app running with npm run dev command</em></p>
<h3 id="heading-how-to-create-the-form-component">How to Create the Form Component</h3>
<p>Add a new file called <strong>AddTask.tsx</strong> in the components folder and paste the following into it:</p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">import</span> { useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> Select <span class="hljs-keyword">from</span> <span class="hljs-string">"./Select"</span>;
<span class="hljs-keyword">import</span> Button <span class="hljs-keyword">from</span> <span class="hljs-string">"./Button"</span>;

<span class="hljs-keyword">const</span> AddTask = <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> [titleVal, setTitleVal] = useState(<span class="hljs-string">""</span>);
    <span class="hljs-keyword">const</span> [textAreaVal, setTextAreaVal] = useState(<span class="hljs-string">""</span>);
    <span class="hljs-keyword">const</span> [dueDate, setDueDate] = useState(<span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>());

    <span class="hljs-keyword">const</span> priorityArray = [<span class="hljs-string">"low"</span>, <span class="hljs-string">"medium"</span>, <span class="hljs-string">"high"</span>];

    <span class="hljs-keyword">const</span> [priority, setPriority] = useState(priorityArray[<span class="hljs-number">0</span>]);

    <span class="hljs-keyword">return</span> (
        &lt;form id=<span class="hljs-string">"form"</span> className=<span class="hljs-string">"m-8"</span>&gt;
            &lt;div className=<span class="hljs-string">"flex flex-col mb-6"</span>&gt;
                &lt;label htmlFor=<span class="hljs-string">"title"</span>&gt;Task Title&lt;/label&gt;
                &lt;input
                    <span class="hljs-keyword">type</span>=<span class="hljs-string">"text"</span>
                    id=<span class="hljs-string">"title"</span>
                    placeholder=<span class="hljs-string">"Title of your task"</span>
                    value={titleVal}
                    onChange={<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> setTitleVal(e.target.value)}
                    className=<span class="hljs-string">"bg-inherit border rounded-sm p-2 focus:outline-none focus:ring-1 border-input focus:ring-slate-900"</span>
                /&gt;
            &lt;/div&gt;
            &lt;div className=<span class="hljs-string">"flex flex-col mb-6"</span>&gt;
                &lt;label htmlFor=<span class="hljs-string">"description"</span> className=<span class="hljs-string">"mb-1"</span>&gt;
                    Task Description
                &lt;/label&gt;
                &lt;textarea
                    id=<span class="hljs-string">"description"</span>
                    placeholder=<span class="hljs-string">"Describe your task"</span>
                    maxLength={<span class="hljs-number">200</span>}
                    value={textAreaVal}
                    onChange={<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> setTextAreaVal(e.target.value)}
                    className=<span class="hljs-string">"bg-inherit border rounded-sm p-2 h-32 resize-none focus:outline-none focus:ring-1 border-input focus:ring-slate-900"</span>
                /&gt;
            &lt;/div&gt;
            &lt;div className=<span class="hljs-string">"flex flex-col mb-6"</span>&gt;
                &lt;label htmlFor=<span class="hljs-string">"description"</span> className=<span class="hljs-string">"mb-1"</span>&gt;
                    Task Priority
                &lt;/label&gt;
                &lt;Select
                    defaultSelectValue={priority}
                    selectOptions={priorityArray}
                    handleSelectChange={<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> setPriority(e.target.value)}
                /&gt;
            &lt;/div&gt;
            &lt;div className=<span class="hljs-string">"flex flex-col mb-6"</span>&gt;
                &lt;label htmlFor=<span class="hljs-string">"description"</span> className=<span class="hljs-string">"mb-1"</span>&gt;
                    Task Due <span class="hljs-built_in">Date</span>
                &lt;/label&gt;
                &lt;input
                    <span class="hljs-keyword">type</span>=<span class="hljs-string">"date"</span>
                    id=<span class="hljs-string">"date"</span>
                    value={dueDate!.toISOString().split(<span class="hljs-string">"T"</span>)[<span class="hljs-number">0</span>]}
                    min={<span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>().toISOString().split(<span class="hljs-string">"T"</span>)[<span class="hljs-number">0</span>]}
                    onChange={<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> setDueDate(<span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(e.target.value))}
                    className=<span class="hljs-string">"bg-inherit border rounded-sm border-input p-2 focus:outline-none focus:ring-1 focus:ring-slate-900 invalid:focus:ring-red-600"</span>
                /&gt;
            &lt;/div&gt;
            &lt;Button
                <span class="hljs-keyword">type</span>=<span class="hljs-string">"submit"</span>
                content={{
                    text: <span class="hljs-string">"Add Task"</span>,
                }}
                extraBtnClasses=<span class="hljs-string">"bg-pink-700 justify-center text-white font-semibold px-4 py-2 outline-1 hover:bg-pink-800 focus:ring-1 focus:ring-pink-800 w-full"</span>
            /&gt;
        &lt;/form&gt;
    );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> AddTask;
</code></pre>
<p>Now create a new file in components called <strong>Select.tsx</strong>, paste the following in it:</p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">import</span> { useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;

<span class="hljs-keyword">interface</span> SelectProps {
    defaultSelectValue: <span class="hljs-built_in">string</span>;
    selectOptions: <span class="hljs-built_in">string</span>[];
    handleSelectChange: <span class="hljs-function">(<span class="hljs-params">e: React.ChangeEvent&lt;HTMLSelectElement&gt;</span>) =&gt;</span> <span class="hljs-built_in">void</span>;
}

<span class="hljs-keyword">const</span> Select = <span class="hljs-function">(<span class="hljs-params">{
    defaultSelectValue,
    handleSelectChange,
    selectOptions,
}: SelectProps</span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> [selectVal, setSelectVal] = useState(defaultSelectValue);
    <span class="hljs-keyword">return</span> (
        &lt;select
            value={selectVal}
            onChange={<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
                setSelectVal(e.target.value);
                handleSelectChange(e);
            }}
            className=<span class="hljs-string">"bg-inherit border rounded-sm border-input p-2 focus:outline-none focus:ring-1 focus:ring-slate-900 cursor-pointer"</span>
        &gt;
            {selectOptions.map(<span class="hljs-function">(<span class="hljs-params">option</span>) =&gt;</span> (
                &lt;option key={option} value={option}&gt;
                    {option.charAt(<span class="hljs-number">0</span>).toUpperCase() + option.slice(<span class="hljs-number">1</span>)}
                &lt;/option&gt;
            ))}
        &lt;/select&gt;
    );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Select;
</code></pre>
<p>This defines a <code>Select</code> component and its props. The <code>Select</code> component props are a function for handling change, an array of options and the default value it should display. </p>
<p>Now, import the <code>AddTask</code> component in the <strong>Index.tsx</strong> file between the <code>h1</code> tags like so: </p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">import</span> AddTask <span class="hljs-keyword">from</span> <span class="hljs-string">"../components/AddTask"</span>;

<span class="hljs-keyword">const</span> Index = <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">return</span> (
        &lt;main className=<span class="hljs-string">"container mx-auto"</span>&gt;
            &lt;section className=<span class="hljs-string">"max-w-5xl mx-auto m-12 p-16"</span>&gt;
                &lt;h1 className=<span class="hljs-string">"text-4xl md:text-7xl font-bold text-center py-3 mb-16"</span>&gt;
                    AI-enhanced, Voice-enabled, Searchable Task Manager
                &lt;/h1&gt;
                &lt;AddTask /&gt;
            &lt;/section&gt;
        &lt;/main&gt;
    );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Index;
</code></pre>
<p>Your application should now display the form:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/03/Screenshot-2024-03-08-at-18.57.25.png" alt="Image" width="600" height="400" loading="lazy">
<em>task app form</em></p>
<h3 id="heading-how-to-set-up-form-to-create-task">How to Set Up Form to Create Task</h3>
<p>To make the form functional, you need to hook it up to a submit function that will call the create function defined in the <strong>db.ts</strong> file. </p>
<p>Additionally, you will need to validate the form to avoid sending bad data and having Appwrite send errors back to the React application.</p>
<p>In the <code>AddTask</code> component file, paste the following code above the <code>return</code> statement and below the <code>setPriority</code> useState:</p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">const</span> [priority, setPriority] = useState(priorityArray[<span class="hljs-number">0</span>]);

<span class="hljs-comment">//paste here</span>
<span class="hljs-keyword">const</span> navigate = useNavigate();

<span class="hljs-keyword">const</span> [isSubmitting, setIsSubmitting] = useState(<span class="hljs-literal">false</span>);
<span class="hljs-keyword">const</span> [titleValidationError, setTitleValidationError] = useState(<span class="hljs-string">""</span>);

    <span class="hljs-keyword">const</span> handleTitleChange = <span class="hljs-function">(<span class="hljs-params">e: React.ChangeEvent&lt;HTMLInputElement&gt;</span>) =&gt;</span> {
        setTitleVal(e.target.value);

        <span class="hljs-keyword">if</span> (e.target.value.trim() !== <span class="hljs-string">""</span>) {
            setTitleValidationError(<span class="hljs-string">""</span>);
        }
    };

    <span class="hljs-keyword">const</span> handleSubmitTask = <span class="hljs-keyword">async</span> (e: React.FormEvent&lt;HTMLFormElement&gt;) =&gt; {
        e.preventDefault();
        setIsSubmitting(<span class="hljs-literal">true</span>);

        <span class="hljs-keyword">try</span> {
            <span class="hljs-keyword">if</span> (!titleVal) {
                setTitleValidationError(<span class="hljs-string">"Please provide at least a title for the task"</span>);
                <span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =&gt;</span> setTitleValidationError(<span class="hljs-string">""</span>), <span class="hljs-number">2000</span>);
                setIsSubmitting(<span class="hljs-literal">false</span>);
                <span class="hljs-keyword">return</span>;
            }

            <span class="hljs-keyword">if</span> (titleVal.length &gt; <span class="hljs-number">49</span>) {
                setTitleValidationError(
                    <span class="hljs-string">"Title too long. It can only be 49 characters long"</span>
                );
                <span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =&gt;</span> setTitleValidationError(<span class="hljs-string">""</span>), <span class="hljs-number">2000</span>);
                setIsSubmitting(<span class="hljs-literal">false</span>);
                <span class="hljs-keyword">return</span>;
            }

            <span class="hljs-keyword">const</span> payload: IPayload = {
                title: titleVal,
                description: textAreaVal,
                due_date: dueDate,
                priority: priority,
            };

            <span class="hljs-keyword">await</span> createDocument(payload);

            <span class="hljs-comment">// reset form</span>
            setTitleVal(<span class="hljs-string">""</span>);
            setTextAreaVal(<span class="hljs-string">""</span>);
            setDueDate(<span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>());
            setPriority(priorityArray[<span class="hljs-number">0</span>]);
            setTitleValidationError(<span class="hljs-string">""</span>);
            setIsSubmitting(<span class="hljs-literal">false</span>);
            navigate(<span class="hljs-string">"/tasks"</span>);
        } <span class="hljs-keyword">catch</span> (error) {
            <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Error in handleSubmitTask:"</span>, error);
            setIsSubmitting(<span class="hljs-literal">false</span>);
        }
    };

    <span class="hljs-keyword">return</span> (
    <span class="hljs-comment">//rest of the code unchanged below</span>
</code></pre>
<p>Then replace the <code>return</code> statement with the following code:</p>
<pre><code>
<span class="hljs-keyword">return</span> (
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"form"</span> <span class="hljs-attr">onSubmit</span>=<span class="hljs-string">{handleSubmitTask}</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"m-8"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"flex flex-col mb-6"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">htmlFor</span>=<span class="hljs-string">"title"</span>&gt;</span>Task Title<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">input</span>
            <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span>
            <span class="hljs-attr">id</span>=<span class="hljs-string">"title"</span>
            <span class="hljs-attr">placeholder</span>=<span class="hljs-string">"Title of your task"</span>
            <span class="hljs-attr">value</span>=<span class="hljs-string">{titleVal}</span>
            <span class="hljs-attr">onChange</span>=<span class="hljs-string">{handleTitleChange}</span>
            <span class="hljs-attr">className</span>=<span class="hljs-string">{</span>`<span class="hljs-attr">bg-inherit</span> <span class="hljs-attr">border</span> <span class="hljs-attr">rounded-sm</span> <span class="hljs-attr">p-2</span> <span class="hljs-attr">focus:outline-none</span>                         <span class="hljs-attr">focus:ring-1</span> ${
                    <span class="hljs-attr">titleValidationError</span>
                    ? "<span class="hljs-attr">border-error</span> <span class="hljs-attr">focus:ring-red-500</span> <span class="hljs-attr">invalid:focus:ring-red-</span>                         <span class="hljs-attr">600</span>"
                    <span class="hljs-attr">:</span> "<span class="hljs-attr">border-input</span> <span class="hljs-attr">focus:ring-slate-900</span>"
            }`}
        /&gt;</span>
        {titleValidationError &amp;&amp; (
        <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-error mt-1"</span>&gt;</span>{titleValidationError}<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
        )}
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"flex flex-col mb-6"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">htmlFor</span>=<span class="hljs-string">"description"</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"mb-1"</span>&gt;</span>
            Task Description
        <span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">textarea</span>
            <span class="hljs-attr">id</span>=<span class="hljs-string">"description"</span>
            <span class="hljs-attr">placeholder</span>=<span class="hljs-string">"Describe your task"</span>
            <span class="hljs-attr">maxLength</span>=<span class="hljs-string">{200}</span>
            <span class="hljs-attr">value</span>=<span class="hljs-string">{textAreaVal}</span>
            <span class="hljs-attr">onChange</span>=<span class="hljs-string">{(e)</span> =&gt;</span> setTextAreaVal(e.target.value)}
            className={`bg-inherit border rounded-sm p-2 h-32 resize-none                             focus:outline-none focus:ring-1 ${
                        textAreaVal.length &gt; 197
                        ? "border-error focus:ring-red-500 invalid:focus:ring-                             red-600"
                        : "border-input focus:ring-slate-900"
                }`}
        /&gt;
        {textAreaVal.length &gt; 197 &amp;&amp; (
        <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-error mt-1"</span>&gt;</span>
            Warning description getting too long. Can only be 200 characters
        <span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
        )}
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"flex flex-col mb-6"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">htmlFor</span>=<span class="hljs-string">"description"</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"mb-1"</span>&gt;</span>
            Task Priority
        <span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Select</span>
            <span class="hljs-attr">defaultSelectValue</span>=<span class="hljs-string">{priority}</span>
            <span class="hljs-attr">selectOptions</span>=<span class="hljs-string">{priorityArray}</span>
            <span class="hljs-attr">handleSelectChange</span>=<span class="hljs-string">{(e)</span> =&gt;</span> setPriority(e.target.value)}
        /&gt;
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"flex flex-col mb-6"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">htmlFor</span>=<span class="hljs-string">"description"</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"mb-1"</span>&gt;</span>
            Task Due Date
        <span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">input</span>
            <span class="hljs-attr">type</span>=<span class="hljs-string">"date"</span>
            <span class="hljs-attr">id</span>=<span class="hljs-string">"date"</span>
            <span class="hljs-attr">value</span>=<span class="hljs-string">{dueDate!.toISOString().split(</span>"<span class="hljs-attr">T</span>")[<span class="hljs-attr">0</span>]}
            <span class="hljs-attr">min</span>=<span class="hljs-string">{new</span> <span class="hljs-attr">Date</span>()<span class="hljs-attr">.toISOString</span>()<span class="hljs-attr">.split</span>("<span class="hljs-attr">T</span>")[<span class="hljs-attr">0</span>]}
            <span class="hljs-attr">onChange</span>=<span class="hljs-string">{(e)</span> =&gt;</span> setDueDate(new Date(e.target.value))}
                className="bg-inherit border rounded-sm border-input p-2                                      focus:outline-none focus:ring-1 focus:ring-slate-                               900 invalid:focus:ring-red-600"
        /&gt;
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Button</span>
        <span class="hljs-attr">type</span>=<span class="hljs-string">"submit"</span>
        <span class="hljs-attr">disable</span>=<span class="hljs-string">{isSubmitting}</span>
        <span class="hljs-attr">extraBtnClasses</span>=<span class="hljs-string">"bg-primary justify-center text-white font-semibold px-4 py-2 outline-1 hover:bg-primaryHover focus:ring-1 focus:ring-pink-800 w-full"</span>
    &gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">span</span>&gt;</span>
            Add Task
        <span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">Button</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span></span>
);
</code></pre><p>Fix import errors and your application should now be validating title and description as well as creating the task then sending you to the "/tasks" route. You can check the Appwrite console to confirm that the task has been created. </p>
<h3 id="heading-how-set-up-read-and-delete-tasks">How Set Up Read and Delete Tasks</h3>
<p>Open the <strong>Task.jsx</strong> file in the routes folder within <strong>src</strong> folder, and add the following code above the <code>return</code> like so:</p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">const</span> [tasks, setTasks] = useState&lt;ITask[]&gt;([]);
<span class="hljs-keyword">const</span> [tasksError, setTasksError] = useState(<span class="hljs-string">""</span>);

useEffect(<span class="hljs-function">() =&gt;</span> {
        getTasks()
        .then(<span class="hljs-function">(<span class="hljs-params">res</span>) =&gt;</span> {
        setTasks(res.reverse());
        })
        .catch(<span class="hljs-function">(<span class="hljs-params">err</span>) =&gt;</span> {
        <span class="hljs-built_in">console</span>.error(err);
        setTasksError(<span class="hljs-string">"Error fetching tasks, please try again"</span>);
        });
    }, []);

<span class="hljs-keyword">return</span> (
<span class="hljs-comment">//rest of code</span>
</code></pre>
<p>Here, the file is setting some local state using useState to hold the tasks and set any potential task related errors.</p>
<p>Now replace the code in the <code>return</code> with the following code:</p>
<pre><code class="lang-typescript">

&lt;main className=<span class="hljs-string">"container mx-auto"</span>&gt;
    &lt;section className=<span class="hljs-string">"max-w-5xl mx-auto m-12 p-16"</span>&gt;
        &lt;h1 className=<span class="hljs-string">"text-4xl md:text-7xl font-bold text-center py-3 mb-16"</span>&gt;
        Your Tasks
        &lt;/h1&gt;
        {tasksError ? (
            &lt;span className=<span class="hljs-string">"m-8 text-error"</span>&gt;{tasksError}&lt;/span&gt;
        ) : (
            &lt;div className=<span class="hljs-string">"flex flex-col md:flex-row justify-between"</span>&gt;
                &lt;div className=<span class="hljs-string">"flex-1"</span>&gt;
                    &lt;h3 className=<span class="hljs-string">"text-2xl font-bold m-8"</span>&gt;Pending Tasks&lt;/h3&gt;
                    &lt;div&gt;
                         {tasks
                             .filter(<span class="hljs-function">(<span class="hljs-params">task</span>) =&gt;</span> !task.done)
                             .map(<span class="hljs-function">(<span class="hljs-params">task</span>) =&gt;</span> (
                                &lt;TaskItem key={task.$id} task={task} /&gt;
                         ))}
                    &lt;/div&gt;
                &lt;/div&gt;
                &lt;div className=<span class="hljs-string">"flex-1"</span>&gt;
                    &lt;h3 className=<span class="hljs-string">"text-2xl font-bold m-8"</span>&gt;Completed Tasks&lt;/h3&gt;
                    &lt;div&gt;
                        {tasks
                            .filter(<span class="hljs-function">(<span class="hljs-params">task</span>) =&gt;</span> task.done)
                            .map(<span class="hljs-function">(<span class="hljs-params">task</span>) =&gt;</span> (
                                &lt;TaskItem key={task.$id} task={task} /&gt;
                        ))}
                    &lt;/div&gt;
                &lt;/div&gt;
            &lt;/div&gt;
        )}
    &lt;/section&gt;
&lt;/main&gt;
</code></pre>
<p>You now need to create the <code>getTasks()</code> function and the <code>TaskItem</code> component. In the components folder, create a file called <strong>TaskItem.tsx</strong> and paste the following code in it:</p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">interface</span> TaskItemProps {
    task: ITask;
}
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">TaskItem</span>(<span class="hljs-params">{ task }: TaskItemProps</span>) </span>{
    <span class="hljs-keyword">return</span> (
    &lt;&gt;
        &lt;div className=<span class="hljs-string">"m-8 cursor-pointer border border-container rounded-md p-4 hover:shadow-lg transition duration-300 ease-in-out max-h-96"</span>&gt;
            &lt;section
            key={task.$id}
            className=<span class="hljs-string">"flex flex-col justify-between gap-2 my-4 h-full"</span>
            &gt;
            &lt;section className=<span class="hljs-string">"flex gap-4 items-center justify-between flex-wrap"</span>&gt;
                {task.priority &amp;&amp; (
                &lt;span&gt;
                    &lt;span className=<span class="hljs-string">"font-medium"</span>&gt;Priority: &lt;/span&gt;
                        &lt;span
                            className={<span class="hljs-string">`<span class="hljs-subst">${
                            task.priority === <span class="hljs-string">"low"</span>
                            ? <span class="hljs-string">"bg-lowPriority text-iconColor"</span>
                            : task.priority === <span class="hljs-string">"medium"</span>
                            ? <span class="hljs-string">"bg-mediumPriority text-iconColor"</span>
                            : <span class="hljs-string">"bg-highPriority text-iconColor"</span>
                            }</span> py-1 px-2 rounded-md`</span>}
                        &gt;
                            {task.priority}
                        &lt;/span&gt;
                &lt;/span&gt;
                )}
                &lt;div className=<span class="hljs-string">"flex gap-2 py-1 ml-auto"</span>&gt;
                    &lt;Button
                        handleClick={<span class="hljs-function">() =&gt;</span> handleEdit(task)}
                        extraBtnClasses=<span class="hljs-string">"bg-ok"</span>
                    &gt;
                        &lt;span className=<span class="hljs-string">"font-medium"</span>&gt;Edit&lt;/span&gt;
                        &lt;PencilSquareIcon height={<span class="hljs-number">25</span>} className=<span class="hljs-string">"hidden lg:flex"</span> /&gt;
                    &lt;/Button&gt;
                    &lt;Button
                        handleClick={<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> handleDelete(e, task.$id)}
                        extraBtnClasses=<span class="hljs-string">"bg-highPriority"</span>
                    &gt;
                        &lt;span className=<span class="hljs-string">"font-medium"</span>&gt;Delete&lt;/span&gt;
                        &lt;TrashIcon height={<span class="hljs-number">25</span>} className=<span class="hljs-string">"hidden lg:flex"</span> /&gt;
                    &lt;/Button&gt;
                &lt;/div&gt;
            &lt;/section&gt;
            &lt;section className=<span class="hljs-string">""</span>&gt;
                &lt;h2 className=<span class="hljs-string">"text-xl font-medium py-2 break-words"</span>&gt;
                    {task.title}
                &lt;/h2&gt;
                &lt;p className=<span class="hljs-string">"py-1 mb-4 min-h-16 break-words"</span>&gt;
                    {task.description.length &gt; <span class="hljs-number">70</span>
                        ? task.description.substring(<span class="hljs-number">0</span>, <span class="hljs-number">70</span>) + <span class="hljs-string">"..."</span>
                        : task.description}
                &lt;/p&gt;
                &lt;span className=<span class="hljs-string">"font-extralight mt-2"</span>&gt;
                    &lt;span className=<span class="hljs-string">"font-medium"</span>&gt;Due on: &lt;/span&gt;
                        &lt;span className=<span class="hljs-string">"underline"</span>&gt;{<span class="hljs-string">`<span class="hljs-subst">${<span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(
                            task.due_date
                        ).toLocaleDateString()}</span>`</span>}
                    &lt;/span&gt;
                &lt;/span&gt;
                &lt;/section&gt;
                &lt;section className=<span class="hljs-string">"flex justify-between"</span>&gt;
                    {task.done ? (
                        &lt;span className=<span class="hljs-string">"items-center text-ok font-bol ml-auto"</span>&gt;
                            Completed
                        &lt;/span&gt;
                    ) : (
                    &lt;div className=<span class="hljs-string">"flex items-center ml-auto hover:scale-105 transition duration-300 ease-in-out"</span>&gt;
                        &lt;label htmlFor=<span class="hljs-string">"done"</span> className=<span class="hljs-string">"mr-2 font-light"</span>&gt;
                            Mark <span class="hljs-keyword">as</span> complete
                        &lt;/label&gt;
                        &lt;input
                            <span class="hljs-keyword">type</span>=<span class="hljs-string">"checkbox"</span>
                            checked={isDone}
                            onClick={<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> e.stopPropagation()}
                            onChange={<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
                            setIsDone(e.target.checked);
                            handleCheckbox(task, task.$id, e);
                        }}
                            className=<span class="hljs-string">"size-5 accent-pink-600 rounded-sm"</span>
                    /&gt;
                &lt;/div&gt;
                )}
                &lt;/section&gt;
            &lt;/section&gt;
        &lt;/div&gt;
    &lt;/&gt;
    );
}
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> TaskItem;
</code></pre>
<p>This gives the file some markup to display. It divides the page into two columns, one for the pending tasks and one for the completed tasks, and it handles responsiveness of the page.</p>
<p>In order to get rid of the errors, paste the following code just before the <code>return</code> statement like so: </p>
<pre><code class="lang-typescript">
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">TaskItem</span>(<span class="hljs-params">{ task }: TaskItemProps</span>) </span>{

<span class="hljs-comment">//paste here</span>
<span class="hljs-keyword">const</span> [isDone, setIsDone] = useState(<span class="hljs-literal">false</span>);

<span class="hljs-keyword">const</span> handleDelete = <span class="hljs-keyword">async</span> (
        currentTaskId: <span class="hljs-built_in">string</span>
    ) =&gt; {
        <span class="hljs-keyword">try</span> {
            <span class="hljs-keyword">await</span> deleteDocument(currentTaskId);
        } <span class="hljs-keyword">catch</span> (error) {
            <span class="hljs-built_in">console</span>.error(error);
        }
};

<span class="hljs-keyword">const</span> handleCheckbox = <span class="hljs-keyword">async</span> (
        currentTask: IPayload,
        id: <span class="hljs-built_in">string</span>,
        checkedVal: <span class="hljs-built_in">boolean</span>
    ) =&gt; {
        <span class="hljs-keyword">if</span> (!checkedVal) <span class="hljs-keyword">return</span>;

        <span class="hljs-keyword">const</span> payload: IPayload = {
        title: currentTask.title,
        description: currentTask.description,
        due_date: currentTask.due_date,
        priority: currentTask.priority,
        done: checkedVal,
        };

        <span class="hljs-keyword">try</span> {
            <span class="hljs-keyword">await</span> updateDocument(payload, id);
        } <span class="hljs-keyword">catch</span> (error) {
            <span class="hljs-built_in">console</span>.error(error);
        }
};

<span class="hljs-comment">//rest of code below untouched</span>
<span class="hljs-keyword">return</span> (
......
</code></pre>
<p>This adds the ability to delete a task item and the ability to mark it as complete. </p>
<p>Create a new file in the <strong>utils</strong> folder and call it <strong>shared.ts</strong>. This file will house any function that will be called in more than two places in the application. </p>
<p>The <code>getTasks</code> function is one such repetitive function, so it will be placed in the <strong>shared.ts</strong> file. Paste the following code into it:</p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">import</span> { readDocuments } <span class="hljs-keyword">from</span> <span class="hljs-string">"./db"</span>;
<span class="hljs-keyword">import</span> { ITask } <span class="hljs-keyword">from</span> <span class="hljs-string">"../models/interface"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> getTasks = <span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">const</span> { documents } = <span class="hljs-keyword">await</span> readDocuments();

    <span class="hljs-keyword">return</span> documents <span class="hljs-keyword">as</span> ITask[];
};
</code></pre>
<p>This defines the function and returns an array of <code>ITasks</code>. Go back to the <strong>Task.tsx</strong> file and fix any import errors. </p>
<p>Run the application and you should see something like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/03/Screenshot-2024-03-09-at-00.10.56.png" alt="Image" width="600" height="400" loading="lazy">
<em>your pending and completed tasks displayed in the browser</em></p>
<p>The task can be deleted or marked as complete but you won't see an update on the UI until the page is refreshed. To fix that, go back to the <strong>TaskItem</strong> file and paste the following code below the <code>isDone</code> useState and above the <code>handleDelete</code> function:</p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">const</span> [isDone, setIsDone] = useState(<span class="hljs-literal">false</span>);

<span class="hljs-comment">//paste here</span>
<span class="hljs-keyword">const</span> updateTasks = <span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">const</span> allTasks = <span class="hljs-keyword">await</span> getTasks();
        <span class="hljs-keyword">if</span> (setTasks) setTasks(allTasks.reverse());
    } <span class="hljs-keyword">catch</span> (error) {
        <span class="hljs-built_in">console</span>.error(error);
    }
};

<span class="hljs-comment">//rest of code below remains as is</span>
<span class="hljs-keyword">const</span> handleDelete = <span class="hljs-keyword">async</span> (
</code></pre>
<p>Update <code>TaskItem</code> props interface and the <code>TaskItem</code> function like so:</p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">interface</span> TaskItemProps {
    task: ITask;
    setTasks?: <span class="hljs-function">(<span class="hljs-params">tasks: ITask[]</span>) =&gt;</span> <span class="hljs-built_in">void</span>;
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">TaskItem</span>(<span class="hljs-params">{ task, setTasks }: TaskItemProps</span>) </span>{
<span class="hljs-comment">//rest of code below</span>
    <span class="hljs-keyword">const</span> [isDone, setIsDone] = useState(<span class="hljs-literal">false</span>);
</code></pre>
<p>This gives a setter function that resets the tasks array as a prop to the <code>TaskItem</code> component. </p>
<p>Adjust the <code>handleDelete</code> and <code>handleCheckbox</code> functions in the <code>TaskItem</code> component to include the <code>updateTasks</code> function you added above. it should read like this:</p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">const</span> handleDelete = <span class="hljs-keyword">async</span> (
    e: React.MouseEvent&lt;HTMLButtonElement&gt;,
    currentTaskId: <span class="hljs-built_in">string</span>
) =&gt; {
    <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">await</span> deleteDocument(currentTaskId);
        updateTasks();
    } <span class="hljs-keyword">catch</span> (error) {
        <span class="hljs-built_in">console</span>.error(error);
    }
};

<span class="hljs-keyword">const</span> handleCheckbox = <span class="hljs-keyword">async</span> (
    currentTask: IPayload,
    id: <span class="hljs-built_in">string</span>,
    e: React.ChangeEvent&lt;HTMLInputElement&gt;
    ) =&gt; {

    <span class="hljs-keyword">const</span> payload: IPayload = {
        title: currentTask.title,
        description: currentTask.description,
        due_date: currentTask.due_date,
        priority: currentTask.priority,
        done: e.target.checked,
    };

    <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">await</span> updateDocument(payload, id);
        updateTasks();
    } <span class="hljs-keyword">catch</span> (error) {
        <span class="hljs-built_in">console</span>.error(error);
    }
};
</code></pre>
<p>Go back to the <strong>Task.tsx</strong> file and pass <code>setTasks</code> to the <code>TaskItem</code> component like so: <code>&lt;TaskItem key={task.$id} task={task} setTasks={setTasks} /&gt;</code>. Now, the UI updates without needing to manually refresh the page.</p>
<h3 id="heading-how-to-make-the-tasks-editable">How to Make the Tasks Editable</h3>
<p>In order to edit a task, you will need to pass a function to the edit button in the <code>TaskItem</code> component. </p>
<p>Paste the following code in the <strong>TaskItem.tsx</strong> file between the <code>updateTasks</code> and <code>handleDelete</code> functions like so:</p>
<pre><code class="lang-typescript">
<span class="hljs-comment">//paste below updateTasks</span>
<span class="hljs-keyword">const</span> handleEdit = <span class="hljs-keyword">async</span> (
    currentTask: ITask
) =&gt; {
    navigate(<span class="hljs-string">"/"</span>, { state: { task: currentTask } });
};

<span class="hljs-comment">//rest of code untouched below</span>
</code></pre>
<p>Add this line right above the <code>isDone</code> useState: <code>const navigate = useNavigate();</code>. </p>
<p>In the same file, find the edit button and pass the <code>handleEdit</code> function to it. Also, wrap it in a condition that checks if the task is done such that the button is only displayed in the case that the task is not marked as complete. Like so:</p>
<pre><code class="lang-typescript">{!task.done &amp;&amp; (
    &lt;Button
        handleClick={<span class="hljs-function">() =&gt;</span> handleEdit(task)}
        extraBtnClasses=<span class="hljs-string">"bg-ok"</span>
    &gt;
        &lt;span className=<span class="hljs-string">"font-medium"</span>&gt;Edit&lt;/span&gt;
        &lt;PencilSquareIcon height={<span class="hljs-number">25</span>} className=<span class="hljs-string">"hidden lg:flex"</span> /&gt;
    &lt;/Button&gt;
)
</code></pre>
<p>The <code>AddTask</code> component has to be adjusted to handle editing a task and the <strong>Index.tsx</strong> file needs to be updated to handle the task to be edited being passed to it via the navigate <code>handleEdit</code> function.</p>
<p>First, go to the <strong>AddTask</strong> file and add some prop definitions directly below the import statements, then pass the new props to the component like so:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span>....

<span class="hljs-comment">// pass a task and an isEdit boolean</span>
<span class="hljs-comment">// if isEdit is true, then the form will be populated with the task's data</span>
<span class="hljs-keyword">interface</span> ITaskFormProps {
    task: ITask | <span class="hljs-literal">null</span>;
    isEdit?: <span class="hljs-built_in">boolean</span>;
    setTasks?: <span class="hljs-function">(<span class="hljs-params">tasks: ITask[]</span>) =&gt;</span> <span class="hljs-built_in">void</span>;
}

<span class="hljs-comment">//pass component props</span>
<span class="hljs-keyword">const</span> AddTask = <span class="hljs-function">(<span class="hljs-params">{ task, isEdit, setTasks }: ITaskFormProps</span>) =&gt;</span> {
<span class="hljs-comment">//code untouched below</span>
</code></pre>
<p>Adjust the due date and priority useStates to read like below:</p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">const</span> [dueDate, setDueDate] = useState(
    isEdit &amp;&amp; task?.due_date ? <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(task.due_date) : <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>()
);

<span class="hljs-keyword">const</span> [priority, setPriority] = useState(
    isEdit &amp;&amp; task?.priority ? task?.priority : priorityArray[<span class="hljs-number">0</span>]
);
</code></pre>
<p>Add a useEffect below the useStates like so:</p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">const</span> [titleValidationError, setTitleValidationError] = useState(<span class="hljs-string">""</span>);

<span class="hljs-comment">//paste below useState statements</span>
useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">if</span> (isEdit &amp;&amp; task) {
        setTitleVal(task.title);
        setTextAreaVal(task.description);
    } <span class="hljs-keyword">else</span> {
        setTitleVal(<span class="hljs-string">""</span>);
    }
}, [isEdit, task]);
</code></pre>
<p>In the <code>handleSubmit</code> function within the same <strong>AddTask</strong> file, delete this line: <code>await createDocument(payload);</code> and replace with the below:</p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">if</span> (isEdit &amp;&amp; task) {
    <span class="hljs-keyword">await</span> updateDocument(payload, task!.$id);
    <span class="hljs-keyword">const</span> allTasks = <span class="hljs-keyword">await</span> getTasks();
    <span class="hljs-keyword">if</span> (setTasks) <span class="hljs-keyword">return</span> setTasks(allTasks.reverse());
} <span class="hljs-keyword">else</span> {
    <span class="hljs-keyword">await</span> createDocument(payload);
}
</code></pre>
<p>Now replace the <code>Button</code> component at the bottom of the file just above the form closing tag with this:</p>
<pre><code class="lang-typescript">
&lt;Button
    <span class="hljs-keyword">type</span>=<span class="hljs-string">"submit"</span>
    disable={isSubmitting}
    extraBtnClasses=<span class="hljs-string">"bg-primary justify-center text-white font-semibold px-4 py-2 outline-1 hover:bg-primaryHover focus:ring-1 focus:ring-pink-800 w-full"</span>
&gt;
    &lt;span&gt;
        {isSubmitting ? <span class="hljs-string">"Submitting..."</span> : task ? <span class="hljs-string">"Edit Task"</span> : <span class="hljs-string">"Add Task"</span>}
    &lt;/span&gt;
&lt;/Button&gt;
<span class="hljs-comment">//unchanged code below</span>
&lt;/form&gt;
    );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> AddTask;
</code></pre>
<p>This sets the text on the button depending on whether the form is submitting, creating a new task or updating an exisiting task.</p>
<p>Go to the <strong>Index.tsx</strong> file in the <strong>routes</strong> folder and paste the following above the <code>return</code> statement:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> Index = <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-comment">//paste here</span>
    <span class="hljs-keyword">const</span> location = useLocation();
    <span class="hljs-keyword">const</span> navigate = useNavigate();

    <span class="hljs-keyword">const</span> taskFromState: ITask = location.state?.task;

    <span class="hljs-keyword">const</span> [taskToEdit] = useState&lt;ITask | <span class="hljs-literal">null</span>&gt;(taskFromState ?? <span class="hljs-literal">null</span>);

    useEffect(<span class="hljs-function">() =&gt;</span> {
        <span class="hljs-keyword">if</span> (taskFromState) {
            navigate(location.pathname, {});
        }
    }, [taskFromState, location.pathname, navigate]);

    <span class="hljs-comment">//below code remains unchanged</span>
    <span class="hljs-keyword">return</span> (....
</code></pre>
<p>Here, the file gets the task passed to it from the "/tasks" route and sets it to local state. Then the useEffect nullies the passed task so that the form is reset on refresh.</p>
<p>In the <strong>Index.tsx</strong> file replace the <code>AddTask</code> component with this line: <code>&lt;AddTask task={taskToEdit} isEdit={taskToEdit ? true : false} /&gt;</code>.</p>
<p>Run your application and you should be able to click on the edit button, be navigated back to the "/" route, have the form pre-filled with the task details, be able to edit some of the fields and be redirected back to "/tasks" once you click on the "Edit Task" button. </p>
<h3 id="heading-how-to-enable-viewing-of-tasks">How to Enable Viewing of Tasks</h3>
<p>The application now creates, reads, updates and deletes tasks. All that is left on that is the ability to view a particular task. </p>
<p>Go to the <strong>TaskItem</strong> file, add the following to the interface <code>TaskItemProps</code>: <code>isViewTask: boolean;    handleViewTask?: (e: React.MouseEvent&lt;HTMLDivElement&gt;) =&gt; void;</code>.</p>
<p>Add them as props to the <code>TaskItem</code> component and set <code>isViewTask</code> to a default of <code>false</code> like so:</p>
<pre><code class="lang-typescript">
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">TaskItem</span>(<span class="hljs-params">{
    task,
    setTasks,
    isViewTask = <span class="hljs-literal">false</span>,
    handleViewTask,
}: TaskItemProps</span>) </span>{    
    <span class="hljs-comment">//rest of code below unchanged</span>
    <span class="hljs-keyword">const</span> navigate = useNavigate();
</code></pre>
<p>Replace the paragraph tag that displays the task description within the <code>return</code> of the component with this markup:</p>
<pre><code class="lang-typescript">
&lt;p className=<span class="hljs-string">"py-1 mb-4 min-h-16 break-words"</span>&gt;
    {task.description.length &gt; <span class="hljs-number">70</span> &amp;&amp; !isViewTask
        ? task.description.substring(<span class="hljs-number">0</span>, <span class="hljs-number">70</span>) + <span class="hljs-string">"..."</span>
        : task.description
    }
&lt;/p&gt;
</code></pre>
<p>The change introduced will ensure that the full description is visible if <code>isViewTask</code> is set to <code>true</code>.</p>
<p>On the <code>div</code> tag just below the <code>return</code> in the same component, add an <code>onClick</code> handler like so:</p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">return</span> (
&lt;&gt;
    &lt;div
        className=<span class="hljs-string">"m-8 cursor-pointer border border-container rounded-md p-4                        hover:shadow-lg transition duration-300 ease-in-out max-h-                       96"</span>
        onClick={handleViewTask}
    &gt;
<span class="hljs-comment">//rest unchanged code</span>
...
</code></pre>
<p>Back in the <strong>Task.tsx</strong> file, paste the following just above the useEffect:</p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">const</span> handleViewTask = <span class="hljs-function">(<span class="hljs-params">
    e: React.MouseEvent&lt;HTMLDivElement&gt;,
    activeTask: ITask
</span>) =&gt;</span> {
    setIsViewTask(<span class="hljs-literal">true</span>);
    setSelectedTask(activeTask);
};

<span class="hljs-comment">//unchanged code below</span>
useEffect(...
</code></pre>
<p>Add the following useState functions above the <code>handleViewTask</code> function, below the other useStates:</p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">const</span> [tasksError, setTasksError] = useState(<span class="hljs-string">""</span>);
<span class="hljs-comment">//paste here</span>
<span class="hljs-keyword">const</span> [isViewTask, setIsViewTask] = useState(<span class="hljs-literal">false</span>);
<span class="hljs-keyword">const</span> [selectedTask, setSelectedTask] = useState&lt;ITask&gt;();

<span class="hljs-comment">//code below unchanged</span>
<span class="hljs-keyword">const</span> handleViewTask = (...
</code></pre>
<p>Now paste the following code within the <code>return</code> statement in the same file, just above the <code>h1</code> tag displaying "Your Tasks" text:</p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">return</span> (
&lt;main className=<span class="hljs-string">"container mx-auto"</span>&gt;
    &lt;section className=<span class="hljs-string">"max-w-5xl mx-auto m-12 p-16"</span>&gt;
        <span class="hljs-comment">//paste here</span>
        {isViewTask &amp;&amp; selectedTask &amp;&amp; (
            &lt;Dialog key={selectedTask.$id} setIsViewTask={setIsViewTask}&gt;
                &lt;TaskItem
                    task={selectedTask}
                    handleViewTask={<span class="hljs-function">() =&gt;</span> handleViewTask(selectedTask!)}
                    isViewTask={isViewTask}
                /&gt;
            &lt;/Dialog&gt;
        )}
        &lt;h1 className=<span class="hljs-string">"text-4xl md:text-7xl font-bold text-center py-3 mb-16"</span>&gt;
        Your Tasks
        &lt;/h1&gt;
        <span class="hljs-comment">//rest of code below remains unchanged</span>
</code></pre>
<p>You will need to create the Dialog component. Create a new file in the components folder and call it <strong>Dialog.tsx</strong>, then paste the following in it:</p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">import</span> { XMarkIcon } <span class="hljs-keyword">from</span> <span class="hljs-string">"@heroicons/react/24/solid"</span>;
<span class="hljs-keyword">import</span> { ReactNode, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { ITask } <span class="hljs-keyword">from</span> <span class="hljs-string">"../models/interface"</span>;
<span class="hljs-keyword">import</span> Button <span class="hljs-keyword">from</span> <span class="hljs-string">"./Button"</span>;

<span class="hljs-keyword">interface</span> DialogProps {
    setIsViewTask?: <span class="hljs-function">(<span class="hljs-params">isViewTask: <span class="hljs-built_in">boolean</span></span>) =&gt;</span> <span class="hljs-built_in">void</span>;
    children: ReactNode;
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Dialog</span>(<span class="hljs-params">{ setIsViewTask, children }: DialogProps</span>) </span>{
    <span class="hljs-keyword">const</span> [isOpen, setIsOpen] = useState(<span class="hljs-literal">true</span>);

    <span class="hljs-keyword">const</span> closeModal = <span class="hljs-function">() =&gt;</span> {
        <span class="hljs-keyword">if</span> (setIsViewTask) setIsViewTask(<span class="hljs-literal">false</span>);
        setIsOpen(<span class="hljs-literal">false</span>);
    };
    <span class="hljs-keyword">return</span> (
        &lt;dialog
            open={isOpen}
            id=<span class="hljs-string">"modal"</span>
            style={{
            backgroundColor: <span class="hljs-string">"var(--base-bg)"</span>,
            color: <span class="hljs-string">"var(--text-main)"</span>,
            }}
            className={<span class="hljs-string">`<span class="hljs-subst">${
                isOpen ? <span class="hljs-string">"opacity-100"</span> : <span class="hljs-string">"opacity-0 pointer-events-none"</span>
                }</span> transition-opacity duration-300 ease-in-out fixed inset-0                      backdrop-filter backdrop-blur-md backdrop-brightness-50 w-4/6                      border border-container rounded-md max-h-[80vh] overflow-y-auto                 text-main`</span>}
        &gt;
        &lt;Button
            handleClick={closeModal}
            content={{ text: <span class="hljs-string">"Close"</span>, icon: XMarkIcon }}
            extraBtnClasses=<span class="hljs-string">"ml-auto text-main font-medium hover:text-error"</span>
        /&gt;
        &lt;div className=<span class="hljs-string">"max-h-[80vh] overflow-y-auto"</span>&gt;{children}&lt;/div&gt;
        &lt;/dialog&gt;
    );
    }

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Dialog;
</code></pre>
<p>Here, the file defined a <code>Dialog</code> component that takes in some props, displays a button and the children it receives from props. </p>
<p>Finally, replace the two <code>TaskItem</code> components in the <code>tasks.filter...</code> function within the return statement of the <strong>Task.tsx</strong> file with the following:</p>
<pre><code class="lang-typescript">
{tasks
    .filter(<span class="hljs-function">(<span class="hljs-params">task</span>) =&gt;</span> !task.done)
    .map(<span class="hljs-function">(<span class="hljs-params">task</span>) =&gt;</span> (
        &lt;TaskItem
            key={task.$id}
            task={task}
            setTasks={setTasks}
            handleViewTask={<span class="hljs-function">() =&gt;</span> handleViewTask(task)}
            isViewTask={isViewTask}
        /&gt;
))}
</code></pre>
<p>You should be able to click on the task items and have the dialog pop up with the details of the task. </p>
<p>However, if you try to delete the item you will notice that it opens the dialog while deleting it. To fix that, adjust the <code>handleDelete</code> function in the <strong>TaskItem.tsx</strong> file to read like this: </p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">const</span> handleDelete = <span class="hljs-keyword">async</span> (
    e: React.MouseEvent&lt;HTMLButtonElement&gt;,
    currentTaskId: <span class="hljs-built_in">string</span>
) =&gt; {
    e.stopPropagation();
    <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">await</span> deleteDocument(currentTaskId);
        <span class="hljs-keyword">if</span> (isViewTask) {
            navigate(<span class="hljs-number">0</span>);
        } <span class="hljs-keyword">else</span> {
            updateTasks();
        }
    } <span class="hljs-keyword">catch</span> (error) {
        <span class="hljs-built_in">console</span>.error(error);
    }
};
</code></pre>
<p>You've added <code>e.stopPropagation()</code> to stop the event from bubbling up to the parent and potentially interfering with the click to open the dialog. </p>
<p>You've also added a check after deleting the task to see if the task is being viewed, in which case we refresh the page via the <code>navigate(0)</code> to force the UI to update to the proper state. If not, it proceeds to call <code>updateTasks()</code> to refresh the state.</p>
<p>You will notice the same issue when you try to mark the task as complete in which the dialog pops up. To fix this, adjust the checkbox input to include this line: <code>onClick={(e) =&gt; e.stopPropagation()}</code>. </p>
<p>The new line stops the event from bubbling up to the parent <code>div</code>. It is added to the <code>onClick</code> instead of the <code>onChange</code> because the event it is trying to intercept is of type <code>onClick</code>. The input should read like this:</p>
<pre><code class="lang-typescript">
&lt;label htmlFor=<span class="hljs-string">"done"</span> className=<span class="hljs-string">"mr-2 font-light"</span>&gt;
    Mark <span class="hljs-keyword">as</span> complete
&lt;/label&gt;
&lt;input
    <span class="hljs-keyword">type</span>=<span class="hljs-string">"checkbox"</span>
    checked={isDone}
    onClick={<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> e.stopPropagation()}
    onChange={<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
    setIsDone(e.target.checked);
    handleCheckbox(task, task.$id, e);
    }}
    className=<span class="hljs-string">"size-5 accent-pink-600 rounded-sm"</span>
/&gt;
</code></pre>
<p>At this point, the React application is responsive, can CRUD the Appwrite database and the user can view individual tasks. </p>
<h3 id="heading-how-to-auto-generate-descriptions-with-vercels-ai-sdk">How to Auto Generate Descriptions with Vercel's AI SDK</h3>
<p>To enhance the application and its' user experience, you can add the ability to auto generate descriptions for the tasks using AI. </p>
<p>To get started, open an integrated terminal and run the following command: <code>npm i ai</code>. This adds the <a target="_blank" href="https://vercel.com/blog/introducing-the-vercel-ai-sdk">Vercel AI SDK</a> to the React application. </p>
<p>Next, run this command in the terminal: <code>npm i @huggingface/inference</code> to add Hugging Face support. The application will use <a target="_blank" href="https://sdk.vercel.ai/docs/guides/providers/huggingface">Hugging Face</a> because you need to pay to get programmatic access to OpenAI.</p>
<p>Create a new file in the utils folder, call it <strong>ai.ts</strong> and paste the following in it:</p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">import</span> { HfInference } <span class="hljs-keyword">from</span> <span class="hljs-string">"@huggingface/inference"</span>;
<span class="hljs-keyword">import</span> { HuggingFaceStream, StreamingTextResponse } <span class="hljs-keyword">from</span> <span class="hljs-string">"ai"</span>;

<span class="hljs-comment">// Create a new HuggingFace Inference instance</span>
<span class="hljs-keyword">const</span> Hf = <span class="hljs-keyword">new</span> HfInference(<span class="hljs-keyword">import</span>.meta.env.VITE_HUGGINGFACE_KEY);

<span class="hljs-comment">// IMPORTANT! Set the runtime to edge</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> runtime = <span class="hljs-string">"edge"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> callAI = <span class="hljs-keyword">async</span> (prompt: <span class="hljs-built_in">string</span>) =&gt; {
    <span class="hljs-keyword">const</span> response = Hf.textGenerationStream({
        model: <span class="hljs-string">"OpenAssistant/oasst-sft-4-pythia-12b-epoch-3.5"</span>,
        inputs: <span class="hljs-string">`&lt;|prompter|&gt;<span class="hljs-subst">${prompt}</span>&lt;|endoftext|&gt;&lt;|assistant|&gt;`</span>,
        parameters: {
            max_new_tokens: <span class="hljs-number">150</span>,
            <span class="hljs-comment">// @ts-ignore</span>
            typical_p: <span class="hljs-number">0.2</span>,
            repetition_penalty: <span class="hljs-number">1</span>,
            truncate: <span class="hljs-number">1000</span>,
            return_full_text: <span class="hljs-literal">false</span>,
        },
    });

    <span class="hljs-comment">// Convert the response into a friendly text-stream</span>
    <span class="hljs-keyword">const</span> stream = HuggingFaceStream(response);

    <span class="hljs-comment">// Respond with the stream</span>
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> StreamingTextResponse(stream);
};
</code></pre>
<p>This boilerplate creates an instance of Hugging Face, creates a function that takes in a prompt, passes that prompt to the <code>textGenerationStream</code> function. Then it converts the response to a stream and to a text stream. </p>
<p>You will need to add a Hugging Face access token. You can genrate one at this <a target="_blank" href="https://huggingface.co/settings/tokens">address</a>. You will need an account before you can access it. </p>
<p>Once you have token, open the env file and add to it the following line</p>
<pre><code class="lang-env">
//replace with your actual token
VITE_HUGGINGFACE_KEY=YOUR-HF-ACCESS-TOKEN
</code></pre>
<p>Open the <strong>AddTask.tsx</strong> file and paste the following button just above the closing div tag of the <code>div</code> containing the textarea input:</p>
<pre><code class="lang-typescript">
{textAreaVal.length &gt; <span class="hljs-number">197</span> &amp;&amp; (
    &lt;span className=<span class="hljs-string">"text-error mt-1"</span>&gt;
        Warning description getting too long. Can only be <span class="hljs-number">200</span> characters
    &lt;/span&gt;
)}
<span class="hljs-comment">//paste here</span>
&lt;Button
    handleClick={generateDesc}
    disable={isGenerating}
    extraBtnClasses=<span class="hljs-string">"bg-light mt-2 w-fit ml-auto"</span>
&gt;
    &lt;span&gt;Generate description&lt;/span&gt;
    &lt;SparklesIcon height={<span class="hljs-number">20</span>} /&gt;
&lt;/Button&gt;
<span class="hljs-comment">//rest of below code unchanged</span>
&lt;/div&gt;
</code></pre>
<p>Define <code>generateDesc</code> function just above the <code>return</code> statement in the <strong>AddTask</strong> file like so:</p>
<pre><code>
<span class="hljs-keyword">const</span> generateDesc = <span class="hljs-keyword">async</span> () =&gt; {
    setTextAreaVal(<span class="hljs-string">""</span>);

    <span class="hljs-keyword">if</span> (!titleVal) {
    alert(<span class="hljs-string">"Please provide a title for the task"</span>);
    <span class="hljs-keyword">return</span>;
    }

    setIsGenerating(<span class="hljs-literal">true</span>);

    <span class="hljs-keyword">const</span> prompt = <span class="hljs-string">`Provide a description for this task: <span class="hljs-subst">${titleVal}</span>. Keep the description to a maximum of 30 words`</span>;

    <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> callAI(prompt);
        <span class="hljs-keyword">const</span> responseText = <span class="hljs-keyword">await</span> res.text();

        setIsGenerating(<span class="hljs-literal">false</span>);

        <span class="hljs-comment">//create a typing effect</span>
        responseText.split(<span class="hljs-string">""</span>).forEach(<span class="hljs-function">(<span class="hljs-params">char, index</span>) =&gt;</span> {
        <span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =&gt;</span> {
        setTextAreaVal(<span class="hljs-function">(<span class="hljs-params">prevText</span>) =&gt;</span> prevText + char);
        }, index * <span class="hljs-number">32</span>);
        });
    } <span class="hljs-keyword">catch</span> (error) {
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"ERROR HUGGING FACE API: "</span> + error);
    }
};
</code></pre><p>The function checks that the title is not empty and uses the title to create a prompt to pass on the Hugging Face helper. The response from the call is saved to local state. A simple typing effect is created as the textarea is populated with the response. </p>
<p>Next, add this useState: <code>const [isGenerating, setIsGenerating] = useState(false);</code> to the other useStates in the <code>AddTask</code> component.</p>
<p>Replace the textarea input in the same component with the following:</p>
<pre><code class="lang-typescript">
&lt;textarea
    id=<span class="hljs-string">"description"</span>
    placeholder=<span class="hljs-string">"Describe your task"</span>
    maxLength={<span class="hljs-number">200</span>}
    value={isGenerating ? <span class="hljs-string">"generating..."</span> : textAreaVal}
    onChange={<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> setTextAreaVal(e.target.value)}
    className={<span class="hljs-string">`bg-inherit border rounded-sm p-2 h-32 resize-none          focus:outline-none focus:ring-1 <span class="hljs-subst">${
    textAreaVal.length &gt; <span class="hljs-number">197</span>
    ? <span class="hljs-string">"border-error focus:ring-red-500 invalid:focus:ring-red-600"</span>
    : <span class="hljs-string">"border-input focus:ring-slate-900"</span>
    }</span>`</span>}
/&gt;
</code></pre>
<p>On checking the application, you should see the button and be able to generate a description for the title of a task as below:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/03/Screenshot-2024-03-09-at-04.57.05.png" alt="Image" width="600" height="400" loading="lazy">
<em>ai-enhanced task description</em></p>
<h3 id="heading-voice-enable-the-application-with-the-react-speech-recognition-package">Voice-enable the Application with the React Speech Recognition Package</h3>
<p>First, you need to add the dependency and its Typescript helper by running the following commands in an integrated terminal window: <code>npm i react-speech-recognition</code> and <code>npm i @types/react-speech-recognition</code>. </p>
<p>Additionally, run the following command to get the dependency to work properly: <code>npm i regenerator-runtime</code>.</p>
<p>Create a <strong>hooks</strong> folder within the <strong>src</strong> folder. Create a file within it called <strong>useSpeechToTextHelper.ts</strong> and paste the following in it:</p>
<pre><code class="lang-typeacript">
import "regenerator-runtime/runtime";
import { useState } from "react";
import { useSpeechRecognition } from "react-speech-recognition";

export function useSpeechToTextHelper() {
    const [error, setError] = useState("");

    const {
        transcript,
        listening,
        resetTranscript,
        browserSupportsSpeechRecognition,
    } = useSpeechRecognition();

    if (!browserSupportsSpeechRecognition) {
        setError("Browser doesn't support speech recognition.");
    }

    return {
        error,
        listening,
        transcript,
        resetTranscript,
    };
}
</code></pre>
<p>This hook exposes some built in helper functions from React Speech Recognition, handles the case that the browser does not support the <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognition">relevant web APIs</a> and returns some of that data.</p>
<p>Create a new file in the components folder called <strong>Speaker.tsx</strong>. Paste the following code into it:</p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">import</span> { useSpeechToTextHelper } <span class="hljs-keyword">from</span> <span class="hljs-string">"../hooks/useSpeechToTextHelper"</span>;
<span class="hljs-keyword">import</span> { MicrophoneIcon, XCircleIcon } <span class="hljs-keyword">from</span> <span class="hljs-string">"@heroicons/react/24/solid"</span>;
<span class="hljs-keyword">import</span> Button <span class="hljs-keyword">from</span> <span class="hljs-string">"./Button"</span>;
<span class="hljs-keyword">import</span> SpeechRecognition <span class="hljs-keyword">from</span> <span class="hljs-string">"react-speech-recognition"</span>;

<span class="hljs-keyword">interface</span> SpeakerProps {
handleClear: <span class="hljs-function">(<span class="hljs-params">e: React.MouseEvent&lt;HTMLButtonElement&gt;</span>) =&gt;</span> <span class="hljs-built_in">void</span>;
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Speaker</span>(<span class="hljs-params">{ handleClear }: SpeakerProps</span>) </span>{
    <span class="hljs-keyword">const</span> { listening, error } = useSpeechToTextHelper();

    <span class="hljs-keyword">const</span> handleSpeech = <span class="hljs-function">() =&gt;</span> {
        SpeechRecognition.startListening();
    };

    <span class="hljs-keyword">return</span> (
    &lt;div&gt;
        {error &amp;&amp; &lt;div&gt;{error}&lt;/div&gt;}
        &lt;div className=<span class="hljs-string">"flex gap-2 py-1 items-center text-center justify-center"</span>&gt;
            &lt;span className=<span class="hljs-string">"font-medium"</span>&gt;{listening ? <span class="hljs-string">"Mic on"</span> : <span class="hljs-string">"Mic off"</span>}&lt;/span&gt;
            &lt;Button
                handleClick={handleSpeech}
                extraBtnClasses=<span class="hljs-string">"bg-lightOk"</span>
                title=<span class="hljs-string">"Start"</span>
            &gt;
                &lt;MicrophoneIcon height={<span class="hljs-number">25</span>} /&gt;
            &lt;/Button&gt;
            &lt;Button
                handleClick={handleClear}
                extraBtnClasses=<span class="hljs-string">"bg-light"</span>
                <span class="hljs-keyword">type</span>=<span class="hljs-string">"reset"</span>
                title=<span class="hljs-string">"Reset"</span>
            &gt;
                &lt;XCircleIcon height={<span class="hljs-number">25</span>} /&gt;
            &lt;/Button&gt;
        &lt;/div&gt;
    &lt;/div&gt;
    );
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Speaker;
</code></pre>
<p>This components accepts a function to clear the voice input as a prop, uses the helper hook, defines a function to handle the actual speech, handles potential error state and displays a button for handling the speech and another for clearing the voice transcript.</p>
<p>In the <strong>AddTask</strong> file, delete the label for title and replace it with the following markup:</p>
<pre><code class="lang-typescript">
&lt;div className=<span class="hljs-string">"flex flex-row justify-between items-center"</span>&gt;
    &lt;label htmlFor=<span class="hljs-string">"title"</span>&gt;Task Title&lt;/label&gt;
    &lt;Speaker handleClear={clearTranscript} /&gt;
&lt;/div&gt;
</code></pre>
<p>This adds the <code>Speaker</code> component and wraps both the label and the <code>Speaker</code> component in a representational <code>div</code> in order to maintain the form layout. </p>
<p>Add the <code>clearTranscript</code> function just above the <code>handleSubmit</code> function like so:</p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">const</span> handleTitleChange = <span class="hljs-function">(<span class="hljs-params">e: React.ChangeEvent&lt;HTMLInputElement&gt;</span>) =&gt;</span> {
    setTitleVal(e.target.value);

    <span class="hljs-keyword">if</span> (e.target.value.trim() !== <span class="hljs-string">""</span>) {
        setTitleValidationError(<span class="hljs-string">""</span>);
    }
};

<span class="hljs-comment">//paste here</span>
<span class="hljs-keyword">const</span> clearTranscript = <span class="hljs-function">() =&gt;</span> {
    resetTranscript();
};
<span class="hljs-comment">//below code is unchanged</span>
<span class="hljs-keyword">const</span> handleSubmitTask = asyc....
</code></pre>
<p>Next in the same <code>AddTask</code> component, add the following:</p>
<pre><code>
<span class="hljs-keyword">const</span> AddTask = <span class="hljs-function">(<span class="hljs-params">{ task, isEdit, setTasks }: ITaskFormProps</span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> navigate = useNavigate();
    <span class="hljs-comment">//paste here</span>
    <span class="hljs-keyword">const</span> { transcript, resetTranscript } = useSpeechToTextHelper();
    <span class="hljs-comment">//rest remains unchanged</span>
</code></pre><p>Replace the useEffect in the file with this new one:</p>
<pre><code class="lang-typescript">
useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">if</span> (isEdit &amp;&amp; task &amp;&amp; !transcript) {
        setTitleVal(task.title);
        setTextAreaVal(task.description);
    } <span class="hljs-keyword">else</span> {
        setTitleVal(transcript || <span class="hljs-string">""</span>);
    }
}, [isEdit, task, transcript]);
</code></pre>
<p>Your application should now support creating titles for the tasks via voice inputs. And should look something like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/03/Screenshot-2024-03-09-at-05.36.51.png" alt="Image" width="600" height="400" loading="lazy">
<em>voice input functionality added to the form</em></p>
<h3 id="heading-how-to-add-search-functionality-to-the-application">How to Add Search Functionality to the Application</h3>
<p>To increase ease of use, it is useful to have some search functionality in the application. </p>
<p>To start, open up the Appwrite console. Click into your collections, click on Indexes tab then click on the "Create index" button. </p>
<p>Leave the index key as is, select FullText in the index type dropdown. Add title attribute and create the index. Repeat the process for the description attribute.</p>
<p>In your application, open <strong>db.ts</strong> file in <strong>utils</strong> folder and paste the following function just above the <code>export</code> keyword, then add it to the list of exports:</p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">const</span> searchTasks = <span class="hljs-keyword">async</span> (searchTerm: <span class="hljs-built_in">string</span>) =&gt; {
    <span class="hljs-keyword">const</span> resTitle = <span class="hljs-keyword">await</span> databases.listDocuments(dbID, collectionID, [
                        Query.search(<span class="hljs-string">"title"</span>, searchTerm),
                    ]);
    <span class="hljs-keyword">const</span> resDesc = <span class="hljs-keyword">await</span> databases.listDocuments(dbID, collectionID, [
                        Query.search(<span class="hljs-string">"description"</span>, searchTerm),
                     ]);
    <span class="hljs-keyword">const</span> res = [...resTitle.documents, ...resDesc.documents];

    <span class="hljs-keyword">return</span> res;
};

<span class="hljs-keyword">export</span> {
    createDocument,
    readDocuments,
    updateDocument,
    deleteDocument,
    searchTasks,
};
</code></pre>
<p>Create a new file in the components folder, calll it <strong>Search.tsx</strong>. Paste the following into it:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { FormEvent, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { ITask } <span class="hljs-keyword">from</span> <span class="hljs-string">"../models/interface"</span>;
<span class="hljs-keyword">import</span> Dialog <span class="hljs-keyword">from</span> <span class="hljs-string">"./Dialog"</span>;
<span class="hljs-keyword">import</span> TaskItem <span class="hljs-keyword">from</span> <span class="hljs-string">"./TaskItem"</span>;
<span class="hljs-keyword">import</span> Button <span class="hljs-keyword">from</span> <span class="hljs-string">"./Button"</span>;
<span class="hljs-keyword">import</span> { searchTasks } <span class="hljs-keyword">from</span> <span class="hljs-string">"../utils/db"</span>;

<span class="hljs-keyword">const</span> Search = <span class="hljs-function">() =&gt;</span> {
<span class="hljs-keyword">const</span> [searchTerm, setSearchTerm] = useState(<span class="hljs-string">""</span>);
<span class="hljs-keyword">const</span> [isSearching, setIsSearching] = useState(<span class="hljs-literal">false</span>);
<span class="hljs-keyword">const</span> [searchedTasks, setSearchedTasks] = useState&lt;ITask[]&gt;([]);
<span class="hljs-keyword">const</span> [error, setError] = useState(<span class="hljs-string">""</span>);

    <span class="hljs-keyword">return</span> (
        &lt;div className=<span class="hljs-string">"flex flex-col w-full md:w-1/2"</span>&gt;
            &lt;form
                className=<span class="hljs-string">"flex flex-col md:flex-row items-start md:items-center gap-2"</span>
                onSubmit={handleSubmit}
            &gt;
                {searchedTasks.length &gt; <span class="hljs-number">0</span> &amp;&amp; (
                    &lt;Dialog setSearchedTasks={setSearchedTasks}&gt;
                        {searchedTasks.map(<span class="hljs-function">(<span class="hljs-params">task: ITask</span>) =&gt;</span> (
                            &lt;TaskItem key={task.$id} task={task} isViewTask={<span class="hljs-literal">true</span>} /&gt;
                        ))}
                    &lt;/Dialog&gt;
                )}
            &lt;input
                aria-roledescription=<span class="hljs-string">"search"</span>
                <span class="hljs-keyword">type</span>=<span class="hljs-string">"text"</span>
                id=<span class="hljs-string">"search"</span>
                placeholder=<span class="hljs-string">"search your tasks..."</span>
                value={searchTerm}
                onChange={<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> setSearchTerm(e.target.value)}
                className={<span class="hljs-string">`bg-inherit w-5/6 border rounded-md p-2 focus:outline-none focus:ring-1 <span class="hljs-subst">${
                error
                ? <span class="hljs-string">"border-error focus:ring-red-500 invalid:focus:ring-red-600"</span>
                : <span class="hljs-string">"border-input focus:ring-slate-900"</span>
                }</span>`</span>}
            /&gt;
            &lt;Button
            <span class="hljs-keyword">type</span>=<span class="hljs-string">"submit"</span>
            extraBtnClasses=<span class="hljs-string">"bg-primary text-white hover:bg-primaryHover font-medium text-main py-2"</span>
            &gt;
                &lt;span&gt;{isSearching ? <span class="hljs-string">"Searching..."</span> : <span class="hljs-string">"Search"</span>}&lt;/span&gt;
            &lt;/Button&gt;
            &lt;/form&gt;
            &lt;span className=<span class="hljs-string">"text-error font-medium mt-1"</span>&gt;{error}&lt;/span&gt;
        &lt;/div&gt;
    );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Search;
</code></pre>
<p>The new <code>Search</code> component creates some local state and returns a form with an input and search button. It also opens the dialog when it has search results.</p>
<p>Add this <code>handleSubmit</code> function above the <code>return</code> statement like so:</p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">const</span> handleSubmit = <span class="hljs-keyword">async</span> (e: FormEvent&lt;HTMLFormElement&gt;) =&gt; {
    e.preventDefault();
    <span class="hljs-keyword">if</span> (!searchTerm) {
        setError(<span class="hljs-string">"No search term entered"</span>);
        <span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =&gt;</span> {
        setError(<span class="hljs-string">""</span>);
        }, <span class="hljs-number">3000</span>);
        <span class="hljs-keyword">return</span>;
    }

    setIsSearching(<span class="hljs-literal">true</span>);

    <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> searchTasks(searchTerm);
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"res search: "</span>, res);
    <span class="hljs-keyword">if</span> (res.length === <span class="hljs-number">0</span>) {
        setIsSearching(<span class="hljs-literal">false</span>);
        setError(<span class="hljs-string">"No task found"</span>);
        <span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =&gt;</span> {
        setSearchTerm(<span class="hljs-string">""</span>);
        setError(<span class="hljs-string">""</span>);
        }, <span class="hljs-number">3000</span>);
        <span class="hljs-keyword">return</span>;
    }
    setIsSearching(<span class="hljs-literal">false</span>);
    setSearchedTasks(res <span class="hljs-keyword">as</span> ITask[]);
};
</code></pre>
<p>This function sets an error if no search term is received, then it attempts to call the database search function passing it the search term. If successful it sets the tasks to local state and if not, it catches the error.</p>
<p>Click into the dialog component and replace its props with the following, then pass the <code>setSearchedTasks</code> to it like so:</p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">interface</span> DialogProps {
    setIsViewTask?: <span class="hljs-function">(<span class="hljs-params">isViewTask: <span class="hljs-built_in">boolean</span></span>) =&gt;</span> <span class="hljs-built_in">void</span>;
    setSearchedTasks?: <span class="hljs-function">(<span class="hljs-params">tasks: ITask[]</span>) =&gt;</span> <span class="hljs-built_in">void</span>;
    children: ReactNode;
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Dialog</span>(<span class="hljs-params">{ setIsViewTask, setSearchedTasks, children }: DialogProps</span>) </span>{...
</code></pre>
<p>Replace the <code>closeModal</code> function in the dialog component with this snippet:</p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">const</span> closeModal = <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">if</span> (setIsViewTask) setIsViewTask(<span class="hljs-literal">false</span>);
    <span class="hljs-comment">//this is the new line</span>
    <span class="hljs-keyword">if</span> (setSearchedTasks) setSearchedTasks([]);
    setIsOpen(<span class="hljs-literal">false</span>);
};
</code></pre>
<p>Go back into the <strong>Task.tsx</strong> file and paste this below the <code>h1</code> tag that displays the "Your Tasks" text:</p>
<pre><code class="lang-typescript">
&lt;h1 className=<span class="hljs-string">"text-4xl md:text-7xl font-bold text-center py-3 mb-16"</span>&gt;
Your Tasks
&lt;/h1&gt;
<span class="hljs-comment">//paste here</span>
&lt;div className=<span class="hljs-string">"m-8 flex flex-col-reverse md:flex-row gap-8 items-start                     md:items-center md:justify-between"</span>&gt;
    &lt;Search /&gt;
    &lt;Button
        handleClick={<span class="hljs-function">() =&gt;</span> navigate(<span class="hljs-string">"/"</span>)}
        extraBtnClasses=<span class="hljs-string">"bg-primary text-white font-medium py-2 hover:bg-    primaryHover ml-auto"</span>
    &gt;
        &lt;span&gt;Add Task&lt;/span&gt;
        &lt;PlusIcon height={<span class="hljs-number">25</span>} className=<span class="hljs-string">"hidden md:flex"</span> /&gt;
    &lt;/Button&gt;
&lt;/div&gt;
<span class="hljs-comment">//rest of code stays unchanged</span>
</code></pre>
<p>This adds the search component and a button that takes you back to the Index page when clicked.</p>
<p>Add the following within the Task component just below the useStates:</p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">const</span> [selectedTask, setSelectedTask] = useState&lt;ITask&gt;();
<span class="hljs-comment">//paste here</span>
<span class="hljs-keyword">const</span> navigate = useNavigate();
<span class="hljs-comment">//all below remain unchanged</span>
<span class="hljs-keyword">const</span> handleSelectChange = (e: React.ChangeEvent&lt;HTMLSelectElement&gt;)
</code></pre>
<p>You can now test your search functionality. It works but has one bug: if the search term is present in the both the title and the description we get back two search results. </p>
<p>To fix that, modify the <code>searchTasks</code> function in <strong>db.ts</strong> to filter out duplicate tasks by IDs like so: </p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">const</span> searchTasks = <span class="hljs-keyword">async</span> (searchTerm: <span class="hljs-built_in">string</span>) =&gt; {
    <span class="hljs-keyword">const</span> resTitle = <span class="hljs-keyword">await</span> databases.listDocuments(dbID, collectionID, [
        Query.search(<span class="hljs-string">"title"</span>, searchTerm),
    ]);
    <span class="hljs-keyword">const</span> resDesc = <span class="hljs-keyword">await</span> databases.listDocuments(dbID, collectionID, [
        Query.search(<span class="hljs-string">"description"</span>, searchTerm),
    ]);

    <span class="hljs-keyword">const</span> res = [...resTitle.documents, ...resDesc.documents];

    <span class="hljs-comment">// remove duplicate tasks</span>
    <span class="hljs-keyword">const</span> uniqueRes = res.filter(
        <span class="hljs-function">(<span class="hljs-params">task, index, self</span>) =&gt;</span> index === self.findIndex(<span class="hljs-function">(<span class="hljs-params">t</span>) =&gt;</span> t.$id ===                  task.$id)
    );

    <span class="hljs-keyword">return</span> uniqueRes;
};
</code></pre>
<p>Now your search should work as expected and should look like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/03/Screenshot-2024-03-09-at-08.00.40.png" alt="Image" width="600" height="400" loading="lazy">
<em>your pending and completed tasks displayed in the browser</em></p>
<h3 id="heading-how-to-add-ability-to-sort-tasks-via-due-date-and-priority">How to Add Ability to Sort Tasks via Due Date and Priority</h3>
<p>The application will only sort the pending tasks as it makes the most sense. It will sort by due date from the earliest date to the latest and vice versa. It will also sort by priority from lowest to highest and vice versa.</p>
<p>To get started, paste the following in the <strong>Tasks.tsx</strong> file right below the <code>h3</code> tag with the "Pending Tasks" text like so:</p>
<pre><code class="lang-typescript">
&lt;h3 className=<span class="hljs-string">"text-2xl font-bold m-8"</span>&gt;Pending Tasks&lt;/h3&gt;
<span class="hljs-comment">//paste here</span>
&lt;div className=<span class="hljs-string">"m-8 flex items-start lg:items-center gap-1 justify-between flex-col lg:flex-row"</span>&gt;
    &lt;span className=<span class="hljs-string">"font-medium"</span>&gt;Sort Tasks by: &lt;/span&gt;
    &lt;Select
        defaultSelectValue={selectArray[<span class="hljs-number">0</span>]}
        handleSelectChange={handleSelectChange}
        selectOptions={selectArray}
    /&gt;
&lt;/div&gt;
</code></pre>
<p>Then paste the following array that will contain the options for select component above. Paste it right above <code>handleViewTask</code> function like so:</p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">const</span> navigate = useNavigate();
<span class="hljs-comment">//paste here</span>
<span class="hljs-keyword">const</span> selectArray = [
    <span class="hljs-string">"priority - (low - high)"</span>,
    <span class="hljs-string">"priority - (high - low)"</span>,
    <span class="hljs-string">"due date - (earliest - latest)"</span>,
    <span class="hljs-string">"due date - (latest - earliest)"</span>,
];
<span class="hljs-comment">//rest remains unchanged</span>
<span class="hljs-keyword">const</span> handleViewTask = (...
</code></pre>
<p>Add the <code>handleSelectChange</code> and the sort functions above the <code>selectArray</code>, like so:</p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">const</span> sortByPriority = (tasksList: ITask[], isAsc: <span class="hljs-built_in">boolean</span>): ITask[] =&gt; {
    <span class="hljs-keyword">const</span> priorityOrder: { [key: <span class="hljs-built_in">string</span>]: <span class="hljs-built_in">number</span> } = {
    low: <span class="hljs-number">1</span>,
    medium: <span class="hljs-number">2</span>,
    high: <span class="hljs-number">3</span>,
    };

    <span class="hljs-keyword">return</span> [...tasksList].sort(<span class="hljs-function">(<span class="hljs-params">a, b</span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> priorityA = priorityOrder[a.priority!.toLowerCase()];
    <span class="hljs-keyword">const</span> priorityB = priorityOrder[b.priority!.toLowerCase()];
    <span class="hljs-keyword">return</span> isAsc ? priorityA - priorityB : priorityB - priorityA;
    });
};

<span class="hljs-keyword">const</span> handleSelectChange = <span class="hljs-keyword">async</span> (
    e: React.ChangeEvent&lt;HTMLSelectElement&gt;
) =&gt; {
    <span class="hljs-keyword">const</span> selectedOption = e.target.value;
    <span class="hljs-keyword">const</span> doneTasks = tasks.filter(<span class="hljs-function">(<span class="hljs-params">task</span>) =&gt;</span> task.done);

    <span class="hljs-keyword">switch</span> (selectedOption) {
        <span class="hljs-keyword">case</span> <span class="hljs-string">"priority - (low - high)"</span>:
        <span class="hljs-keyword">case</span> <span class="hljs-string">"priority - (high - low)"</span>: {
            <span class="hljs-keyword">const</span> isAsc = selectedOption === <span class="hljs-string">"priority - (low - high)"</span>;
            <span class="hljs-keyword">const</span> sortedTasks = sortByPriority(tasks, isAsc);
            setTasks([...doneTasks, ...sortedTasks.filter(<span class="hljs-function">(<span class="hljs-params">task</span>) =&gt;</span>                           !task.done)]);
            <span class="hljs-keyword">break</span>;
        }
        <span class="hljs-keyword">case</span> <span class="hljs-string">"due date - (earliest - latest)"</span>:
        <span class="hljs-keyword">case</span> <span class="hljs-string">"due date - (latest - earliest)"</span>: {
            <span class="hljs-keyword">const</span> isEarliestToLatest =
            selectedOption === <span class="hljs-string">"due date - (earliest - latest)"</span>;
            <span class="hljs-keyword">const</span> dueDateResult = <span class="hljs-keyword">await</span> sortByDueDate(isEarliestToLatest);
            <span class="hljs-keyword">const</span> sortedTasks = dueDateResult.documents <span class="hljs-keyword">as</span> ITask[];
            setTasks([...doneTasks, ...sortedTasks.filter(<span class="hljs-function">(<span class="hljs-params">task</span>) =&gt;</span>                            !task.done)]);
            <span class="hljs-keyword">break</span>;
        }
        <span class="hljs-keyword">default</span>:
            <span class="hljs-keyword">break</span>;
        }
};

<span class="hljs-comment">//below remains unchanged</span>
<span class="hljs-keyword">const</span> selectArray = .....
</code></pre>
<p>The sortByPriority function creates an object whose keys map to the priority array and gives them numerical values. This makes it easier to sort as it is hard to tell which string is higher priority without that.</p>
<p>The <code>handleSelectChange</code> function picks out the selected option and filters the tasks to get the completed ones. It does matching logic in the switch statements, calling <code>sortByPriority</code> for the cases where the user is trying to do that and it calls <code>sortByDueDate</code> for the rest of the cases. </p>
<p><code>sortByDueDate</code> is defined in the <strong>db.ts</strong> file. Open it and paste the following at the bottom of the file above the exports. Then add it to the exports list like so:</p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">const</span> sortByDueDate = <span class="hljs-keyword">async</span> (isEarliestToLatest: <span class="hljs-built_in">boolean</span>) =&gt; {
    <span class="hljs-keyword">const</span> orderQuery = isEarliestToLatest
        ? Query.orderAsc(<span class="hljs-string">"due_date"</span>)
        : Query.orderDesc(<span class="hljs-string">"due_date"</span>);
    <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> databases.listDocuments(dbID, collectionID,              [orderQuery]);
    <span class="hljs-keyword">return</span> res;
};

<span class="hljs-keyword">export</span> {
    createDocument,
    readDocuments,
    updateDocument,
    deleteDocument,
    searchTasks,
    sortByDueDate,
};
</code></pre>
<p>This function leverages Appwrites' Query methods to sort the date string according to the Boolean that is passed to it.</p>
<p>Going back to your application, run it to test the sorting functionality. The application should be sorted and the sorting should only apply to the pending tasks.</p>
<h3 id="heading-bonus-how-to-add-dark-mode-support">Bonus: How to Add Dark Mode Support</h3>
<p>The last thing left is to add Dark Mode support that respects the users' systems setting. </p>
<p>For this, open the <strong>tailwind.config.ts</strong> file and replace its contents with the following:</p>
<pre><code class="lang-typescript">
<span class="hljs-comment">/** @type {import('tailwindcss').Config} */</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> {
    content: [<span class="hljs-string">"./index.html"</span>, <span class="hljs-string">"./src/**/*.{js,ts,jsx,tsx}"</span>],
    darkMode: <span class="hljs-string">"selector"</span>,
    theme: {
        extend: {
            textColor: {
                error: <span class="hljs-string">"var(--text-error)"</span>,
                ok: <span class="hljs-string">"var(--text-ok)"</span>,
                main: <span class="hljs-string">"var(--text-main)"</span>,
                iconColor: <span class="hljs-string">"var(--btn-icon-main)"</span>,
            },
            backgroundColor: {
                base: <span class="hljs-string">"var(--base-bg)"</span>,
                primary: <span class="hljs-string">"var(--btn-bg-primary)"</span>,
                primaryHover: <span class="hljs-string">"var(--btn-bg-primary-hover)"</span>,
                ok: <span class="hljs-string">"var(--btn-bg-ok)"</span>,
                lightOk: <span class="hljs-string">"var(--btn-bg-light-ok)"</span>,
                light: <span class="hljs-string">"var(--btn-bg-light)"</span>,
                lowPriority: <span class="hljs-string">"var(--low-priority)"</span>,
                mediumPriority: <span class="hljs-string">"var(--medium-priority)"</span>,
                highPriority: <span class="hljs-string">"var(--high-priority)"</span>,
            },
            borderColor: {
                container: <span class="hljs-string">"var(--border-container)"</span>,
                input: <span class="hljs-string">"var(--border-input)"</span>,
                error: <span class="hljs-string">"var(--border-error)"</span>,
            },
        },
    },
    plugins: [],
};
</code></pre>
<p>This extends the tailwind preset colors and ties the CSS variables that were set in the <strong>index.css</strong> file to the Tailwind config. </p>
<p>In the <strong>index.css</strong> file, add this dark class below the date class like so:</p>
<pre><code class="lang-css">
<span class="hljs-selector-id">#date</span><span class="hljs-selector-pseudo">::-webkit-calendar-picker-indicator</span> {
    <span class="hljs-attribute">background-color</span>: <span class="hljs-built_in">var</span>(--btn-bg-light); 
}
//<span class="hljs-selector-tag">paste</span> <span class="hljs-selector-tag">here</span>
<span class="hljs-selector-class">.dark</span>{
    <span class="hljs-attribute">--base-bg</span>: <span class="hljs-number">#262626</span>;
    <span class="hljs-attribute">--text-main</span>: <span class="hljs-number">#ffffff</span>;
    <span class="hljs-attribute">--text-error</span>: <span class="hljs-number">#fca5a5</span>;
    <span class="hljs-attribute">--text-ok</span>: <span class="hljs-number">#86efac</span>;
    <span class="hljs-attribute">--border-input</span>: <span class="hljs-number">#e2e8f0</span>;
    <span class="hljs-attribute">--border-error</span>: <span class="hljs-number">#fca5a5</span>;
}
</code></pre>
<p>This changes some of the CSS variables values when the dark class is applied.</p>
<p>Now, open the navbar file in the components folder and replace its contents with the following:</p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">const</span> Navbar = <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> navigate = useNavigate();

    <span class="hljs-keyword">const</span> themeArray = [<span class="hljs-string">"light"</span>, <span class="hljs-string">"dark"</span>, <span class="hljs-string">"system"</span>];
    <span class="hljs-keyword">const</span> [theme, setTheme] = useState(<span class="hljs-function">() =&gt;</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-built_in">localStorage</span>.getItem(<span class="hljs-string">"theme"</span>) || themeArray[<span class="hljs-number">2</span>];
    });

    <span class="hljs-keyword">const</span> applyTheme = <span class="hljs-function">(<span class="hljs-params">selectedTheme: <span class="hljs-built_in">string</span></span>) =&gt;</span> {
        <span class="hljs-keyword">const</span> isDarkModePreferred = <span class="hljs-built_in">window</span>.matchMedia(
            <span class="hljs-string">"(prefers-color-scheme: dark)"</span>
            ).matches;

        <span class="hljs-built_in">document</span>.documentElement.classList.remove(<span class="hljs-string">"light"</span>, <span class="hljs-string">"dark"</span>);
        <span class="hljs-built_in">document</span>.documentElement.classList.add(selectedTheme);

        <span class="hljs-keyword">if</span> (selectedTheme === <span class="hljs-string">"system"</span>) {
        <span class="hljs-built_in">document</span>.documentElement.classList.toggle(<span class="hljs-string">"dark"</span>, isDarkModePreferred);
        <span class="hljs-built_in">document</span>.documentElement.classList.toggle(<span class="hljs-string">"light"</span>,                  !isDarkModePreferred);
        }
    };

    <span class="hljs-keyword">const</span> handleSelectTheme = <span class="hljs-function">(<span class="hljs-params">e: React.ChangeEvent&lt;HTMLSelectElement&gt;</span>) =&gt;</span> {
        <span class="hljs-keyword">const</span> selectedTheme = e.target.value;
        setTheme(selectedTheme);

        <span class="hljs-comment">// Store the selected theme in localStorage</span>
        <span class="hljs-built_in">localStorage</span>.setItem(<span class="hljs-string">"theme"</span>, selectedTheme);
    };

    useEffect(<span class="hljs-function">() =&gt;</span> {
        applyTheme(theme);
    }, [theme]);

    <span class="hljs-keyword">return</span> (
        &lt;nav className=<span class="hljs-string">"py-4 border-b-2 border-container shadow-md shadow-gray-400 w-full fixed top-0 bg-base"</span>&gt;
            &lt;ul className=<span class="hljs-string">"flex items-center justify-between  w-11/12 mx-auto"</span>&gt;
                &lt;Link to=<span class="hljs-string">"/"</span>&gt;
                    &lt;Button&gt;
                        &lt;span className=<span class="hljs-string">"font-semibold text-main"</span>&gt;Taskwrite&lt;/span&gt;
                        &lt;PencilIcon height={<span class="hljs-number">20</span>} className=<span class="hljs-string">"text-main"</span> /&gt;
                    &lt;/Button&gt;
                &lt;/Link&gt;
                &lt;div className=<span class="hljs-string">"flex items-center justify-between gap-6"</span>&gt;
                &lt;Link
                    to=<span class="hljs-string">"/tasks"</span>
                    className=<span class="hljs-string">"font-semibold hover:scale-105 transition duration-300 ease-in-out"</span>
                &gt;
                    View Tasks
                &lt;/Link&gt;
                &lt;div className=<span class="hljs-string">"flex gap-2 items-center"</span>&gt;
                    &lt;span className=<span class="hljs-string">"font-semibold"</span>&gt; Theme: &lt;/span&gt;
                    &lt;Select
                        defaultSelectValue={theme}
                        selectOptions={themeArray}
                        handleSelectChange={handleSelectTheme}
                    /&gt;
                &lt;/div&gt;
                &lt;/div&gt;
            &lt;/ul&gt;
        &lt;/nav&gt;
    );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Navbar;
</code></pre>
<p>Your application should now have a select in the navigation menu that successfully toggles between dark and light themes while defaulting to the system preferences when set to "System". </p>
<p>It should look like so:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/03/Screenshot-2024-03-09-at-13.21.38.png" alt="Image" width="600" height="400" loading="lazy">
<em>Taskwrite complete interface and functionalities</em></p>
<p>And Taskwrite is complete! You have successfully built a task manager application that is AI-enhanced, voice-enabled, searchable and sortable using React and Appwrite.</p>
<h2 id="heading-notes">Notes</h2>
<p>Appwrite recently announced some new features that would greatly simplify the search functionality above but, at the time of writing, these changes were not rolled out to their cloud offering. </p>
<p>The application could be further simplified by using state management solutions and this will be added to it in subsequent articles.</p>
<p>The application is live <a target="_blank" href="https://taskwrite.netlify.app/">here</a>.</p>
<h2 id="heading-limitations">Limitations</h2>
<p>The following are some known limitations and issues with this application:</p>
<ul>
<li>The navigation menu is not responsive</li>
<li>The application has no tests written</li>
<li>The permissions set for Appwrite are permissive and not recommended for production environments</li>
<li>The application could leverage <a target="_blank" href="https://appwrite.io/docs/apis/realtime">Appwrites' Realtime</a> capabilites for a smoother experience</li>
<li>The application could do with push notifications to remind the user when the task due date is coming up</li>
</ul>
<p>That said, the application is going to continue being improved and worked on. You can follow along with that on <a target="_blank" href="https://github.com/FatumaA/taskwrite">GitHub</a>. All contributions and improvements on the codebase are welcome. Please star the repository while you are at it.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Learn Next.js by Building a Custom API with MongoDB ]]>
                </title>
                <description>
                    <![CDATA[ We're excited to announce the launch of a new course on the freeCodeCamp.org YouTube channel that will give you an in-depth understanding of how to build a custom API using Next.js and MongoDB. This course was created by Hitesh Choudhary. He is an an... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/full-stack-with-nextjs-and-appwrite-course/</link>
                <guid isPermaLink="false">66b2028308bc664c3c097e85</guid>
                
                    <category>
                        <![CDATA[ Appwrite ]]>
                    </category>
                
                    <category>
                        <![CDATA[ youtube ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Beau Carnes ]]>
                </dc:creator>
                <pubDate>Thu, 27 Jul 2023 12:28:05 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/07/next.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>We're excited to announce the launch of a new course on the freeCodeCamp.org YouTube channel that will give you an in-depth understanding of how to build a custom API using Next.js and MongoDB.</p>
<p>This course was created by Hitesh Choudhary. He is an an expert in the field and has extensive experience in developing, teaching, and sharing his knowledge in an engaging and easy-to-understand manner.</p>
<h3 id="heading-about-the-course">About the Course</h3>
<p>This course will provide a comprehensive guide on building an authentication system that can register the user, login, create a secure cookie with a JWT Token, use email service, verify the user's email, forget password, middleware route protection, and much more. It's a project-based course where you learn by doing, and by the end of it, you'll have a fully functioning authentication system built from scratch.</p>
<p>The course further explores how to integrate Appwrite, a secure end-to-end backend server for Web, Mobile, and Flutter developers that makes it easy to build web and mobile applications, into your Next.js application.</p>
<p>Deployment is a crucial part of any application's lifecycle, and Hitesh makes sure not to skip that part. You will learn about deploying your Next.js application to Vercel, a cloud platform for static sites and Serverless Functions that fits perfectly with your workflow. It enables developers to host Jamstack websites and web services that deploy instantly.</p>
<h3 id="heading-what-will-you-learn">What Will You Learn</h3>
<p>The course is neatly divided into multiple sections to make it easier for learners to grasp complex topics. Here's what you will learn:</p>
<p><strong>Start, basics and Prerequisites</strong>: Before diving into the actual project, Hitesh covers the basics and the prerequisites required for the course. This is particularly helpful for beginners or for those who need a quick refresher.</p>
<p><strong>Project Structure, Diagrams, and Tech Stack</strong>: Hitesh walks you through the entire project structure, providing diagrams for a better understanding, and explains the tech stack that will be used throughout the project.</p>
<p><strong>Signup and Login</strong>: In this section, you'll start getting your hands dirty by implementing the Signup and Login functionality for your application.</p>
<p><strong>Middleware in Nextjs</strong>: Middleware is an important concept in Next.js, and you'll learn how to use it for route protection and much more.</p>
<p><strong>User Verification with Email</strong>: User verification is an essential part of any authentication system. Hitesh shows you how to implement it using an email service.</p>
<p><strong>Nextjs Deployment with Database Connectivity</strong>: You will learn how to deploy your Next.js application while maintaining the database connectivity.</p>
<p><strong>Nextjs Meets Appwrite</strong>: Introduction to Appwrite, its features, and how it can be integrated with your Next.js application.</p>
<p><strong>How to Integrate Appwrite to Nextjs</strong>: A step-by-step guide to integrate Appwrite into your Next.js application.</p>
<p><strong>Nextjs Component to Talk to Appwrite</strong>: Creating a Next.js component that can communicate with Appwrite.</p>
<p><strong>Appwrite Nextjs and Context API</strong>: Learn about using Context API in Next.js and how it can be used with Appwrite.</p>
<p><strong>What’s Next After This</strong>: Once you've built your application, Hitesh guides you on the next steps you can take to further improve your skills and the application you just built.</p>
<h3 id="heading-who-is-this-course-for">Who is this Course for?</h3>
<p>This course is designed for anyone who is passionate about web development, looking to expand their knowledge, and add a new skill to their toolkit. Whether you're a beginner or an experienced developer, this course will offer you valuable insights and practical experience.</p>
<h3 id="heading-final-thoughts">Final Thoughts</h3>
<p>We at freeCodeCamp.org are committed to providing free, quality education to everyone. We believe that this new course on our YouTube channel fits perfectly within that commitment. It’s time to set your learning goals, and join us on this journey into the exciting world of Next.js, MongoDB, and Appwrite.   </p>
<p>Watch the course on the <a target="_blank" href="https://youtu.be/ETV17M4SauU">freeCodeCamp.org YouTube channel</a> (6-hour watch).</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/ETV17M4SauU" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
