From e29c63f5cbc2ad60870fd241b8c1b150a45be60e Mon Sep 17 00:00:00 2001 From: Steve Kinney Date: Tue, 17 Sep 2024 15:50:09 -0600 Subject: [PATCH] Fix some bugs --- examples/task-list/server/tasks.js | 8 +- examples/task-list/src/create-task.tsx | 4 +- examples/task-list/src/date-time.tsx | 4 +- examples/task-list/src/task-context.tsx | 145 +++++++++++++++--------- examples/task-list/src/task.tsx | 4 +- examples/task-list/src/tasks.tsx | 4 +- 6 files changed, 105 insertions(+), 64 deletions(-) diff --git a/examples/task-list/server/tasks.js b/examples/task-list/server/tasks.js index 756512c..ef28e0c 100644 --- a/examples/task-list/server/tasks.js +++ b/examples/task-list/server/tasks.js @@ -47,7 +47,13 @@ export const updateTask = (id, updates) => { if (!task) return undefined; - Object.assign(task, updates, { lastModified: new Date() }); + for (const key in updates) { + if (updates[key] !== undefined) { + task[key] = updates[key]; + } + } + + Object.assign(task, { lastModified: new Date() }); return task; }; diff --git a/examples/task-list/src/create-task.tsx b/examples/task-list/src/create-task.tsx index 977be37..bd80370 100644 --- a/examples/task-list/src/create-task.tsx +++ b/examples/task-list/src/create-task.tsx @@ -1,12 +1,12 @@ import { useState } from 'react'; -import { useTaskContext } from './task-context'; +import { useTaskActions } from './task-context'; type CreateTaskProps = { onSubmit: (title: string) => void; }; export const CreateTask = ({ onSubmit }: CreateTaskProps) => { - const { addTask } = useTaskContext(); + const { addTask } = useTaskActions(); const [title, setTitle] = useState(''); return ( diff --git a/examples/task-list/src/date-time.tsx b/examples/task-list/src/date-time.tsx index cf5bf36..ca37067 100644 --- a/examples/task-list/src/date-time.tsx +++ b/examples/task-list/src/date-time.tsx @@ -1,10 +1,10 @@ export const DateTime = ({ date, title }: { date: Date; title: string }) => { return ( -
+

{title}

-

+

{date.toLocaleString(undefined, { dateStyle: 'short', timeStyle: 'short', diff --git a/examples/task-list/src/task-context.tsx b/examples/task-list/src/task-context.tsx index 6222470..8993252 100644 --- a/examples/task-list/src/task-context.tsx +++ b/examples/task-list/src/task-context.tsx @@ -1,9 +1,11 @@ import { createContext, useReducer, + useMemo, useEffect, type ReactNode, useContext, + useCallback, } from 'react'; import { TasksActions, taskReducer, initialState } from './task-reducer'; import type { Task } from '../types'; @@ -27,7 +29,7 @@ const TaskProvider = ({ children }: TaskProviderProps) => { const [state, dispatch] = useReducer(taskReducer, initialState); // Fetch all tasks - const fetchTasks = async () => { + const fetchTasks = useCallback(async () => { dispatch({ type: TasksActions.SET_LOADING }); try { const response = await fetch('/api/tasks'); @@ -39,66 +41,78 @@ const TaskProvider = ({ children }: TaskProviderProps) => { payload: 'Failed to fetch tasks', }); } - }; + }, [dispatch]); // Add a new task - const addTask = async (title: string) => { - dispatch({ type: TasksActions.SET_LOADING }); - try { - const response = await fetch('/api/tasks', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ title }), - }); - const data = await response.json(); - dispatch({ type: TasksActions.ADD_TASK, payload: data }); - } catch (error) { - dispatch({ type: TasksActions.SET_ERROR, payload: 'Failed to add task' }); - } - }; + const addTask = useCallback( + async (title: string) => { + dispatch({ type: TasksActions.SET_LOADING }); + try { + const response = await fetch('/api/tasks', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ title }), + }); + const data = await response.json(); + dispatch({ type: TasksActions.ADD_TASK, payload: data }); + } catch (error) { + dispatch({ + type: TasksActions.SET_ERROR, + payload: 'Failed to add task', + }); + } + }, + [dispatch], + ); // Update a task - const updateTask = async (id: string, updatedTask: Partial) => { - dispatch({ type: TasksActions.SET_LOADING }); + const updateTask = useCallback( + async (id: string, updatedTask: Partial) => { + dispatch({ type: TasksActions.SET_LOADING }); - try { - const response = await fetch(`/api/tasks/${id}`, { - method: 'PATCH', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(updatedTask), - }); + try { + const response = await fetch(`/api/tasks/${id}`, { + method: 'PATCH', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(updatedTask), + }); - if (!response.ok) { - throw new Error('Failed to update task'); + if (!response.ok) { + throw new Error('Failed to update task'); + } + + dispatch({ + type: TasksActions.UPDATE_TASK, + payload: { id, ...updatedTask }, + }); + } catch (error) { + dispatch({ + type: TasksActions.SET_ERROR, + payload: 'Failed to update task', + }); } - - dispatch({ - type: TasksActions.UPDATE_TASK, - payload: { id, ...updatedTask }, - }); - } catch (error) { - dispatch({ - type: TasksActions.SET_ERROR, - payload: 'Failed to update task', - }); - } - }; + }, + [dispatch], + ); // Delete a task - const deleteTask = async (id: string) => { - dispatch({ type: TasksActions.SET_LOADING }); - try { - await fetch(`/api/tasks/${id}`, { - method: 'DELETE', - }); - dispatch({ type: TasksActions.DELETE_TASK, payload: id }); - } catch (error) { - dispatch({ - type: TasksActions.SET_ERROR, - payload: 'Failed to delete task', - }); - } - }; + const deleteTask = useCallback( + async (id: string) => { + dispatch({ type: TasksActions.SET_LOADING }); + try { + await fetch(`/api/tasks/${id}`, { + method: 'DELETE', + }); + dispatch({ type: TasksActions.DELETE_TASK, payload: id }); + } catch (error) { + dispatch({ + type: TasksActions.SET_ERROR, + payload: 'Failed to delete task', + }); + } + }, + [dispatch], + ); useEffect(() => { fetchTasks(); @@ -120,12 +134,33 @@ const TaskProvider = ({ children }: TaskProviderProps) => { ); }; -const useTaskContext = () => { +const useTaskState = () => { const context = useContext(TaskContext); + if (!context) { throw new Error('useTaskContext must be used within a TaskProvider'); } - return context; + + return context.tasks; }; -export { TaskContext, TaskProvider, useTaskContext }; +export const useTaskActions = () => { + const context = useContext(TaskContext); + + if (!context) { + throw new Error('useTaskContext must be used within a TaskProvider'); + } + + const actions = useMemo( + () => ({ + addTask: context.addTask, + updateTask: context.updateTask, + deleteTask: context.deleteTask, + }), + [context.addTask, context.updateTask, context.deleteTask], + ); + + return actions; +}; + +export { TaskContext, TaskProvider, useTaskState }; diff --git a/examples/task-list/src/task.tsx b/examples/task-list/src/task.tsx index dea411c..e26e10a 100644 --- a/examples/task-list/src/task.tsx +++ b/examples/task-list/src/task.tsx @@ -1,12 +1,12 @@ import { DateTime } from './date-time'; -import { useTaskContext } from './task-context'; +import { useTaskActions } from './task-context'; type TaskProps = { task: import('../types').Task; }; export const Task = ({ task }: TaskProps) => { - const { updateTask, deleteTask } = useTaskContext(); + const { updateTask, deleteTask } = useTaskActions(); return (

  • diff --git a/examples/task-list/src/tasks.tsx b/examples/task-list/src/tasks.tsx index f395806..32670ba 100644 --- a/examples/task-list/src/tasks.tsx +++ b/examples/task-list/src/tasks.tsx @@ -1,8 +1,8 @@ import { Task } from './task'; -import { useTaskContext } from './task-context'; +import { useTaskState } from './task-context'; export const Tasks = () => { - const { tasks } = useTaskContext(); + const tasks = useTaskState(); return (