diff --git a/src/CONST.js b/src/CONST.js
index 369049a6e79d..2f8bd398b35c 100755
--- a/src/CONST.js
+++ b/src/CONST.js
@@ -463,6 +463,7 @@ const CONST = {
                 CLOSED: 'CLOSED',
                 CREATED: 'CREATED',
                 TASKEDITED: 'TASKEDITED',
+                TASKCANCELED: 'TASKCANCELED',
                 IOU: 'IOU',
                 RENAMED: 'RENAMED',
                 CHRONOSOOOLIST: 'CHRONOSOOOLIST',
diff --git a/src/components/ReportActionItem/TaskAction.js b/src/components/ReportActionItem/TaskAction.js
index f1e94b0b5ed9..1406c78956f7 100644
--- a/src/components/ReportActionItem/TaskAction.js
+++ b/src/components/ReportActionItem/TaskAction.js
@@ -54,6 +54,9 @@ const TaskAction = (props) => {
         case CONST.REPORT.ACTIONS.TYPE.TASKCOMPLETED:
             messageLinkText = props.translate('task.messages.completed');
             break;
+        case CONST.REPORT.ACTIONS.TYPE.TASKCANCELED:
+            messageLinkText = props.translate('task.messages.canceled');
+            break;
         case CONST.REPORT.ACTIONS.TYPE.TASKREOPENED:
             messageLinkText = props.translate('task.messages.reopened');
             break;
diff --git a/src/components/ReportActionItem/TaskPreview.js b/src/components/ReportActionItem/TaskPreview.js
index 394ca5d9e366..ae2c5accab26 100644
--- a/src/components/ReportActionItem/TaskPreview.js
+++ b/src/components/ReportActionItem/TaskPreview.js
@@ -69,6 +69,7 @@ const TaskPreview = (props) => {
                     style={[styles.mr2]}
                     containerStyle={[styles.taskCheckbox]}
                     isChecked={isTaskCompleted}
+                    disabled={TaskUtils.isTaskCanceled(props.taskReport)}
                     onPress={() => {
                         if (isTaskCompleted) {
                             TaskUtils.reopenTask(props.taskReportID, parentReportID, taskTitle);
diff --git a/src/components/TaskHeader.js b/src/components/TaskHeader.js
index 5b2c8825d0eb..474a009693ee 100644
--- a/src/components/TaskHeader.js
+++ b/src/components/TaskHeader.js
@@ -87,6 +87,7 @@ function TaskHeader(props) {
                                 ) : (
                                     <Button
                                         success
+                                        isDisabled={TaskUtils.isTaskCanceled(props.report)}
                                         medium
                                         text={props.translate('newTaskPage.markAsDone')}
                                         onPress={() => TaskUtils.completeTask(props.report.reportID, parentReportID, title)}
diff --git a/src/languages/en.js b/src/languages/en.js
index b2093577957a..c4f2b1181312 100755
--- a/src/languages/en.js
+++ b/src/languages/en.js
@@ -1198,6 +1198,7 @@ export default {
         completed: 'Completed',
         messages: {
             completed: 'Completed task',
+            canceled: 'Canceled task',
             reopened: 'Reopened task',
         },
     },
diff --git a/src/languages/es.js b/src/languages/es.js
index 51041e2d839a..ce49199f88b6 100644
--- a/src/languages/es.js
+++ b/src/languages/es.js
@@ -1203,6 +1203,7 @@ export default {
         completed: 'Completada',
         messages: {
             completed: 'Tarea completada',
+            canceled: 'Tarea cancelada',
             reopened: 'Tarea reabrir',
         },
     },
diff --git a/src/libs/ReportActionsUtils.js b/src/libs/ReportActionsUtils.js
index 075df72e2243..20062514675c 100644
--- a/src/libs/ReportActionsUtils.js
+++ b/src/libs/ReportActionsUtils.js
@@ -240,7 +240,11 @@ function shouldReportActionBeVisible(reportAction, key) {
     }
 
     // Filter out any unsupported reportAction types
-    if (!_.has(CONST.REPORT.ACTIONS.TYPE, reportAction.actionName) && !_.contains(_.values(CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG), reportAction.actionName)) {
+    if (
+        !_.has(CONST.REPORT.ACTIONS.TYPE, reportAction.actionName) &&
+        !_.contains(_.values(CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG), reportAction.actionName) &&
+        !_.contains(_.values(CONST.REPORT.ACTIONS.TYPE.TASK), reportAction.actionName)
+    ) {
         return false;
     }
 
diff --git a/src/libs/actions/Task.js b/src/libs/actions/Task.js
index 1dfa8cfc7393..0de8dd3bb30c 100644
--- a/src/libs/actions/Task.js
+++ b/src/libs/actions/Task.js
@@ -7,8 +7,8 @@ import * as ReportUtils from '../ReportUtils';
 import * as Report from './Report';
 import Navigation from '../Navigation/Navigation';
 import ROUTES from '../../ROUTES';
-import DateUtils from '../DateUtils';
 import CONST from '../../CONST';
+import DateUtils from '../DateUtils';
 
 /**
  * Clears out the task info from the store
@@ -502,6 +502,71 @@ function getShareDestination(reportID, reports, personalDetails) {
     };
 }
 
+/**
+ * Cancels a task by setting the report state to SUBMITTED and status to CLOSED
+ * @param {string} taskReportID
+ * @param {string} parentReportID
+ * @param {string} taskTitle
+ * @param {number} originalStateNum
+ * @param {number} originalStatusNum
+ */
+function cancelTask(taskReportID, parentReportID, taskTitle, originalStateNum, originalStatusNum) {
+    const message = `Canceled task: ${taskTitle}`;
+    const optimisticCancelReportAction = ReportUtils.buildOptimisticTaskReportAction(taskReportID, CONST.REPORT.ACTIONS.TYPE.TASKCANCELED, message);
+    const optimisticReportActionID = optimisticCancelReportAction.reportActionID;
+
+    const optimisticData = [
+        {
+            onyxMethod: Onyx.METHOD.MERGE,
+            key: `${ONYXKEYS.COLLECTION.REPORT}${taskReportID}`,
+            value: {
+                stateNum: CONST.REPORT.STATE_NUM.SUBMITTED,
+                statusNum: CONST.REPORT.STATUS.CLOSED,
+            },
+        },
+        {
+            onyxMethod: Onyx.METHOD.MERGE,
+            key: `${ONYXKEYS.COLLECTION.REPORT}${parentReportID}`,
+            value: {
+                lastVisibleActionCreated: optimisticCancelReportAction.created,
+                lastMessageText: message,
+                lastActorEmail: optimisticCancelReportAction.actorEmail,
+            },
+        },
+        {
+            onyxMethod: Onyx.METHOD.MERGE,
+            key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReportID}`,
+            value: {
+                [optimisticReportActionID]: optimisticCancelReportAction,
+            },
+        },
+    ];
+
+    const failureData = [
+        {
+            onyxMethod: Onyx.METHOD.MERGE,
+            key: `${ONYXKEYS.COLLECTION.REPORT}${taskReportID}`,
+            value: {
+                stateNum: originalStateNum,
+                statusNum: originalStatusNum,
+            },
+        },
+        {
+            onyxMethod: Onyx.METHOD.MERGE,
+            key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReportID}`,
+            value: {
+                [optimisticReportActionID]: null,
+            },
+        },
+    ];
+
+    API.write('CancelTask', {taskReportID, optimisticReportActionID}, {optimisticData, failureData});
+}
+
+function isTaskCanceled(taskReport) {
+    return taskReport.stateNum === CONST.REPORT.STATE_NUM.SUBMITTED && taskReport.statusNum === CONST.REPORT.STATUS.CLOSED;
+}
+
 export {
     createTaskAndNavigate,
     editTaskAndNavigate,
@@ -517,4 +582,6 @@ export {
     clearOutTaskInfoAndNavigate,
     getAssignee,
     getShareDestination,
+    cancelTask,
+    isTaskCanceled,
 };
diff --git a/src/pages/home/HeaderView.js b/src/pages/home/HeaderView.js
index 2ed0b337aaae..47d62048b980 100644
--- a/src/pages/home/HeaderView.js
+++ b/src/pages/home/HeaderView.js
@@ -26,8 +26,8 @@ import colors from '../../styles/colors';
 import reportPropTypes from '../reportPropTypes';
 import ONYXKEYS from '../../ONYXKEYS';
 import ThreeDotsMenu from '../../components/ThreeDotsMenu';
+import * as Task from '../../libs/actions/Task';
 import reportActionPropTypes from './report/reportActionPropTypes';
-import * as TaskUtils from '../../libs/actions/Task';
 
 const propTypes = {
     /** Toggles the navigationMenu open and closed */
@@ -86,15 +86,13 @@ const HeaderView = (props) => {
     // We hide the button when we are chatting with an automated Expensify account since it's not possible to contact
     // these users via alternative means. It is possible to request a call with Concierge so we leave the option for them.
     const shouldShowCallButton = (isConcierge && guideCalendarLink) || (!isAutomatedExpensifyAccount && !isTaskReport);
-    const shouldShowThreeDotsButton = isTaskReport;
     const threeDotMenuItems = [];
-
-    if (shouldShowThreeDotsButton) {
+    if (isTaskReport) {
         if (props.report.stateNum === CONST.REPORT.STATE_NUM.OPEN && props.report.statusNum === CONST.REPORT.STATUS.OPEN) {
             threeDotMenuItems.push({
                 icon: Expensicons.Checkmark,
                 text: props.translate('newTaskPage.markAsDone'),
-                onSelected: () => TaskUtils.completeTask(props.report.reportID, props.report.parentReportID, title),
+                onSelected: () => Task.completeTask(props.report.reportID, props.report.parentReportID, title),
             });
         }
 
@@ -103,7 +101,7 @@ const HeaderView = (props) => {
             threeDotMenuItems.push({
                 icon: Expensicons.Checkmark,
                 text: props.translate('newTaskPage.markAsIncomplete'),
-                onSelected: () => TaskUtils.reopenTask(props.report.reportID, props.report.parentReportID, title),
+                onSelected: () => Task.reopenTask(props.report.reportID, props.report.parentReportID, title),
             });
         }
 
@@ -112,12 +110,11 @@ const HeaderView = (props) => {
             threeDotMenuItems.push({
                 icon: Expensicons.Trashcan,
                 text: props.translate('common.cancel'),
-
-                // Implementing in https://github.com/Expensify/App/issues/16857
-                onSelected: () => {},
+                onSelected: () => Task.cancelTask(props.report.reportID, props.report.parentReportID, props.report.reportName, props.report.stateNum, props.report.statusNum),
             });
         }
     }
+    const shouldShowThreeDotsButton = !!threeDotMenuItems.length;
 
     const avatarTooltip = isChatRoom ? undefined : _.pluck(displayNamesWithTooltips, 'tooltip');
     const shouldShowSubscript = isPolicyExpenseChat && !props.report.isOwnPolicyExpenseChat && !ReportUtils.isArchivedRoom(props.report) && !isTaskReport;
@@ -231,7 +228,11 @@ export default compose(
     withOnyx({
         account: {
             key: ONYXKEYS.ACCOUNT,
-            selector: (account) => account && {guideCalendarLink: account.guideCalendarLink},
+            selector: (account) =>
+                account && {
+                    guideCalendarLink: account.guideCalendarLink,
+                    primaryLogin: account.primaryLogin,
+                },
         },
         parentReportActions: {
             key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.parentReportID}`,
diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js
index c2c342e332ac..d6514e0ad91f 100644
--- a/src/pages/home/report/ReportActionItem.js
+++ b/src/pages/home/report/ReportActionItem.js
@@ -204,7 +204,7 @@ class ReportActionItem extends Component {
                     checkIfContextMenuActive={this.checkIfContextMenuActive}
                 />
             );
-        } else if (this.props.action.actionName === CONST.REPORT.ACTIONS.TYPE.TASKCOMPLETED) {
+        } else if (this.props.action.actionName === CONST.REPORT.ACTIONS.TYPE.TASKCOMPLETED || this.props.action.actionName === CONST.REPORT.ACTIONS.TYPE.TASKCANCELED) {
             children = (
                 <TaskAction
                     taskReportID={this.props.action.originalMessage.taskReportID.toString()}
