In this lesson, we will create TypeScript types to model the API response.
Let's take a look at the API response for listing a task.
Visit List task API
We can see the response is of the following shape.
{
"columns": {
"pending": {
"id": "pending",
"title": "Pending",
"taskIDs": ["2"]
},
"in_progress": {
"id": "in_progress",
"title": "In progress",
"taskIDs": ["1"]
},
"done": {
"id": "done",
"title": "Done",
"taskIDs": []
}
},
"tasks": {
"1": {
"id": 1,
"title": "Sample Task",
"description": "Sample description about the task which is to be completed",
"dueDate": "",
"state": "in_progress",
"assignee": null,
"assignedUserName": null
},
"2": {
"id": 2,
"title": "Another Sample Task",
"description": "Sample description about the task which is to be completed",
"dueDate": "",
"state": "pending",
"assignee": null,
"assignedUserName": null
}
},
"columnOrder": ["pending", "in_progress", "done"]
}
We have three columns i.e., pending
, in_progress
, and done
. We have columnOrder
, which can be used to control in what order the lists must be rendered. Then we have the tasks which can be accessed using their id
.
Now, let's create types to model this shape.
We will use a union
type to model the columns.
Open src/context/task/types.ts
file in VS Code and add the following entry to it.
export type AvailableColumns = "pending" | "in_progress" | "done";
Each entry in a column, i.e.,
{
"id": "pending",
"title": "Pending",
"taskIDs": ["2"]
}
...can be modelled as follows.
export type ColumnData = {
id: string;
title: string;
taskIDs: string[];
};
Then the whole column
key in the response can be modelled as:
export type Columns = {
[k in AvailableColumns]: ColumnData;
};
Task details can be modelled as:
export type TaskDetails = {
id: number;
title: string;
description: string;
dueDate: string;
state: AvailableColumns;
assignee?: number;
assignedUserName?: string;
};
We can rewrite the TaskDetailsPayload
to re-use the TaskDetails
type. TypeScript provides a utility type called Omit
, which helps in creating a new type by list of attributes of an already existing type. We just need to discard id
assignee
, state
from TaskDetails
type.
export type TaskDetailsPayload = Omit<TaskDetails, "id" | "assignee" | "state">;
Tasks in the response can then be modelled as
export type Tasks = {
[k: string]: TaskDetails;
};
Finally, the entire API response can be modelled as:
export type ProjectData = {
tasks: Tasks;
columns: Columns;
columnOrder: AvailableColumns[];
};
We will use the useReducer
hook to manage the state. So, we have already defined TaskListState
as the state's type. In the state, we have the API response, then some flags whether the data is currently loading or has errored etc. We will also add the ProjectData
type to it.
export interface TaskListState {
projectData: ProjectData;
isLoading: boolean;
isError: boolean;
errorMessage: string;
}
You can also use a type
here instead of an interface
.
At first, we will work with static data and make sure our drag and drop works. Let's create a file initialData.ts
in src/context/task
folder with following content.
import { ProjectData } from "./types";
const initialData: ProjectData = {
columns: {
pending: {
id: "pending",
title: "Pending",
taskIDs: ["2"],
},
in_progress: {
id: "in_progress",
title: "In progress",
taskIDs: ["1"],
},
done: {
id: "done",
title: "Done",
taskIDs: [],
},
},
tasks: {
"1": {
id: 1,
title: "Sample Task",
description: "Sample description about the task which is to be completed",
dueDate: "",
state: "in_progress",
assignee: undefined,
assignedUserName: undefined,
},
"2": {
id: 2,
title: "Another Sample Task",
description: "Sample description about the task which is to be completed",
dueDate: "",
state: "pending",
assignee: undefined,
assignedUserName: undefined,
},
},
columnOrder: ["pending", "in_progress", "done"],
};
export default initialData;
Now, we will use this initial state in our taskReducer
. Let's open src/context/task/reducer.ts
and update the initial state.
import { Reducer } from "react";
import projectData from "./initialData";
import { ProjectData, TaskDetails, TaskListState } from "./types";
// Define the initial state
export const initialState: TaskListState = {
projectData: projectData,
isLoading: false,
isError: false,
errorMessage: "",
};
We have now defined an initial state. Next, we need to update the actions available and the reducer itself.
Switch to types.ts
and add an entry REORDER_TASKS
We use an enum
to model the available actions so that we don't deal with any magic strings
export enum TaskListAvailableAction {
CREATE_TASK_REQUEST = "CREATE_TASK_REQUEST",
CREATE_TASK_SUCCESS = "CREATE_TASK_SUCCESS",
CREATE_TASK_FAILURE = "CREATE_TASK_FAILURE",
REORDER_TASKS = "REORDER_TASKS",
}
// Define the action types and payload
export type TaskActions =
| { type: TaskListAvailableAction.CREATE_TASK_REQUEST }
| { type: TaskListAvailableAction.CREATE_TASK_SUCCESS }
| { type: TaskListAvailableAction.CREATE_TASK_FAILURE; payload: string }
| { type: TaskListAvailableAction.REORDER_TASKS; payload: ProjectData };
We will add the action REORDER_TASKS
, which will have a payload of type ProjectData
with updated orderings of tasks.
Next we will update the reducer itself.
export const taskReducer: Reducer<TaskListState, TaskActions> = (
state = initialState,
action
) => {
switch (action.type) {
case TaskListAvailableAction.CREATE_TASK_REQUEST:
return { ...state, isLoading: true };
case TaskListAvailableAction.CREATE_TASK_SUCCESS:
return { ...state, isLoading: false };
case TaskListAvailableAction.CREATE_TASK_FAILURE:
return {
...state,
isLoading: false,
isError: true,
errorMessage: action.payload,
};
case TaskListAvailableAction.REORDER_TASKS:
return { ...state, isLoading: false, projectData: action.payload };
default:
return state;
}
};
We will update the state with new ordering whenever REORDER_TASKS
action is dispatched.
To actually invoke it, we need to add a reorderTasks
function in action.ts
. Let's open src/context/task/action.ts
and add it.
export const reorderTasks = (
dispatch: TasksDispatch,
newState: ProjectData
) => {
dispatch({ type: TaskListAvailableAction.REORDER_TASKS, payload: newState });
};
See you in the next lesson.