kaneo (minimalist kanban) fork to experiment adding a tangled integration
github.com/usekaneo/kaneo
1import { useCallback, useEffect, useRef } from "react";
2import { useForm } from "react-hook-form";
3import { useTranslation } from "react-i18next";
4
5import { Form, FormField } from "@/components/ui/form";
6import { useUpdateTaskTitle } from "@/hooks/mutations/task/use-update-task-title";
7import useGetTask from "@/hooks/queries/task/use-get-task";
8import debounce from "@/lib/debounce";
9
10type TaskTitleProps = {
11 taskId: string;
12};
13
14export default function TaskTitle({ taskId }: TaskTitleProps) {
15 const { t } = useTranslation();
16 const { data: task } = useGetTask(taskId);
17 const { mutateAsync: updateTaskTitle } = useUpdateTaskTitle();
18 const isInitializedRef = useRef(false);
19 const taskRef = useRef(task);
20 const updateTaskRef = useRef(updateTaskTitle);
21
22 useEffect(() => {
23 taskRef.current = task;
24 updateTaskRef.current = updateTaskTitle;
25 }, [task, updateTaskTitle]);
26
27 // biome-ignore lint/correctness/useExhaustiveDependencies: taskId is not needed here
28 useEffect(() => {
29 isInitializedRef.current = false;
30 }, [taskId]);
31
32 const form = useForm<{
33 title: string;
34 }>({
35 values: {
36 title: task?.title || "",
37 },
38 });
39
40 useEffect(() => {
41 if (task?.title !== undefined) isInitializedRef.current = true;
42 }, [task?.title]);
43
44 const debouncedUpdate = useCallback(
45 debounce(async (title: string) => {
46 if (!isInitializedRef.current) return;
47
48 const currentTask = taskRef.current;
49 const updateTaskFn = updateTaskRef.current;
50
51 if (!currentTask || !updateTaskFn) return;
52
53 try {
54 await updateTaskFn({
55 ...currentTask,
56 title,
57 });
58 } catch (error) {
59 console.error("Failed to update title:", error);
60 }
61 }, 800),
62 [],
63 );
64
65 const handleTitleChange = useCallback(
66 (value: string) => {
67 if (!isInitializedRef.current) return;
68
69 debouncedUpdate(value);
70 },
71 [debouncedUpdate],
72 );
73
74 return (
75 <Form {...form}>
76 <FormField
77 control={form.control}
78 name="title"
79 render={({ field }) => (
80 <input
81 {...field}
82 type="text"
83 placeholder={t("tasks:detail.titlePlaceholder")}
84 className="block h-auto w-full appearance-none border-0 bg-transparent p-0 font-heading text-[2rem] leading-[1.15] font-semibold tracking-[-0.02em] text-foreground outline-none placeholder:text-foreground/45"
85 onChange={(e) => {
86 field.onChange(e);
87 handleTitleChange(e.target.value);
88 }}
89 />
90 )}
91 />
92 </Form>
93 );
94}