From 5b162671e7f8ef8bd6139a05bea583c80f8d6ee5 Mon Sep 17 00:00:00 2001 From: Diego Lavayen Alarcon Date: Thu, 7 Apr 2022 21:43:55 +0100 Subject: [PATCH 01/10] fix: update API endpoint --- .env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env b/.env index 5bb4b74..50bf507 100644 --- a/.env +++ b/.env @@ -1 +1 @@ -REACT_APP_API_ENDPOINT=https://dev.jsonplaceholder.typicode.com/todos +REACT_APP_API_ENDPOINT=https://jsonplaceholder.typicode.com/todos From 324ebb03e7515726cdc2b0624f1d7aa057926282 Mon Sep 17 00:00:00 2001 From: Diego Lavayen Alarcon Date: Thu, 7 Apr 2022 22:25:02 +0100 Subject: [PATCH 02/10] feat: lift PUT logic to the App component (turning Task component into a controlled component) --- src/App.js | 20 ++++++++++++++++++-- src/components/Task.js | 24 +++++++----------------- src/components/TaskList.js | 4 ++-- src/hooks/useFetch.js | 6 +++++- 4 files changed, 32 insertions(+), 22 deletions(-) diff --git a/src/App.js b/src/App.js index 8f4e8de..c2d8e3f 100644 --- a/src/App.js +++ b/src/App.js @@ -5,13 +5,29 @@ import TaskList from './components/TaskList'; import { useFetch } from './hooks/useFetch'; const App = () => { - const [state, tasks] = useFetch(process.env.REACT_APP_API_ENDPOINT); + const [state, tasks, setTasks] = useFetch(process.env.REACT_APP_API_ENDPOINT); + + const updateTask = (updatedTask) => { + fetch(`${process.env.REACT_APP_API_ENDPOINT}/${updatedTask.id}`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(updatedTask), + }); + + setTasks((previous) => { + return previous.map((task) => + task.id === updatedTask.id ? updatedTask : task, + ); + }); + }; return ( <>

TODO List

{state === 'PENDING' && } - {state === 'SUCCESS' && } + {state === 'SUCCESS' && ( + + )} {state === 'FAILED' && Something went wrong} ); diff --git a/src/components/Task.js b/src/components/Task.js index e205dae..af8c84d 100644 --- a/src/components/Task.js +++ b/src/components/Task.js @@ -1,21 +1,11 @@ -import { useState } from 'react'; - -const Task = ({ task }) => { - const [item, setItem] = useState(task); - - const inputId = `task-${item.id}`; +const Task = ({ task, updateTask }) => { + const inputId = `task-${task.id}`; const handleInputChange = (event) => { - setItem({ - ...item, + updateTask({ + ...task, completed: event.target.checked, }); - - fetch(`${process.env.REACT_APP_API_ENDPOINT}/${item.id}`, { - method: 'PUT', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ completed: event.target.checked }), - }); }; return ( @@ -23,14 +13,14 @@ const Task = ({ task }) => { ); diff --git a/src/components/TaskList.js b/src/components/TaskList.js index 613a3d7..8c00476 100644 --- a/src/components/TaskList.js +++ b/src/components/TaskList.js @@ -1,10 +1,10 @@ import Task from './Task'; -const TaskList = ({ tasks }) => { +const TaskList = ({ tasks, updateTask }) => { return (
    {tasks.map((task) => ( - + ))}
); diff --git a/src/hooks/useFetch.js b/src/hooks/useFetch.js index ccc34dc..5bc005c 100644 --- a/src/hooks/useFetch.js +++ b/src/hooks/useFetch.js @@ -5,6 +5,10 @@ export const useFetch = (url) => { const [requestState, setRequestState] = useState('INITIAL'); useEffect(() => { + if (!url) { + return; + } + setRequestState('PENDING'); fetch(url) @@ -16,5 +20,5 @@ export const useFetch = (url) => { .catch(() => setRequestState('FAILED')); }, [url]); - return [requestState, requestResponse]; + return [requestState, requestResponse, setRequestResponse]; }; From 882d43e41e7e2aef8ffc158762f0c6215e1f6586 Mon Sep 17 00:00:00 2001 From: Diego Lavayen Alarcon Date: Thu, 7 Apr 2022 22:31:31 +0100 Subject: [PATCH 03/10] feat: move update logic to `useFetch` (and make it generic enough) --- src/App.js | 16 +++------------- src/hooks/useFetch.js | 14 +++++++++++++- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/App.js b/src/App.js index c2d8e3f..05ede17 100644 --- a/src/App.js +++ b/src/App.js @@ -5,20 +5,10 @@ import TaskList from './components/TaskList'; import { useFetch } from './hooks/useFetch'; const App = () => { - const [state, tasks, setTasks] = useFetch(process.env.REACT_APP_API_ENDPOINT); + const [state, tasks, update] = useFetch(process.env.REACT_APP_API_ENDPOINT); - const updateTask = (updatedTask) => { - fetch(`${process.env.REACT_APP_API_ENDPOINT}/${updatedTask.id}`, { - method: 'PUT', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(updatedTask), - }); - - setTasks((previous) => { - return previous.map((task) => - task.id === updatedTask.id ? updatedTask : task, - ); - }); + const updateTask = (task) => { + update(task.id, task); }; return ( diff --git a/src/hooks/useFetch.js b/src/hooks/useFetch.js index 5bc005c..5bec031 100644 --- a/src/hooks/useFetch.js +++ b/src/hooks/useFetch.js @@ -20,5 +20,17 @@ export const useFetch = (url) => { .catch(() => setRequestState('FAILED')); }, [url]); - return [requestState, requestResponse, setRequestResponse]; + const updateRequest = (id, request) => { + fetch(`${process.env.REACT_APP_API_ENDPOINT}/${id}`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(request), + }); + + setRequestResponse((previous) => { + return previous.map((object) => (object.id === id ? request : object)); + }); + }; + + return [requestState, requestResponse, updateRequest]; }; From ca57baa1d3e39f5969d38f91423215441b81d928 Mon Sep 17 00:00:00 2001 From: Diego Lavayen Alarcon Date: Thu, 7 Apr 2022 22:48:50 +0100 Subject: [PATCH 04/10] feat: make `updateRequest` async & update method to PATCH --- src/hooks/useFetch.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/hooks/useFetch.js b/src/hooks/useFetch.js index 5bec031..37a1be3 100644 --- a/src/hooks/useFetch.js +++ b/src/hooks/useFetch.js @@ -20,9 +20,9 @@ export const useFetch = (url) => { .catch(() => setRequestState('FAILED')); }, [url]); - const updateRequest = (id, request) => { - fetch(`${process.env.REACT_APP_API_ENDPOINT}/${id}`, { - method: 'PUT', + const updateRequest = async (id, request) => { + await fetch(`${process.env.REACT_APP_API_ENDPOINT}/${id}`, { + method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(request), }); From 9ed7163bcf538a64409562fbc8a6d9ab6d0e28f1 Mon Sep 17 00:00:00 2001 From: Diego Lavayen Alarcon Date: Thu, 7 Apr 2022 22:49:22 +0100 Subject: [PATCH 05/10] feat: include method to add (post) a task --- src/hooks/useFetch.js | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/hooks/useFetch.js b/src/hooks/useFetch.js index 37a1be3..a927484 100644 --- a/src/hooks/useFetch.js +++ b/src/hooks/useFetch.js @@ -32,5 +32,18 @@ export const useFetch = (url) => { }); }; - return [requestState, requestResponse, updateRequest]; + const addRequest = async (request) => { + const response = await fetch(`${process.env.REACT_APP_API_ENDPOINT}`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(request), + }); + const json = await response.json(); + + setRequestResponse((previous) => { + return [...previous, json]; + }); + }; + + return [requestState, requestResponse, updateRequest, addRequest]; }; From 58d7d5d5e026194de9ea73d358d4af06894d9182 Mon Sep 17 00:00:00 2001 From: Diego Lavayen Alarcon Date: Thu, 7 Apr 2022 22:50:15 +0100 Subject: [PATCH 06/10] chore: method names housekeeping --- src/hooks/useFetch.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/hooks/useFetch.js b/src/hooks/useFetch.js index a927484..06010f0 100644 --- a/src/hooks/useFetch.js +++ b/src/hooks/useFetch.js @@ -20,7 +20,7 @@ export const useFetch = (url) => { .catch(() => setRequestState('FAILED')); }, [url]); - const updateRequest = async (id, request) => { + const patchRequest = async (id, request) => { await fetch(`${process.env.REACT_APP_API_ENDPOINT}/${id}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, @@ -32,7 +32,7 @@ export const useFetch = (url) => { }); }; - const addRequest = async (request) => { + const postRequest = async (request) => { const response = await fetch(`${process.env.REACT_APP_API_ENDPOINT}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, @@ -45,5 +45,5 @@ export const useFetch = (url) => { }); }; - return [requestState, requestResponse, updateRequest, addRequest]; + return [requestState, requestResponse, patchRequest, postRequest]; }; From 73d0dd42a151bb46bcd7538543c9cf8dfdd472cf Mon Sep 17 00:00:00 2001 From: Diego Lavayen Alarcon Date: Thu, 7 Apr 2022 22:50:32 +0100 Subject: [PATCH 07/10] feat: include method to add a new task --- src/App.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/App.js b/src/App.js index 05ede17..3940c2d 100644 --- a/src/App.js +++ b/src/App.js @@ -5,18 +5,24 @@ import TaskList from './components/TaskList'; import { useFetch } from './hooks/useFetch'; const App = () => { - const [state, tasks, update] = useFetch(process.env.REACT_APP_API_ENDPOINT); + const [state, tasks, update, add] = useFetch( + process.env.REACT_APP_API_ENDPOINT, + ); const updateTask = (task) => { update(task.id, task); }; + const addTask = (title) => { + add({ title, completed: false }); + }; + return ( <>

TODO List

{state === 'PENDING' && } {state === 'SUCCESS' && ( - + )} {state === 'FAILED' && Something went wrong} From 6f01b9a79a3b1478974d0a6953fd6f3ef9bef508 Mon Sep 17 00:00:00 2001 From: Diego Lavayen Alarcon Date: Thu, 7 Apr 2022 22:50:48 +0100 Subject: [PATCH 08/10] feat: add controlled form to add a new task --- src/components/TaskList.js | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/components/TaskList.js b/src/components/TaskList.js index 8c00476..1a12f53 100644 --- a/src/components/TaskList.js +++ b/src/components/TaskList.js @@ -1,11 +1,34 @@ +import { useState } from 'react'; import Task from './Task'; -const TaskList = ({ tasks, updateTask }) => { +const TaskList = ({ tasks, updateTask, addTask }) => { + const [task, setTask] = useState(''); + + const handleInputChange = (event) => { + setTask(event.target.value); + }; + + const handleSubmit = (event) => { + event.preventDefault(); + addTask(task); + setTask(''); + }; + return (
    {tasks.map((task) => ( ))} +
  • +
    + +
    +
); }; From 999fd08172601a6191fce2d729237aa515a796de Mon Sep 17 00:00:00 2001 From: Diego Lavayen Alarcon Date: Thu, 7 Apr 2022 22:57:09 +0100 Subject: [PATCH 09/10] feat: move input logic into a custom hook --- src/components/TaskList.js | 8 ++++---- src/hooks/useInput.js | 17 +++++++++++++++++ 2 files changed, 21 insertions(+), 4 deletions(-) create mode 100644 src/hooks/useInput.js diff --git a/src/components/TaskList.js b/src/components/TaskList.js index 1a12f53..89d755b 100644 --- a/src/components/TaskList.js +++ b/src/components/TaskList.js @@ -1,17 +1,17 @@ -import { useState } from 'react'; import Task from './Task'; +import { useInput } from '../hooks/useInput'; const TaskList = ({ tasks, updateTask, addTask }) => { - const [task, setTask] = useState(''); + const [{ task }, update, reset] = useInput({ task: '' }); const handleInputChange = (event) => { - setTask(event.target.value); + update(event); }; const handleSubmit = (event) => { event.preventDefault(); addTask(task); - setTask(''); + reset(); }; return ( diff --git a/src/hooks/useInput.js b/src/hooks/useInput.js new file mode 100644 index 0000000..310af22 --- /dev/null +++ b/src/hooks/useInput.js @@ -0,0 +1,17 @@ +import { useState } from 'react'; + +export const useInput = (initialValues) => { + const [input, setInput] = useState(initialValues); + + const update = (event) => { + setInput((previous) => { + return { ...previous, [event.target.name]: event.target.value }; + }); + }; + + const reset = () => { + setInput(initialValues); + }; + + return [input, update, reset]; +}; From 780d9ddd880d77fcc8dbb34dae967c58dd09141c Mon Sep 17 00:00:00 2001 From: Diego Lavayen Alarcon Date: Thu, 7 Apr 2022 23:00:09 +0100 Subject: [PATCH 10/10] feat: move form to its own component --- src/components/TaskForm.js | 28 ++++++++++++++++++++++++++++ src/components/TaskList.js | 23 ++--------------------- 2 files changed, 30 insertions(+), 21 deletions(-) create mode 100644 src/components/TaskForm.js diff --git a/src/components/TaskForm.js b/src/components/TaskForm.js new file mode 100644 index 0000000..dbca0a2 --- /dev/null +++ b/src/components/TaskForm.js @@ -0,0 +1,28 @@ +import { useInput } from '../hooks/useInput'; + +const TaskForm = ({ addTask }) => { + const [{ task }, update, reset] = useInput({ task: '' }); + + const handleInputChange = (event) => { + update(event); + }; + + const handleSubmit = (event) => { + event.preventDefault(); + addTask(task); + reset(); + }; + + return ( +
+ +
+ ); +}; + +export default TaskForm; diff --git a/src/components/TaskList.js b/src/components/TaskList.js index 89d755b..2e63cef 100644 --- a/src/components/TaskList.js +++ b/src/components/TaskList.js @@ -1,33 +1,14 @@ import Task from './Task'; -import { useInput } from '../hooks/useInput'; +import TaskForm from './TaskForm'; const TaskList = ({ tasks, updateTask, addTask }) => { - const [{ task }, update, reset] = useInput({ task: '' }); - - const handleInputChange = (event) => { - update(event); - }; - - const handleSubmit = (event) => { - event.preventDefault(); - addTask(task); - reset(); - }; - return (
    {tasks.map((task) => ( ))}
  • -
    - -
    +
);