diff --git a/package-lock.json b/package-lock.json index 994fe64..b8a36c8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,8 @@ "express": "^4.18.1", "helmet": "^6.0.0", "heroku-ssl-redirect": "^0.1.1", + "mobx": "^6.6.2", + "mobx-react-lite": "^3.4.0", "pg": "^8.8.0", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -2455,6 +2457,36 @@ "node": ">=10" } }, + "node_modules/mobx": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/mobx/-/mobx-6.6.2.tgz", + "integrity": "sha512-IOpS0bf3+hXIhDIy+CmlNMBfFpAbHS0aVHcNC+xH/TFYEKIIVDKNYRh9eKlXuVfJ1iRKAp0cRVmO145CyJAMVQ==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mobx" + } + }, + "node_modules/mobx-react-lite": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/mobx-react-lite/-/mobx-react-lite-3.4.0.tgz", + "integrity": "sha512-bRuZp3C0itgLKHu/VNxi66DN/XVkQG7xtoBVWxpvC5FhAqbOCP21+nPhULjnzEqd7xBMybp6KwytdUpZKEgpIQ==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mobx" + }, + "peerDependencies": { + "mobx": "^6.1.0", + "react": "^16.8.0 || ^17 || ^18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -5257,6 +5289,17 @@ "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", "dev": true }, + "mobx": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/mobx/-/mobx-6.6.2.tgz", + "integrity": "sha512-IOpS0bf3+hXIhDIy+CmlNMBfFpAbHS0aVHcNC+xH/TFYEKIIVDKNYRh9eKlXuVfJ1iRKAp0cRVmO145CyJAMVQ==" + }, + "mobx-react-lite": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/mobx-react-lite/-/mobx-react-lite-3.4.0.tgz", + "integrity": "sha512-bRuZp3C0itgLKHu/VNxi66DN/XVkQG7xtoBVWxpvC5FhAqbOCP21+nPhULjnzEqd7xBMybp6KwytdUpZKEgpIQ==", + "requires": {} + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", diff --git a/package.json b/package.json index 047efab..3403f47 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,8 @@ "express": "^4.18.1", "helmet": "^6.0.0", "heroku-ssl-redirect": "^0.1.1", + "mobx": "^6.6.2", + "mobx-react-lite": "^3.4.0", "pg": "^8.8.0", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/src/App.tsx b/src/App.tsx index 10f565d..ba06943 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,87 +1,43 @@ -import { useEffect, useState } from "react"; -import { remult } from "remult"; -import { Task } from "./shared/Task"; -import { TasksController } from "./shared/TasksController"; +import { useEffect } from "react"; +import { observer } from "mobx-react-lite"; +import { action } from 'mobx'; +import { Store } from "./Store"; -const taskRepo = remult.repo(Task); - -async function fetchTasks(hideCompleted: boolean) { - return taskRepo.find({ - limit: 20, - orderBy: { completed: "asc" }, - where: { completed: hideCompleted ? false : undefined } - }); -} - - - -function App() { - const [tasks, setTasks] = useState([]); - const [hideCompleted, setHideCompleted] = useState(false); +export const store = new Store(); +const App = observer(() => { useEffect(() => { - fetchTasks(hideCompleted).then(setTasks); - }, [hideCompleted]); - - const addTask = () => { - setTasks([...tasks, new Task()]) - }; - const setAll = async (completed: boolean) => { - await TasksController.setAll(completed); - setTasks(await fetchTasks(hideCompleted)); - } - + store.loadTasks() + }, [store.hideCompleted]); return (
setHideCompleted(e.target.checked)} /> Hide Completed + checked={store.hideCompleted} + onChange={action(e => store.hideCompleted = e.target.checked)} /> Hide Completed
- {tasks.map(task => { - const handleChange = (values: Partial) => { - setTasks(tasks.map(t => t === task ? { ...task, ...values } : t)); - }; - - const saveTask = async () => { - try { - const savedTask = await taskRepo.save(task); - setTasks(tasks.map(t => t === task ? savedTask : t)); - } catch (error: any) { - alert(error.message); - } - } - - - const deleteTask = async () => { - await taskRepo.delete(task); - setTasks(tasks.filter(t => t !== task)); - }; - + {store.tasks.map(task => { return (
handleChange({ completed: e.target.checked })} /> + onChange={action(e => task.completed = e.target.checked)} /> handleChange({ title: e.target.value })} /> - - + onChange={action(e => task.title = e.target.value)} /> + +
); })} -
- +
- - + +
-
); -} - +}); export default App; diff --git a/src/Store.ts b/src/Store.ts new file mode 100644 index 0000000..08e1915 --- /dev/null +++ b/src/Store.ts @@ -0,0 +1,43 @@ +import { remult } from "remult"; +import { Task } from "./shared/Task"; +import { TasksController } from "./shared/TasksController"; +import { makeAutoObservable, runInAction } from 'mobx'; +import { store } from "./App"; + +export class Store { + taskRepo = remult.repo(Task); + tasks: Task[] = []; + hideCompleted = false; + constructor() { + makeAutoObservable(this); + } + replaceTasks(tasks: Task[]) { + this.tasks = tasks; + } + async loadTasks() { + this.replaceTasks(await this.taskRepo.find({ + limit: 20, + orderBy: { completed: "asc" }, + where: { completed: this.hideCompleted ? false : undefined } + })); + } + addTask() { + this.tasks.push(new Task()); + } + async saveTask(task: Task) { + try { + const savedTask = await this.taskRepo.save(task); + runInAction(() => store.tasks[store.tasks.indexOf(task)] = savedTask); + } catch (error: any) { + alert(error.message); + } + } + async deleteTask(task: Task) { + await this.taskRepo.delete(task); + runInAction(() => this.tasks = this.tasks.filter(t => t !== task)); + } + async setAll(completed: boolean) { + await TasksController.setAll(completed); + this.loadTasks(); + } +} diff --git a/src/shared/Task.ts b/src/shared/Task.ts index 3c7c3bb..720b89d 100644 --- a/src/shared/Task.ts +++ b/src/shared/Task.ts @@ -1,3 +1,4 @@ +import { makeAutoObservable } from "mobx"; import { Allow, Entity, Fields, Validators } from "remult"; import { Roles } from "./Roles"; @@ -8,6 +9,9 @@ import { Roles } from "./Roles"; allowApiDelete: Roles.admin }) export class Task { + constructor(){ + makeAutoObservable(this); + } @Fields.uuid() id!: string; @@ -19,4 +23,4 @@ export class Task { @Fields.boolean() completed = false; -} +} \ No newline at end of file