From a236268348916d69ca93b5392bfec0cc262f2fa1 Mon Sep 17 00:00:00 2001 From: Tsuyoshi Maeda Date: Mon, 25 Jul 2022 12:29:06 +0900 Subject: [PATCH 1/2] Imple todo functionality but need refactor. --- package-lock.json | 29 +++++++- package.json | 6 +- src/App.tsx | 165 ++++++++++++++++++++++++++++++++++++++-------- src/main.tsx | 9 ++- 4 files changed, 174 insertions(+), 35 deletions(-) diff --git a/package-lock.json b/package-lock.json index 885e3a5..3eceb9f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,11 +9,13 @@ "version": "0.0.0", "dependencies": { "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "uuid": "^8.3.2" }, "devDependencies": { "@types/react": "^18.0.15", "@types/react-dom": "^18.0.6", + "@types/uuid": "^8.3.4", "@vitejs/plugin-react": "^2.0.0", "typescript": "^4.6.4", "vite": "^3.0.0" @@ -503,6 +505,12 @@ "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==", "dev": true }, + "node_modules/@types/uuid": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz", + "integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==", + "dev": true + }, "node_modules/@vitejs/plugin-react": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-2.0.0.tgz", @@ -1377,6 +1385,14 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/vite": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/vite/-/vite-3.0.2.tgz", @@ -1785,6 +1801,12 @@ "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==", "dev": true }, + "@types/uuid": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz", + "integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==", + "dev": true + }, "@vitejs/plugin-react": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-2.0.0.tgz", @@ -2299,6 +2321,11 @@ "picocolors": "^1.0.0" } }, + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" + }, "vite": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/vite/-/vite-3.0.2.tgz", diff --git a/package.json b/package.json index 2738130..38062f7 100644 --- a/package.json +++ b/package.json @@ -10,13 +10,15 @@ }, "dependencies": { "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "uuid": "^8.3.2" }, "devDependencies": { "@types/react": "^18.0.15", "@types/react-dom": "^18.0.6", + "@types/uuid": "^8.3.4", "@vitejs/plugin-react": "^2.0.0", "typescript": "^4.6.4", "vite": "^3.0.0" } -} \ No newline at end of file +} diff --git a/src/App.tsx b/src/App.tsx index cd20136..f28e395 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,34 +1,145 @@ -import { useState } from 'react' -import reactLogo from './assets/react.svg' -import './App.css' +import { useState, FC } from 'react'; +import { v4 as uuidv4 } from 'uuid'; function App() { - const [count, setCount] = useState(0) + const [todos, setTodos] = useState([]); + + const onSubmit = (todo: Todo) => { + setTodos([...todos, todo]); + }; + + const onChangeTodoCompleted = (updateTodoInput: TodoUpdateInput) => { + const updatedTodos = todos.map((todo) => { + if (todo.id !== updateTodoInput.id) return todo; + if (updateTodoInput.completed === undefined) return todo; + + return { + ...todo, + completed: updateTodoInput.completed, + }; + }); + + setTodos(updatedTodos); + }; return ( -
-
- - Vite logo - - - React logo - -
-

Vite + React

-
- -

- Edit src/App.tsx and save to test HMR -

-
-

- Click on the Vite and React logos to learn more -

+
+

簡易Todoアプリ

+ +
+
- ) + ); } -export default App +type Todo = { + id: string; + title: string; + completed: boolean; +}; + +// Pickの参考記事 +// https://typescriptbook.jp/reference/type-reuse/utility-types/pick +type TodoInput = Pick; + +// Partialの参考記事 +// https://typescriptbook.jp/reference/type-reuse/utility-types/partial +type TodoUpdateInput = Pick & Partial; + +type TodoFormProps = { + onSubmit: (todo: Todo) => void; +}; + +const TodoForm: FC = ({ onSubmit }) => { + const [input, setInput] = useState({ + title: '', + }); + + const onSubmitHandler: React.FormEventHandler = (event) => { + event.preventDefault(); + if (!input.title) { + alert('タイトルを入力してください'); + return; + } + + const todo = createTodo(input); + onSubmit(todo); + setInput({ + title: '', + }); + }; + + const onChangeHandler: React.ChangeEventHandler = ( + event + ) => { + setInput({ + title: event.target.value, + }); + }; + + return ( +
+ + + +
+ ); +}; + +type TodoListProps = { + todos: Todo[]; + onChangeCompleted: (updateInput: TodoUpdateInput) => void; +}; + +const TodoList: FC = ({ todos, onChangeCompleted }) => { + return ( + + + + + + + + + + {todos.map((todo) => { + const { id, completed, title } = todo; + return ( + + + + + + ); + })} + +
idタイトル進捗
{id}{title} + { + onChangeCompleted({ + ...todo, + completed: event.target.checked, + }); + }} + /> +
+ ); +}; + +const createTodo = (input: TodoInput): Todo => { + return { + // https://github.com/uuidjs/uuid#uuidv4options-buffer-offset + id: uuidv4(), + title: input.title, + completed: false, + }; +}; + +export default App; diff --git a/src/main.tsx b/src/main.tsx index 611e848..abd10b8 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,10 +1,9 @@ -import React from 'react' -import ReactDOM from 'react-dom/client' -import App from './App' -import './index.css' +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import App from './App'; ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( -) +); From a14b13e8585bcee72d69697caa09da54e400f397 Mon Sep 17 00:00:00 2001 From: Tsuyoshi Maeda Date: Mon, 25 Jul 2022 12:47:16 +0900 Subject: [PATCH 2/2] Refactored Todo feature. --- src/App.tsx | 142 +-------------------- src/features/todos/components/TodoForm.tsx | 48 +++++++ src/features/todos/components/TodoList.tsx | 44 +++++++ src/features/todos/crud/create.ts | 11 ++ src/features/todos/pages/TodoPage.tsx | 35 +++++ src/features/todos/types.ts | 13 ++ 6 files changed, 155 insertions(+), 138 deletions(-) create mode 100644 src/features/todos/components/TodoForm.tsx create mode 100644 src/features/todos/components/TodoList.tsx create mode 100644 src/features/todos/crud/create.ts create mode 100644 src/features/todos/pages/TodoPage.tsx create mode 100644 src/features/todos/types.ts diff --git a/src/App.tsx b/src/App.tsx index f28e395..b68b336 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,145 +1,11 @@ -import { useState, FC } from 'react'; -import { v4 as uuidv4 } from 'uuid'; +import { TodoPage } from './features/todos/pages/TodoPage'; function App() { - const [todos, setTodos] = useState([]); - - const onSubmit = (todo: Todo) => { - setTodos([...todos, todo]); - }; - - const onChangeTodoCompleted = (updateTodoInput: TodoUpdateInput) => { - const updatedTodos = todos.map((todo) => { - if (todo.id !== updateTodoInput.id) return todo; - if (updateTodoInput.completed === undefined) return todo; - - return { - ...todo, - completed: updateTodoInput.completed, - }; - }); - - setTodos(updatedTodos); - }; - return ( -
-

簡易Todoアプリ

- -
- -
+ <> + + ); } -type Todo = { - id: string; - title: string; - completed: boolean; -}; - -// Pickの参考記事 -// https://typescriptbook.jp/reference/type-reuse/utility-types/pick -type TodoInput = Pick; - -// Partialの参考記事 -// https://typescriptbook.jp/reference/type-reuse/utility-types/partial -type TodoUpdateInput = Pick & Partial; - -type TodoFormProps = { - onSubmit: (todo: Todo) => void; -}; - -const TodoForm: FC = ({ onSubmit }) => { - const [input, setInput] = useState({ - title: '', - }); - - const onSubmitHandler: React.FormEventHandler = (event) => { - event.preventDefault(); - if (!input.title) { - alert('タイトルを入力してください'); - return; - } - - const todo = createTodo(input); - onSubmit(todo); - setInput({ - title: '', - }); - }; - - const onChangeHandler: React.ChangeEventHandler = ( - event - ) => { - setInput({ - title: event.target.value, - }); - }; - - return ( -
- - - -
- ); -}; - -type TodoListProps = { - todos: Todo[]; - onChangeCompleted: (updateInput: TodoUpdateInput) => void; -}; - -const TodoList: FC = ({ todos, onChangeCompleted }) => { - return ( - - - - - - - - - - {todos.map((todo) => { - const { id, completed, title } = todo; - return ( - - - - - - ); - })} - -
idタイトル進捗
{id}{title} - { - onChangeCompleted({ - ...todo, - completed: event.target.checked, - }); - }} - /> -
- ); -}; - -const createTodo = (input: TodoInput): Todo => { - return { - // https://github.com/uuidjs/uuid#uuidv4options-buffer-offset - id: uuidv4(), - title: input.title, - completed: false, - }; -}; - export default App; diff --git a/src/features/todos/components/TodoForm.tsx b/src/features/todos/components/TodoForm.tsx new file mode 100644 index 0000000..b82f8d6 --- /dev/null +++ b/src/features/todos/components/TodoForm.tsx @@ -0,0 +1,48 @@ +import { useState, FC } from 'react'; +import { createTodo } from '../crud/create'; +import type { Todo, TodoInput } from '../types'; + +type Props = { + onSubmit: (todo: Todo) => void; +}; + +export const TodoForm: FC = ({ onSubmit }) => { + const [input, setInput] = useState({ + title: '', + }); + + const onSubmitHandler: React.FormEventHandler = (event) => { + event.preventDefault(); + if (!input.title) { + alert('タイトルを入力してください'); + return; + } + + const todo = createTodo(input); + onSubmit(todo); + setInput({ + title: '', + }); + }; + + const onChangeHandler: React.ChangeEventHandler = ( + event + ) => { + setInput({ + title: event.target.value, + }); + }; + + return ( +
+ + + +
+ ); +}; diff --git a/src/features/todos/components/TodoList.tsx b/src/features/todos/components/TodoList.tsx new file mode 100644 index 0000000..348a74d --- /dev/null +++ b/src/features/todos/components/TodoList.tsx @@ -0,0 +1,44 @@ +import type { FC } from 'react'; +import type { Todo, TodoUpdateInput } from '../types'; + +type TodoListProps = { + todos: Todo[]; + onChangeCompleted: (updateInput: TodoUpdateInput) => void; +}; + +export const TodoList: FC = ({ todos, onChangeCompleted }) => { + return ( + + + + + + + + + + {todos.map((todo) => { + const { id, completed, title } = todo; + return ( + + + + + + ); + })} + +
idタイトル進捗
{id}{title} + { + onChangeCompleted({ + ...todo, + completed: event.target.checked, + }); + }} + /> +
+ ); +}; diff --git a/src/features/todos/crud/create.ts b/src/features/todos/crud/create.ts new file mode 100644 index 0000000..4a00899 --- /dev/null +++ b/src/features/todos/crud/create.ts @@ -0,0 +1,11 @@ +import { v4 as uuidv4 } from 'uuid'; +import type { Todo, TodoInput } from '../types'; + +export const createTodo = (input: TodoInput): Todo => { + return { + // https://github.com/uuidjs/uuid#uuidv4options-buffer-offset + id: uuidv4(), + title: input.title, + completed: false, + }; +}; diff --git a/src/features/todos/pages/TodoPage.tsx b/src/features/todos/pages/TodoPage.tsx new file mode 100644 index 0000000..00ee7f1 --- /dev/null +++ b/src/features/todos/pages/TodoPage.tsx @@ -0,0 +1,35 @@ +import { FC, useState } from 'react'; +import { TodoForm } from '../components/TodoForm'; +import { TodoList } from '../components/TodoList'; +import { Todo, TodoUpdateInput } from '../types'; + +export const TodoPage: FC = () => { + const [todos, setTodos] = useState([]); + + const onSubmit = (todo: Todo) => { + setTodos([...todos, todo]); + }; + + const onChangeTodoCompleted = (updateTodoInput: TodoUpdateInput) => { + const updatedTodos = todos.map((todo) => { + if (todo.id !== updateTodoInput.id) return todo; + if (updateTodoInput.completed === undefined) return todo; + + return { + ...todo, + completed: updateTodoInput.completed, + }; + }); + + setTodos(updatedTodos); + }; + + return ( +
+

簡易Todoアプリ

+ +
+ +
+ ); +}; diff --git a/src/features/todos/types.ts b/src/features/todos/types.ts new file mode 100644 index 0000000..c327c25 --- /dev/null +++ b/src/features/todos/types.ts @@ -0,0 +1,13 @@ +export type Todo = { + id: string; + title: string; + completed: boolean; +}; + +// Pickの参考記事 +// https://typescriptbook.jp/reference/type-reuse/utility-types/pick +export type TodoInput = Pick; + +// Partialの参考記事 +// https://typescriptbook.jp/reference/type-reuse/utility-types/partial +export type TodoUpdateInput = Pick & Partial;