1- import React , { useState } from 'react' ;
1+ import React , { useEffect , useState } from 'react' ;
22import type { Todo } from '../types/Todo' ;
33import { v4 as uuidv4 } from 'uuid' ;
44import { TodoContext } from './TodoContextType' ;
5+ import { loadTodos , saveTodos } from '../utils/sessionStorage' ;
56
7+ /**
8+ * Provider responsible for Todo state management.
9+ * Adds sessionStorage hydration / persistence with basic error handling.
10+ */
611export const TodoProvider : React . FC < { children : React . ReactNode } > = ( { children } ) => {
712 const [ todos , setTodos ] = useState < Todo [ ] > ( [ ] ) ;
13+ const [ toastMessage , setToastMessage ] = useState < string | null > ( null ) ;
814
15+ // -----------------------
16+ // Hydrate from sessionStorage on mount
17+ // -----------------------
18+ useEffect ( ( ) => {
19+ const initial = loadTodos ( ) ;
20+ if ( initial . length ) {
21+ setTodos ( initial ) ;
22+ }
23+ } , [ ] ) ;
24+
25+ // -----------------------
26+ // Persist to sessionStorage on every change
27+ // -----------------------
28+ useEffect ( ( ) => {
29+ const { ok, error } = saveTodos ( todos ) ;
30+ if ( ! ok && error ) {
31+ /* eslint-disable-next-line no-console */
32+ console . warn ( 'Failed to persist todos to sessionStorage:' , error ) ;
33+ if (
34+ ( error as { name ?: string ; code ?: number } ) ?. name === 'QuotaExceededError' ||
35+ ( error as { name ?: string ; code ?: number } ) ?. code === 22 ||
36+ ( error as { name ?: string ; code ?: number } ) ?. code === 1014 // Firefox
37+ ) {
38+ triggerToast ( 'Storage quota exceeded – your latest changes may not be saved.' ) ;
39+ }
40+ }
41+ // eslint-disable-next-line react-hooks/exhaustive-deps
42+ } , [ todos ] ) ;
43+
44+ // -----------------------
45+ // Toast helpers
46+ // -----------------------
47+ const triggerToast = ( message : string ) => {
48+ setToastMessage ( message ) ;
49+ // Auto-dismiss after 4s
50+ setTimeout ( ( ) => setToastMessage ( null ) , 4000 ) ;
51+ } ;
52+
53+ // -----------------------
54+ // CRUD helpers
55+ // -----------------------
956 const addTodo = ( title : string , description : string ) => {
1057 const newTodo : Todo = {
1158 id : uuidv4 ( ) ,
@@ -14,24 +61,44 @@ export const TodoProvider: React.FC<{ children: React.ReactNode }> = ({ children
1461 completed : false ,
1562 createdAt : new Date ( ) ,
1663 } ;
17- setTodos ( [ ...todos , newTodo ] ) ;
64+ setTodos ( prev => [ ...prev , newTodo ] ) ;
1865 } ;
1966
2067 const editTodo = ( id : string , updates : Partial < Todo > ) => {
21- setTodos ( todos . map ( todo => ( todo . id === id ? { ...todo , ...updates } : todo ) ) ) ;
68+ setTodos ( prev => prev . map ( todo => ( todo . id === id ? { ...todo , ...updates } : todo ) ) ) ;
2269 } ;
2370
2471 const toggleTodoCompletion = ( id : string ) => {
25- setTodos ( todos . map ( todo => ( todo . id === id ? { ...todo , completed : ! todo . completed } : todo ) ) ) ;
72+ setTodos ( prev =>
73+ prev . map ( todo => ( todo . id === id ? { ...todo , completed : ! todo . completed } : todo ) )
74+ ) ;
2675 } ;
2776
2877 const deleteTodo = ( id : string ) => {
29- setTodos ( todos . filter ( todo => todo . id !== id ) ) ;
78+ setTodos ( prev => prev . filter ( todo => todo . id !== id ) ) ;
3079 } ;
3180
3281 return (
3382 < TodoContext . Provider value = { { todos, addTodo, editTodo, toggleTodoCompletion, deleteTodo } } >
3483 { children }
84+ { toastMessage && (
85+ < div
86+ role = "alert"
87+ style = { {
88+ position : 'fixed' ,
89+ bottom : '1rem' ,
90+ left : '50%' ,
91+ transform : 'translateX(-50%)' ,
92+ backgroundColor : '#333' ,
93+ color : '#fff' ,
94+ padding : '0.75rem 1rem' ,
95+ borderRadius : '4px' ,
96+ zIndex : 9999 ,
97+ } }
98+ >
99+ { toastMessage }
100+ </ div >
101+ ) }
35102 </ TodoContext . Provider >
36103 ) ;
37104} ;
0 commit comments