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.
 
 
LiteTodo/lite.todo/store/useTodos.ts

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,
};
}