This is a solution to the Todo app challenge on Frontend Mentor. Frontend Mentor challenges help you improve your coding skills by building realistic projects.
Users should be able to:
I learnt how to modify the inputs autofilled styles, this was needed to keep the inputs looking appropiate for the dark and light mode.
html.dark input:-webkit-autofill,
input:-webkit-autofill:hover,
input:-webkit-autofill:focus {
-webkit-text-fill-color: white;
-webkit-box-shadow: 0 0 0px 1000px hsl(235, 24%, 19%) inset;
transition: background-color 5000s ease-in-out 0s;
}
html.light input:-webkit-autofill,
input:-webkit-autofill:hover,
input:-webkit-autofill:focus {
-webkit-text-fill-color: black;
-webkit-box-shadow: 0 0 0px 1000px hsl(236, 33%, 92%) inset;
transition: background-color 5000s ease-in-out 0s;
}
I also had some practice building custom hooks for handling the todos, all I have to do is initialise it once and the entire app has access to it! No need to import a bloated and convoluted state management library!
export function useTodos(initialValue: Todo[], localStorageKey: string) {
const Todos: Writable<Todo[]> = writable<Todo[]>([], (set) => {
const value = localStorage.getItem(localStorageKey);
if (value) {
const parsed = JSON.parse(value);
if (isArrayTodos(parsed)) initialValue = parsed;
}
set(initialValue);
return Todos.subscribe((todos) => saveTodos(todos, localStorageKey));
});
const Filter = writable<Filter>('ALL');
function useShow(filter: Filter) {
return () => Filter.set(filter);
}
return {
subscribe: Todos.subscribe,
filter: makeReadable(Filter),
filtered: derived([Todos, Filter], ([todos, filter]) => {
switch (filter) {
case 'ACTIVE':
return todos.filter(({ isCompleted }) => !isCompleted);
case 'ALL':
return todos;
case 'COMPLETED':
return todos.filter(({ isCompleted }) => isCompleted);
default:
return todos;
}
}),
left: derived(Todos, (todos) =>
todos.reduce((total, { isCompleted }) => total + (isCompleted ? 0 : 1), 0)
),
add(value: string, isCompleted = false) {
if (isEmpty(value)) return;
Todos.update((todos) => {
return [{ value, isCompleted, id: Date.now() }, ...todos];
});
},
toggle(id: number, isCompleted: boolean) {
Todos.update((todos) => {
const todo = todos.find((todo) => todo.id === id);
if (todo) todo.isCompleted = !isCompleted;
return todos;
});
},
clear() {
Todos.update((todos) => todos.filter((todo) => !todo.isCompleted));
},
delete(id: number) {
Todos.update((todos) => todos.filter((todo) => todo.id !== id));
},
set: Todos.set,
filterBy: {
active: useShow('ACTIVE'),
all: useShow('ALL'),
completed: useShow('COMPLETED'),
},
};
}