You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
90 lines
2.1 KiB
90 lines
2.1 KiB
import { useEffect, useMemo, useRef, useState } from "react";
|
|
import AsyncStorage from "@react-native-async-storage/async-storage";
|
|
|
|
export type Todo = {
|
|
id: string;
|
|
title: string;
|
|
done: boolean;
|
|
createdAt: number;
|
|
};
|
|
|
|
const STORAGE_KEY = "todos.v1";
|
|
const DEBOUNCE_MS = 300;
|
|
|
|
export function useTodos() {
|
|
const [todos, setTodos] = useState<Todo[]>([]);
|
|
const timer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
|
|
// load once
|
|
useEffect(() => {
|
|
(async () => {
|
|
const raw = await AsyncStorage.getItem(STORAGE_KEY);
|
|
if (raw) setTodos(JSON.parse(raw));
|
|
})();
|
|
}, []);
|
|
|
|
// debounce save
|
|
useEffect(() => {
|
|
if (timer.current) clearTimeout(timer.current);
|
|
timer.current = setTimeout(() => {
|
|
AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(todos)).catch((e) => {
|
|
console.warn("Debounce warn: ", e);
|
|
});
|
|
}, DEBOUNCE_MS);
|
|
|
|
return () => {
|
|
if (timer.current) clearTimeout(timer.current);
|
|
};
|
|
}, [todos]);
|
|
|
|
const activeTodos = useMemo(() => todos.filter((t) => !t.done), [todos]);
|
|
const doneTodos = useMemo(() => todos.filter((t) => t.done), [todos]);
|
|
const leftCount = useMemo(() => activeTodos.length, [activeTodos]);
|
|
|
|
const add = (title: string) => {
|
|
const t = title.trim();
|
|
if (!t) return;
|
|
const id = `${Date.now}-${Math.random().toString(36).slice(2, 8)}`;
|
|
setTodos([
|
|
{
|
|
id,
|
|
title: t,
|
|
done: false,
|
|
createdAt: Date.now(),
|
|
},
|
|
...todos,
|
|
]);
|
|
};
|
|
|
|
const toggle = (id: string) =>
|
|
setTodos(
|
|
todos.map((it) =>
|
|
it.id === id
|
|
? {
|
|
...it,
|
|
done: !it.done,
|
|
}
|
|
: it
|
|
)
|
|
);
|
|
|
|
const remove = (id: string) => setTodos(todos.filter((it) => it.id !== id));
|
|
|
|
const clearDone = () => setTodos(todos.filter((it) => !it.done));
|
|
|
|
const completeAll = () =>
|
|
setTodos(todos.map((it) => ({ ...it, done: true })));
|
|
|
|
return {
|
|
todos,
|
|
setTodos,
|
|
activeTodos,
|
|
doneTodos,
|
|
leftCount,
|
|
add,
|
|
toggle,
|
|
remove,
|
|
clearDone,
|
|
completeAll,
|
|
};
|
|
}
|
|
|