Article directory
-
- 1. Project launch: project initialization and configuration
- 2. React and Hook application: implement the project list
- 3. TS Application: JS God Assist - Strong Type
- 4. JWT, user authentication and asynchronous request
- 5. CSS is actually very simple - add styles with CSS-in-JS
- 6. User experience optimization - loading and error state handling
- 7. Hook, routing, and URL state management
- 8. User selector and item editing function
- 9. In-depth React state management and Redux mechanism
- 10. Use react-query to get data and manage cache
- 11. Kanban page and task group page development
Source of learning content: React + React Hook + TS Best Practice - MOOC
Compared with the original tutorial, I used the latest version at the beginning of my study (2023.03):
item | Version |
---|---|
react & react-dom | ^18.2.0 |
react-router & react-router-dom | ^6.11.2 |
antd | ^4.24.8 |
@commitlint/cli & @commitlint/config-conventional | ^17.4.4 |
eslint-config-prettier | ^8.6.0 |
husky | ^8.0.3 |
lint-staged | ^13.1.2 |
prettier | 2.8.4 |
json-server | 0.17.2 |
craco-less | ^2.0.0 |
@craco/craco | ^7.1.0 |
qs | ^6.11.0 |
dayjs | ^1.11.7 |
react-helmet | ^6.1.0 |
@types/react-helmet | ^6.1.6 |
react-query | ^6.1.0 |
@welldone-software/why-did-you-render | ^7.0.1 |
@emotion/react & @emotion/styled | ^11.10.6 |
The specific configuration, operation and content will be different, and the "pit" will also be different. . .
1. Project launch: project initialization and configuration
2. React and Hook application: implement the project list
3. TS Application: JS God Assist - Strong Type
4. JWT, user authentication and asynchronous request
5. CSS is actually very simple - add styles with CSS-in-JS
6. User experience optimization - loading and error state handling
7. Hook, routing, and URL state management
8. User selector and item editing function
9. In-depth React state management and Redux mechanism
10. Use react-query to get data and manage cache
11. Kanban page and task group page development
1~3
4~6
7. Edit task function
Next, create a component for editing tasks:
If you are ready to call the editing task interface and obtain task details Hook
, edit src\utils\task.ts
:
...
import {
useAddConfig, useEditConfig } from "./use-optimistic-options";
export const useEditTask = (queryKey: QueryKey) => {
const client = useHttp();
return useMutation(
(params: Partial<Task>) =>
client(`tasks/${
params.id}`, {
method: "PATCH",
data: params,
}),
useEditConfig(queryKey)
);
};
export const useTask = (id?: number) => {
const client = useHttp();
return useQuery<Task>(["task", id], () => client(`tasks/${
id}`), {
enabled: Boolean(id),
});
};
EDIT src\screens\ViewBoard\utils.ts
(added useTasksModal
):
...
// import { useDebounce } from "utils";
import {
useTask } from "utils/task";
...
export const useTasksSearchParams = () => {
const [param] = useUrlQueryParam([
"name",
"typeId",
"processorId",
"tagId",
]);
const projectId = useProjectIdInUrl();
// const debouncedName = useDebounce(param.name)
return useMemo(
() => ({
projectId,
typeId: Number(param.typeId) || undefined,
processorId: Number(param.processorId) || undefined,
tagId: Number(param.tagId) || undefined,
// name: debouncedName,
name: param.name,
}),
// [projectId, param, debouncedName]
[projectId, param]
);
};
...
export const useTasksModal = () => {
const [{
editingTaskId }, setEditingTaskId] = useUrlQueryParam(['editingTaskId'])
const {
data: editingTask, isLoading } = useTask(Number(editingTaskId))
const startEdit = useCallback((id: number) => {
setEditingTaskId({
editingTaskId: id})
}, [setEditingTaskId])
const close = useCallback(() => {
setEditingTaskId({
editingTaskId: ''})
}, [setEditingTaskId])
return {
editingTaskId,
editingTask,
startEdit,
close,
isLoading
}
}
The use of in the video
useDebounce
makes the search start after the input is completely stopped, avoiding the waste of system resources caused by frequent searches during the input process, and affecting the user experience. After the blogger makes such changes, the Chinese input method cannot be used normally. . . Follow up
New component: src\screens\ViewBoard\components\taskModal.tsx
:
import {
useForm } from "antd/lib/form/Form"
import {
useTasksModal, useTasksQueryKey } from "../utils"
import {
useEditTask } from "utils/task"
import {
useEffect } from "react"
import {
Form, Input, Modal } from "antd"
import {
UserSelect } from "components/user-select"
import {
TaskTypeSelect } from "components/task-type-select"
const layout = {
labelCol: {
span: 8},
wrapperCol: {
span: 16}
}
export const TaskModal = () => {
const [form] = useForm()
const {
editingTaskId, editingTask, close } = useTasksModal()
const {
mutateAsync: editTask, isLoading: editLoading } = useEditTask(useTasksQueryKey())
const onCancel = () => {
close()
form.resetFields()
}
const onOk = async () => {
await editTask({
...editingTask, ...form.getFieldsValue()})
close()
}
useEffect(() => {
form.setFieldsValue(editingTask)
}, [form, editingTask])
return <Modal
forceRender={
true}
onCancel={
onCancel}
onOk={
onOk}
okText={
"确认"}
cancelText={
"取消"}
confirmLoading={
editLoading}
title={
"编辑任务"}
open={
!!editingTaskId}
>
<Form {
...layout} initialValues={
editingTask} form={
form}>
<Form.Item
label={
"任务名"}
name={
"name"}
rules={
[{
required: true, message: "请输入任务名" }]}
>
<Input />
</Form.Item>
<Form.Item label={
"经办人"} name={
"processorId"}>
<UserSelect defaultOptionName={
"经办人"} />
</Form.Item>
<Form.Item label={
"类型"} name={
"typeId"}>
<TaskTypeSelect />
</Form.Item>
</Form>
</Modal>
}
Note: Same
Drawer
as , whenModal
using theuseForm()
extractedform
bound in the componentForm
, you need to addforceRender
the property, otherwise when the page is opened, if the binding is not available, an error will be reported . useForm' is not connected to any Form element. Forget…
EDIT: src\screens\ViewBoard\index.tsx
(introduced TaskModal
):
...
import {
TaskModal } from "./components/taskModal";
export const ViewBoard = () => {
...
return (
<ViewContainer>
...
<TaskModal/>
</ViewContainer>
);
};
...
EDIT: src\screens\ViewBoard\components\ViewboardCloumn.tsx
(introduced useTasksModal
so that clicking on a task card opens for TaskModal
editing):
...
import {
useTasksModal, useTasksSearchParams } from "../utils";
...
export const ViewboardColumn = ({
viewboard }: {
viewboard: Viewboard }) => {
...
const {
startEdit } = useTasksModal()
return (
<Container>
...
<TasksContainer>
{
tasks?.map((task) => (
<Card onClick={
() => startEdit(task.id)} style={
{
marginBottom: "0.5rem", cursor: 'pointer' }} key={
task.id}>
...
</Card>
))}
...
</TasksContainer>
</Container>
);
};
...
View the functions and effects, click on the task card to TaskModal
appear, edit and confirm to see the modified task (using optimistic update, completely unfeeling):
8. Kanban and task deletion function
Next, implement a small function to highlight keywords in the search results
New src\screens\ViewBoard\components\mark.tsx
:
export const Mark = ({
name, keyword}: {
name: string, keyword: string}) => {
if(!keyword) {
return <>{
name}</>
}
const arr = name.split(keyword)
return <>
{
arr.map((str, index) => <span key={
index}>
{
str}
{
index === arr.length -1 ? null : <span style={
{
color: '#257AFD' }}>{
keyword}</span>
}
</span>)
}
</>
}
EDIT src\screens\ViewBoard\components\ViewboardCloumn.tsx
(introduced Task
and TaskCard
extracted separately):
...
import {
Task } from "types/Task";
import {
Mark } from "./mark";
...
const TaskCard = ({
task}: {
task: Task}) => {
const {
startEdit } = useTasksModal();
const {
name: keyword } = useTasksSearchParams()
return <Card
onClick={
() => startEdit(task.id)}
style={
{
marginBottom: "0.5rem", cursor: "pointer" }}
key={
task.id}
>
<p>
<Mark keyword={
keyword} name={
task.name}/>
</p>
<TaskTypeIcon id={
task.id} />
</Card>
}
export const ViewboardColumn = ({
viewboard }: {
viewboard: Viewboard }) => {
const {
data: allTasks } = useTasks(useTasksSearchParams());
const tasks = allTasks?.filter((task) => task.kanbanId === viewboard.id);
return (
<Container>
<h3>{
viewboard.name}</h3>
<TasksContainer>
{
tasks?.map((task) => <TaskCard task={
task}/>)}
<CreateTask kanbanId={
viewboard.id} />
</TasksContainer>
</Container>
);
};
...
View the effect:
Let's start developing the delete function
EDIT src\utils\viewboard.ts
(created and exported useDeleteViewBoard
):
...
export const useDeleteViewBoard = (queryKey: QueryKey) => {
const client = useHttp();
return useMutation(
(id?: number) =>
client(`kanbans/${
id}`, {
method: "DELETE",
}),
useDeleteConfig(queryKey)
);
};
EDIT src\screens\ViewBoard\components\ViewboardCloumn.tsx
:
...
import {
Button, Card, Dropdown, MenuProps, Modal, Row } from "antd";
import {
useDeleteViewBoard } from "utils/viewboard";
...
export const ViewboardColumn = ({
viewboard }: {
viewboard: Viewboard }) => {
const {
data: allTasks } = useTasks(useTasksSearchParams());
const tasks = allTasks?.filter((task) => task.kanbanId === viewboard.id);
return (
<Container>
<Row>
<h3>{
viewboard.name}</h3>
<More viewboard={
viewboard}/>
</Row>
<TasksContainer>
{
tasks?.map((task) => <TaskCard task={
task}/>)}
<CreateTask kanbanId={
viewboard.id} />
</TasksContainer>
</Container>
);
};
const More = ({
viewboard }: {
viewboard: Viewboard }) => {
const {
mutateAsync: deleteViewBoard} = useDeleteViewBoard(useViewBoardQueryKey())
const startDelete = () => {
Modal.confirm({
okText: '确定',
cancelText: '取消',
title: '确定删除看板吗?',
onOk() {
deleteViewBoard(viewboard.id)
}
})
}
const items: MenuProps["items"] = [
{
key: 1,
label: "删除",
onClick: startDelete,
},
];
return <Dropdown menu={
{
items }}>
<Button type="link" onClick={
(e) => e.preventDefault()}>
...
</Button>
</Dropdown>
}
...
Test to delete the Kanban, the function is normal
The following is the delete task function
EDIT src\utils\task.ts
(created and exported useDeleteTask
):
...
export const useDeleteTask = (queryKey: QueryKey) => {
const client = useHttp();
return useMutation(
(id?: number) =>
client(`tasks/${
id}`, {
method: "DELETE",
}),
useDeleteConfig(queryKey)
);
};
EDIT src\screens\ViewBoard\components\taskModal.tsx
:
...
import {
useDeleteTask, useEditTask } from "utils/task";
export const TaskModal = () => {
...
const {
mutateAsync: deleteTask } = useDeleteTask(useTasksQueryKey());
...
const startDelete = () => {
close();
Modal.confirm({
okText: '确定',
cancelText: '取消',
title: '确定删除看板吗?',
onOk() {
deleteTask(Number(editingTaskId));
}
})
}
return (
<Modal {
...}>
<Form {
...}>
...
</Form>
<div style={
{
textAlign: 'right' }}>
<Button style={
{
fontSize: '14px'}} size="small" onClick={
startDelete}>删除</Button>
</div>
</Modal>
);
};
Test to delete the task, the function is normal
Some reference notes are still in draft stage, so stay tuned. . .