{"id":22080777,"url":"https://github.com/david-gmz/markdownnoteblog","last_synced_at":"2026-04-28T09:36:12.197Z","repository":{"id":261303980,"uuid":"883905611","full_name":"david-gmz/MarkdownNoteBlog","owner":"david-gmz","description":"An exercise migrating React 17 js to React 18 TypeScript","archived":false,"fork":false,"pushed_at":"2024-11-12T17:28:47.000Z","size":70,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-06-30T19:51:05.562Z","etag":null,"topics":["chatgpt","context-api","learning-by-doing","localstorage-api","reactjs","typescript","usereducer-hook"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/david-gmz.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-11-05T19:30:27.000Z","updated_at":"2024-11-11T23:11:03.000Z","dependencies_parsed_at":"2025-01-29T04:23:52.200Z","dependency_job_id":"cc2bc95a-37f5-4dd4-8298-25539eb89e85","html_url":"https://github.com/david-gmz/MarkdownNoteBlog","commit_stats":null,"previous_names":["david-gmz/markdownnoteblog"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/david-gmz/MarkdownNoteBlog","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/david-gmz%2FMarkdownNoteBlog","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/david-gmz%2FMarkdownNoteBlog/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/david-gmz%2FMarkdownNoteBlog/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/david-gmz%2FMarkdownNoteBlog/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/david-gmz","download_url":"https://codeload.github.com/david-gmz/MarkdownNoteBlog/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/david-gmz%2FMarkdownNoteBlog/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32375617,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-28T09:24:15.638Z","status":"ssl_error","status_checked_at":"2026-04-28T09:24:15.071Z","response_time":56,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["chatgpt","context-api","learning-by-doing","localstorage-api","reactjs","typescript","usereducer-hook"],"created_at":"2024-11-30T23:16:57.590Z","updated_at":"2026-04-28T09:36:12.180Z","avatar_url":"https://github.com/david-gmz.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Learning TypeScript into ReactJS 18 \u003cbr\u003e (A TypeScript journey Part I)\n## Based on [Learn React Course on Scrimba](https://v2.scrimba.com/learn-react-c0e)[^1] \u003cbr\u003e by [Bob Ziroll](https://v2.scrimba.com/@bobziroll:courses)\n\nTrying to grasp TypeScript in React... strugling at the moment!\n### Intro\nThis is an excersice to practice what I learned in a basic course on TypeScript also in scrimba now applyed usccesfully in small React project found in an excellent course I found in scrimba too, actually after finished TypeScript course I read that Bob has also a course on React. Though I already knew React I've been working on JS only. \nSo the project started reading and writing in the editor and managing the data in `localStorage`, then move data to firebase.\n\u003cdetails\u003e\n\u003csummary\u003eStarting React Project with only JS\u003c/summary\u003e\n\n```js\n//@see ./App.tsx\nexport default function App() {\n    const [notes, setNotes] = React.useState([])\n    const [currentNoteId, setCurrentNoteId] = React.useState(\n        (notes[0] \u0026\u0026 notes[0].id) || \"\"\n    )\n    \n    function createNewNote() {\n        const newNote = {\n            id: nanoid(),\n            body: \"# Type your markdown note's title here\"\n        }\n        setNotes(prevNotes =\u003e [newNote, ...prevNotes])\n        setCurrentNoteId(newNote.id)\n    }\n    \n    function updateNote(text) {\n        setNotes(oldNotes =\u003e oldNotes.map(oldNote =\u003e {\n            return oldNote.id === currentNoteId\n                ? { ...oldNote, body: text }\n                : oldNote\n        }))\n    }\n    \n    function findCurrentNote() {\n        return notes.find(note =\u003e {\n            return note.id === currentNoteId\n        }) || notes[0]\n    }\n    \n    return (\n        \u003cmain\u003e\n        {\n            notes.length \u003e 0 \n            ?\n            \u003cSplit \n                sizes={[30, 70]} \n                direction=\"horizontal\" \n                className=\"split\"\n            \u003e\n                \u003cSidebar\n                    notes={notes}\n                    currentNote={findCurrentNote()}\n                    setCurrentNoteId={setCurrentNoteId}\n                    newNote={createNewNote}\n                /\u003e\n                {\n                    currentNoteId \u0026\u0026 \n                    notes.length \u003e 0 \u0026\u0026\n                    \u003cEditor \n                        currentNote={findCurrentNote()} \n                        updateNote={updateNote} \n                    /\u003e\n                }\n            \u003c/Split\u003e\n            :\n            \u003cdiv className=\"no-notes\"\u003e\n                \u003ch1\u003eYou have no notes\u003c/h1\u003e\n                \u003cbutton \n                    className=\"first-note\" \n                    onClick={createNewNote}\n                \u003e\n                    Create one now\n                \u003c/button\u003e\n            \u003c/div\u003e\n            \n        }\n        \u003c/main\u003e\n    )\n}\n\n//@see ./components/Editor.tsx\nexport default function Editor({ currentNote, updateNote }) {\n    return (\n        \u003csection className=\"pane editor\"\u003e\n            \u003cMDEditor\n                minHeight={80}\n                value={currentNote.body}\n                onChange={updateNote}\n            /\u003e\n        \u003c/section\u003e\n    );\n}\n//@see ./components/Sidebar.tsx\nexport default function Sidebar(props) {\n    const noteElements = props.notes.map((note, index) =\u003e (\n        \u003cdiv key={note.id}\u003e\n            \u003cdiv\n                \n                className={`title ${\n                    note.id === props.currentNote.id ? \"selected-note\" : \"\"\n                }`}\n                onClick={() =\u003e props.setCurrentNoteId(note.id)}\n            \u003e\n                \u003ch4 className=\"text-snippet\"\u003eNote {index + 1}\u003c/h4\u003e\n            \u003c/div\u003e\n        \u003c/div\u003e\n    ))\n\n    return (\n        \u003csection className=\"pane sidebar\"\u003e\n            \u003cdiv className=\"sidebar--header\"\u003e\n                \u003ch3\u003eNotes\u003c/h3\u003e\n                \u003cbutton className=\"new-note\" onClick={props.newNote}\u003e+\u003c/button\u003e\n            \u003c/div\u003e\n            {noteElements}\n        \u003c/section\u003e\n    )\n}\n```\n\u003c/details\u003e\n\n\n\u003cdetails\u003e\n\u003csummary\u003eFirst attempt to migrate the React App to TS\u003c/summary\u003e\n\n## First failed\n\n\u003e### Chating with the AI: \n\u003e *I'm going to skip the first code for brevity porpus.*\n\u003e\n\u003e\u003e**Me** - Since I'm learning I'm going to explain what I did first and get a similar result on what you did but would like your opinion. I started defining my types from the components to the App. You did it from the App to the components, I must admit that your methodology it's clearer because it also the logic on going down when managing states variables, what do you think? \nHowever we got the same type error on the onChange property of the MDEditor component in `Editor.tsx` as follow:\n\n```ts\nType '(text: string) =\u003e void' is not assignable to type '(value?: string | undefined, event?: ChangeEvent\u003cHTMLTextAreaElement\u003e | undefined, state?: ContextStore | undefined) =\u003e void'.\n  Types of parameters 'text' and 'value' are incompatible.\n    Type 'string | undefined' is not assignable to type 'string'.\n      Type 'undefined' is not assignable to type 'string'.ts(2322)\nTypes.d.ts(24, 5): The expected type comes from property 'onChange' which is declared here on type 'IntrinsicAttributes \u0026 MDEditorProps \u0026 RefAttributes\u003cRefMDEditor\u003e'\n```\nTrying to fix this is when I got a mess. How could we fix it?\n\n## Solution\n\u003e### AI Response\n\u003e\u003e **AI** - Let's break down the types for each component in your app and define them one at a time. TypeScript can be tricky initially, but defining the structure for each prop and state value should make it clearer.\n\n#### Step 1: Define Note Type\n*Since you’re working with notes that have an id and body, let's start by creating a `Note type` to represent each note’s structure:*\n```ts\n// @see ./modals/types.ts -\u003e has moved to index.ts\nexport interface Note {\n    id: string;\n    body: string;\n}\n```\n\n#### Step 2: Update App Component Types\n*Let's update the App component. We’ll need to set types for:*\n\n- `Notes (array of Note type)`\n- ` currentNoteId (string)`\n- `createNewNote and updateNote functions.`\n\n*Update your imports and state declarations with these types:*\n\n```ts\nexport default function App() {\n    const [notes, setNotes] = React.useState\u003cNote[]\u003e([]);\n    const [currentNoteId, setCurrentNoteId] = React.useState\u003cstring\u003e(\n        (notes[0] \u0026\u0026 notes[0].id) || \"\"\n    );\n    function createNewNote() {\n        const newNote: Note = {\n            id: nanoid(),\n            body: \"# Type your markdown note's title here\"\n        };\n        setNotes(prevNotes =\u003e [newNote, ...prevNotes]);\n        setCurrentNoteId(newNote.id);\n    }\n\n    function updateNote(text: string) {\n        setNotes(oldNotes =\u003e oldNotes.map(oldNote =\u003e {\n            return oldNote.id === currentNoteId\n                ? { ...oldNote, body: text }\n                : oldNote;\n        }));\n    }function createNewNote() {\n        const newNote: Note = {\n            id: nanoid(),\n            body: \"# Type your markdown note's title here\"\n        };\n        setNotes(prevNotes =\u003e [newNote, ...prevNotes]);\n        setCurrentNoteId(newNote.id);\n    }\n\n    function updateNote(text: string) {\n        setNotes(oldNotes =\u003e oldNotes.map(oldNote =\u003e {\n            return oldNote.id === currentNoteId\n                ? { ...oldNote, body: text }\n                : oldNote;\n        }));\n    }\n    // more code goes here\n\n```\n#### Step 3: Update Editor Component Types\n*In Editor, currentNote is of type Note, and updateNote is a function that accepts a string:*\n\n```ts\n//@see ./components/Editor.tsx\nimport { Note } from './types';\n\ninterface EditorProps {\n    currentNote: Note;\n    updateNote: (text: string) =\u003e void;\n}\n\nexport default function Editor({ currentNote, updateNote }: EditorProps) {\n```\n\n#### Step 4: Update Sidebar Component Types\n*In Sidebar, we’ll define the props types for notes, currentNote, setCurrentNoteId, and newNote.*\n\n```ts\ninterface SidebarProps {\n    notes: Note[];\n    currentNote: Note;\n    setCurrentNoteId: (id: string) =\u003e void;\n    newNote: () =\u003e void;\n}\n\nexport default function Sidebar({ notes, currentNote, setCurrentNoteId, newNote }: SidebarProps) {\n    const noteElements = notes.map((note, index) =\u003e ( // more code here --\u003e\n```\n#### Step 5: Verify and Test\n*After these updates, the app should align well with TypeScript, and each component’s props should be type-safe. Let me know if you need help with additional tweaks!*\n\n**Me**\nSince I'm learning I'm going to explain what I did first and get a similar result on what you did but would like your opinion. I started defining my types from the components to the App, I must admit that your methodology it's clearer because it also the logic on going down when managing states variables what do you think. \nHowever we got the same type error on the onChange property of the MDEditor component in the Editor.tsx as follow:\n\n```ts\nType '(text: string) =\u003e void' is not assignable to type '(value?: string | undefined, event?: ChangeEvent\u003cHTMLTextAreaElement\u003e | undefined, state?: ContextStore | undefined) =\u003e void'.\n  Types of parameters 'text' and 'value' are incompatible.\n    Type 'string | undefined' is not assignable to type 'string'.\n      Type 'undefined' is not assignable to type 'string'.ts(2322)\nTypes.d.ts(24, 5): The expected type comes from property 'onChange' which is declared here on type 'IntrinsicAttributes \u0026 MDEditorProps \u0026 RefAttributes\u003cRefMDEditor\u003e'\n```\n\n**AI**\nIt sounds like you’re close! The issue with the onChange prop is due to MDEditor expecting a more complex function signature. Here, it’s expecting a function that takes value (potentially undefined) along with optional parameters like event and state.\n\n## To resolve this:\n\nWe can adjust the `onChange` function to handle the `undefined` case for `value` explicitly.\nInstead of using `updateNote` directly, wrap it in a function that aligns with the expected MDEditor signature.\nHere’s how to modify Editor.tsx:\n\n```ts\n// Editor.tsx\nimport React from 'react';\nimport { Note } from './types';\n\ninterface EditorProps {\n    currentNote: Note;\n    updateNote: (text: string) =\u003e void;\n}\n\nexport default function Editor({ currentNote, updateNote }: EditorProps) {\n    function handleEditorChange(value?: string) {\n        // Ensure we only pass a defined string to updateNote\n        if (value !== undefined) {\n            updateNote(value);\n        }\n    }\n\n    return (\n        \u003csection className=\"pane editor\"\u003e\n            \u003cMDEditor\n                minHeight={80}\n                value={currentNote.body}\n                onChange={handleEditorChange}\n            /\u003e\n        \u003c/section\u003e\n    );\n}\n```\n## Explanation\n`handleEditorChange` now accepts value?: string, which matches the type expected by `MDEditor`.\nIt checks if value is defined, and if so, calls updateNote.\nThis keeps updateNote as `(text: string) =\u003e void`, maintaining the simplicity in App.tsx.\nThis approach keeps updateNote focused on handling only defined strings and avoids adding complexity to the main app’s logic.\n\n**Me** This clears it up! So we didn't need to specify the `event?: React.ChangeEvent\u003cHTMLTextAreaElement\u003e` property in the `MDEditorProps.onChange` since its optional?\nxactly! Since event is optional in MDEditorProps.onChange, we can skip it if we don’t need it in our handler.\n\nBy only using the value parameter, TypeScript won’t expect you to handle the event or other optional arguments (event and state) unless you need them. This is helpful because it keeps your handleEditorChange function lean and focused on updating the note’s text, without extra complexity.\n\nIn cases where you do need to work with event or state, you can add them to the function signature as needed:\n\n```ts\nfunction handleEditorChange(value?: string, event?: React.ChangeEvent\u003cHTMLTextAreaElement\u003e) {\n    if (value !== undefined) {\n        updateNote(value);\n    }\n    // You could use `event` here if needed.\n}\n```\nSo yes, leaving out optional parameters in TypeScript is perfectly fine unless they're needed!\n\n**Me**\nIt works! Now I can continue developing in this app using TS, sure I'm going to come up with more doubts\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003elocalStorage fail validation\u003c/summary\u003e\n\n## Second fail: string | null contradiction \n\u003e### Chating with the AI: \n\u003e *I'm going to skip the first code for brevity porpus.*\n\u003e\n\u003e\u003e**Me**\nI added this snnipet to be able to localStorage the text notes in the App like:\n\n```ts\n//App.tsx\n    const notesStorage: string = localStorage.getItem('notes');\n   \n    const [notes, setNotes] = React.useState\u003cNote[]\u003e(\n        JSON.parse(notesStorage) || []\n    );\n    \n    const [currentNoteId, setCurrentNoteId] = React.useState(\n        (notes[0] \u0026\u0026 notes[0].id) || \"\"\n    );\n    useEffect(() =\u003e {\n     localStorage.setItem('notes', JSON.stringify(notes))\n    }, [notes])\n```\nBut the `noteStorage` is complaining \n```ts\nType 'string | null' is not assignable to type 'string'.\n  Type 'null' is not assignable to type 'string'.ts(2322)\n```\nSo  if I declare `const notesStorage: string | null = localStorage.getItem('notes');` then in `notesStorage` in the expresion:\n\n```ts\nconst [notes, setNotes] = React.useState\u003cNote[]\u003e(\n        JSON.parse(notesStorage) || []\n    );\n\n```\nI got the error type\n\n```ts\nArgument of type 'string | null' is not assignable to parameter of type 'string'.\n  Type 'null' is not assignable to type 'string'.ts(2345)\n```\nSo then how to set the type correctly?\n## Solution: check for null before attempting to parse it\n### AI Response\nTo handle localStorage.getItem('notes'), which returns string | null in TypeScript, we need to check for null before attempting to parse it. Here’s a revised approach to safely parse notesStorage only if it’s not null.\n\n```ts\n// App.tsx\nimport React, { useEffect } from 'react';\n\nconst notesStorage = localStorage.getItem('notes');\n\nconst initialNotes: Note[] = notesStorage ? JSON.parse(notesStorage) : [];\n\nconst [notes, setNotes] = React.useState\u003cNote[]\u003e(initialNotes);\nconst [currentNoteId, setCurrentNoteId] = React.useState\u003cstring\u003e(\n    (initialNotes[0] \u0026\u0026 initialNotes[0].id) || \"\"\n);\n\nuseEffect(() =\u003e {\n    localStorage.setItem('notes', JSON.stringify(notes));\n}, [notes]);\n```\n### Explanation\nInitialize Safely: By using a conditional notesStorage ? JSON.parse(notesStorage) : [], we ensure JSON.parse is only called when notesStorage is a non-null string.\nType-Safe Initial State: Assign initialNotes as the default value for notes when initializing state. This way, notes starts with an empty array if there’s nothing in localStorage.\nThis approach avoids null issues while keeping notes type-safe as Note[].\n\u003c/details\u003e\n\n\u003cdetails\u003e\u003csummary\u003eEvent Definition Type\u003c/summary\u003e\n \n## Delete Note\n### event: Event | undefined\n\n```ts\ninterface SidebarProps {\n    notes: Note[];\n    currentNote: Note;\n    setCurrentNoteId: (id: string) =\u003e void;\n    newNote: () =\u003e void;\n    deleteNote: (id: string, event: Event | undefined) =\u003e void;\n}\n```\n\nAnd adding this function to delete notes\n```ts\n//App.tsx\nfunction deleteNote(noteId, event) {\n        event.stopPropagation();\n        setNotes(prevNotes =\u003e prevNotes.filter(note =\u003e note.id !== noteId));\n    }\n    //more code ...\n\n    \u003cSidebar\n      notes={notes}\n      currentNote={findCurrentNote()}\n      setCurrentNoteId={setCurrentNoteId}\n      newNote={createNewNote}\n      deleteNote={deleteNote}\n    /\u003e\n\n// Sidebar.tsx I added a delete button\n\n    \u003cdiv key={note.id}\u003e\n        \u003cdiv\n            className={`title ${\n                note.id === currentNote.id ? \"selected-note\" : \"\"\n            }`}\n            onClick={() =\u003e setCurrentNoteId(note.id)}\u003e\n            \u003ch4 className=\"text-snippet\"\u003e{note.body.split(\"\\n\")[0]}\u003c/h4\u003e\n            \u003cbutton\n                className=\"delete-btn\"\n                onClick={() =\u003e deleteNote(note.id, event)}\u003e\n                \u003ci className=\"gg-trash trash-icon\"\u003e\u003c/i\u003e\n            \u003c/button\u003e\n        \u003c/div\u003e\n    \u003c/div\u003e\n```\n\nType Error:\n\n## Solution\nThe issue here is that TypeScript doesn’t know the type of event in the deleteNote function. Since the event comes from a button’s onClick, we can explicitly type it as `React.MouseEvent\u003cHTMLButtonElement\u003e`.\n\nHere's how to update your code:\n\n### Step 1: Update the deleteNote Function in App.tsx\nType the noteId as string and event as `React.MouseEvent\u003cHTMLButtonElement\u003e`. This will clarify that deleteNote expects a click event from a button:\n\n```ts\n// App.tsx\nimport React from 'react';\n\nfunction deleteNote(noteId: string, event: React.MouseEvent\u003cHTMLButtonElement\u003e) {\n    event.stopPropagation();\n    setNotes(prevNotes =\u003e prevNotes.filter(note =\u003e note.id !== noteId));\n}\n```\n### Step 2: Update the Delete Button in Sidebar.tsx\nIn Sidebar, we’ll also ensure deleteNote is called with the correct event. Instead of `() =\u003e deleteNote(note.id, event)`, pass the `event` directly by modifying onClick to capture the event parameter:\n\n```ts\n\u003cbutton\n    className=\"delete-btn\"\n    onClick={(event) =\u003e deleteNote(note.id, event)}\u003e\n    \u003ci className=\"gg-trash trash-icon\"\u003e\u003c/i\u003e\n\u003c/button\u003e\n```\n### Full Updated Sidebar.tsx Snippet\nThe component now directly passes the event to deleteNote:\n\n```ts\ninterface SidebarProps {\n    notes: Note[];\n    currentNote: Note;\n    setCurrentNoteId: (id: string) =\u003e void;\n    newNote: () =\u003e void;\n    deleteNote: (noteId: string, event: React.MouseEvent\u003cHTMLButtonElement\u003e) =\u003e void;\n}\n\nexport default function Sidebar({ notes, currentNote, setCurrentNoteId, newNote, deleteNote }: SidebarProps) {\n    return (\n        \u003csection className=\"pane sidebar\"\u003e\n            \u003cdiv className=\"sidebar--header\"\u003e\n                \u003ch3\u003eNotes\u003c/h3\u003e\n                \u003cbutton className=\"new-note\" onClick={newNote}\u003e+\u003c/button\u003e\n            \u003c/div\u003e\n            {notes.map((note) =\u003e (\n                \u003cdiv key={note.id}\u003e\n                    \u003cdiv\n                        className={`title ${note.id === currentNote.id ? \"selected-note\" : \"\"}`}\n                        onClick={() =\u003e setCurrentNoteId(note.id)}\u003e\n                        \u003ch4 className=\"text-snippet\"\u003e{note.body.split(\"\\n\")[0]}\u003c/h4\u003e\n                        \u003cbutton\n                            className=\"delete-btn\"\n                            onClick={(event) =\u003e deleteNote(note.id, event)}\u003e\n                            \u003ci className=\"gg-trash trash-icon\"\u003e\u003c/i\u003e\n                        \u003c/button\u003e\n                    \u003c/div\u003e\n                \u003c/div\u003e\n            ))}\n        \u003c/section\u003e\n    );\n}\n```\nThis ensures TypeScript understands the types, making your app both type-safe and functionally correct.\n\n**Me** \nHow could I skip this part to add the event parameter at calling the function on the click event!! `onClick={(event) =\u003e deleteNote(note.id, event)}\u003e`.  And even with that little bug the delete button was working but now as you stated makes the app both type-safe and functionally correct. I see that with TypeScript I force myself to know what I'm doing when programming!\n\n**AI**\nAbsolutely right—TypeScript can feel like a strict teacher, but it really helps to catch those little details that can easily slip by otherwise. By enforcing types, it encourages you to think through exactly how data flows through your app, which ultimately makes everything more predictable and robust. It’s helping you deepen your understanding!\n\u003c/details\u003e\n\n\u003cdetails\u003e\u003csummary\u003eType Error on switching to firebase \u003c/summary\u003e\n \n## firebase issues [^2]\n### One Type Error\n Finally I finished my little App and I switch localStorage to firbade to store the text, everything is working except for two things, one is a type error and the other it's an issue of performance when I add a feature. But first I would like to takle the type error... let's see:\n```ts\n//I skiped all the import for brevety\n//./models/index.ts\ninterface Note {\n    id: string;\n    body?: string;\n    createdAt: number;\n    updatedAt: number;\n}\ninterface EditorProps {\n    tempNoteText: string;\n    setTempNoteText: React.Dispatch\u003cReact.SetStateAction\u003cstring\u003e\u003e;\n}\n\ninterface SidebarProps {\n    notes: Note[];\n    currentNote: Note;\n    setCurrentNoteId: (id: string) =\u003e void;\n    addNote: () =\u003e void;\n    deleteNote: (id: string) =\u003e void;\n}\n\nexport type { Note, EditorProps, SidebarProps };\n\n// App.tsx\n// Some imports here\nexport default function App() {\n    const [notes, setNotes] = useState\u003cNote[]\u003e([]);\n    const [currentNoteId, setCurrentNoteId] = useState(\"\");\n    const [tempNoteText, setTempNoteText] = useState(\"\");\n    const currentNote =\n        notes.find(note =\u003e note.id === currentNoteId) || notes[0];\n    const sortedNotes = notes.sort((a, b) =\u003e b.updatedAt - a.updatedAt);\n    useEffect(() =\u003e {\n        if (currentNote \u0026\u0026 currentNote.body !== undefined)\n            setTempNoteText(currentNote.body);\n    }, [currentNote]);\n    useEffect(\n        () =\u003e\n            onSnapshot(notesCollection, snapshot =\u003e\n                setNotes(\n                    snapshot.docs.map(doc =\u003e ({  // here is the type error(1)\n                        id: doc.id,\n                        ...doc.data()\n                    }))\n                )\n            ),\n        []\n    );\n    useEffect(() =\u003e {\n        if (!currentNoteId) {\n            setCurrentNoteId(notes[0]?.id);\n        }\n    }, [currentNoteId, notes]);\n    const addNote = async () =\u003e {\n        const newNoteRef = await addDoc(notesCollection, {\n            body: \"**Type your markdown note's title here**\",\n            createdAt: Date.now(),\n            updatedAt: Date.now()\n        });\n        setCurrentNoteId(newNoteRef.id);\n    };\n\n    const deleteNote = async (noteId: string) =\u003e {\n        const docRef = doc(firestore, \"notes\", noteId);\n        try {\n            await deleteDoc(docRef);\n        } catch (error) {\n            console.log(`I got an error ${error}`);\n        }\n    };\n\n    useEffect(() =\u003e {\n        const timeoutId = setTimeout(() =\u003e {\n            const updateNote = async (text: string) =\u003e {\n                const docRef = doc(firestore, \"notes\", currentNoteId);\n                await setDoc(\n                    docRef,\n                    { body: text, updatedAt: Date.now() },\n                    { merge: true }\n                );\n            };\n            updateNote(tempNoteText);\n        }, 500);\n        return () =\u003e clearTimeout(timeoutId);\n    }, [tempNoteText, currentNoteId]);\n    return (\n        \u003cmain\u003e\n            {notes.length \u003e 0 ? (\n                \u003cSplit\n                    style={{\n                        height: \"100%\",\n                        border: \"1px solid #d5d5d5\",\n                        borderRadius: 3\n                    }}\u003e\n                    \u003cSidebar\n                        notes={sortedNotes}\n                        currentNote={currentNote}\n                        setCurrentNoteId={setCurrentNoteId}\n                        addNote={addNote}\n                        deleteNote={deleteNote}\n                    /\u003e\n                    \u003cEditor\n                        tempNoteText={tempNoteText}\n                        setTempNoteText={setTempNoteText}\n                    /\u003e\n                \u003c/Split\u003e\n            ) : (\n                \u003cdiv className=\"no-notes\"\u003e\n                    \u003ch1\u003eYou have no notes\u003c/h1\u003e\n                    \u003cbutton className=\"first-note\" onClick={() =\u003e addNote()}\u003e\n                        Create one now\n                    \u003c/button\u003e\n                \u003c/div\u003e\n            )}\n        \u003c/main\u003e\n    );\n}\n\n//Editor.tsx\n\nexport default function Editor({ tempNoteText, setTempNoteText }: EditorProps) {\n    const handleChange = (text?: string) =\u003e {\n        if (text !== undefined) setTempNoteText(text);\n    };\n\n    return (\n        \u003csection className=\"pane editor\"\u003e\n            \u003cMDEditor\n                minHeight={200}\n                value={tempNoteText}\n                onChange={handleChange}\n                previewOptions={{\n                    rehypePlugins: [[rehypeSanitize]]\n                }}\n            /\u003e\n        \u003c/section\u003e\n    );\n}\n\n// Sidebar.tsx\n\nexport default function Sidebar({\n    notes,\n    currentNote,\n    setCurrentNoteId,\n    addNote,\n    deleteNote\n}: SidebarProps) {\n\n    const noteElements = notes.map(note =\u003e {\n        return (\n            \u003cdiv key={note.id}\u003e\n                \u003cdiv\n                    className={`title ${\n                        note.id === currentNote.id ? \"selected-note\" : \"\"\n                    }`}\n                    onClick={() =\u003e setCurrentNoteId(note.id!)}\u003e\n                    \u003ch4 className=\"text-snippet\"\u003e{note.body?.split(\"\\n\")[0]}\u003c/h4\u003e\n                    \u003cbutton\n                        className=\"delete-btn\"\n                        onClick={() =\u003e deleteNote(note.id!)}\u003e\n                        \u003ci className=\"gg-trash trash-icon\"\u003e\u003c/i\u003e\n                    \u003c/button\u003e\n                \u003c/div\u003e\n            \u003c/div\u003e\n        );\n    });\n\n    return (\n        \u003csection className=\"pane sidebar\"\u003e\n            \u003cdiv className=\"sidebar--header\"\u003e\n                \u003ch3\u003eNotes\u003c/h3\u003e\n                \u003cbutton className=\"new-note\" onClick={addNote}\u003e\n                    +\n                \u003c/button\u003e\n            \u003c/div\u003e\n            {noteElements}\n        \u003c/section\u003e\n    );\n}\n\n//(1)Error:\nArgument of type '{ id: string; }[]' is not assignable to parameter of type 'SetStateAction\u003cNote[]\u003e'.\n```\nHow to fix that? I made sereral tries and fail.\n\n## Solution: Type Casting doc.data()\nWhen mapping over the `snapshot.docs`, cast `doc.data()` to Note. This will align the data structure with Note[] and satisfy TypeScript's type checking.\n\nHere’s how to apply the fix:\n```ts\n// Inside the App component\nuseEffect(() =\u003e {\n    const unsubscribe = onSnapshot(notesCollection, snapshot =\u003e\n        setNotes(\n            snapshot.docs.map(doc =\u003e ({\n                id: doc.id,\n                ...(doc.data() as Note), // Cast doc.data() as Note\n            }))\n        )\n    );\n    return () =\u003e unsubscribe();\n}, []);\n```\n### Explanation\n`doc.data() as Note`: *This casting tells TypeScript to treat `doc.data()` as an object of type Note, resolving the type mismatch.*\nThis approach makes setNotes understand that it’s dealing with an array of Note objects, satisfying the setNotes function’s expected input of SetStateAction\u003cNote[]\u003e.\nAdditional Tips\n**Check Optional Fields:** If any properties on Note are optional (e.g., body), make sure Note allows for undefined values as needed.\n**Error Handling:** If doc.data() may not always return the full Note structure (e.g., in case of a data corruption), consider adding some fallback values or error handling.\nThis should resolve the type error you’re encountering with Firebase data! Let me know if you have more questions or if you'd like help on the performance issue next.\n\n**Me**\nThis solution fix it but shift the error to the inside line:\n\n```ts\n// Inside the App component\n\n  snapshot.docs.map(doc =\u003e ({\n      id: doc.id, // Type error [^te].\n      ...(doc.data() as Note)\n  }))\n\n//[^te]: Type error:\n'id' is specified more than once, so this usage will be overwritten.ts(2783)\nApp.tsx(27, 25): This spread always overwrites this property.\n\n```\nI just changed that sugested line as:\n\n```ts\nsnapshot.docs.map(doc =\u003e ({\n    ...(doc.data() as Note),\n        id: doc.id\n     }))\n```\nThat clear up all the type erros and appatentely it's working fine.\n\n**AI**\nGreat catch! Reordering the id property after spreading doc.data() avoids the duplicate assignment issue and ensures that id always holds the value of doc.id. This approach preserves the intended structure without any conflict.\n\nNow your Firebase data should load correctly, and TypeScript can verify that everything is in place. Nicely done handling that refinement—glad to hear it’s working smoothly now!\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\u003csummary\u003ePerformance issue\u003c/summary\u003e\n \n## Trying to delay the update text note\n###  App Crashed\n \n Now lets try to tackle the performance issue.\n If I click in any note of the sidebar the note is updated after 500ms automatically. But I want to update it till I do some text changes on the editor. So to achieve that I put a condition like this:\n\n ```ts\n useEffect(() =\u003e {\n        const timeoutId = setTimeout(() =\u003e {\n            if (tempNoteText !== currentNote.body) { //This is the condition added\n                const updateNote = async (text: string) =\u003e {\n                    const docRef = doc(firestore, \"notes\", currentNoteId);\n                    await setDoc(\n                        docRef,\n                        { body: text, updatedAt: Date.now() },\n                        { merge: true }\n                    );\n                };\n                updateNote(tempNoteText);\n            }\n        }, 500);\n        return () =\u003e clearTimeout(timeoutId);\n    }, [tempNoteText, currentNoteId, currentNote.body]);\n```\nSo that condition should prevent to update till the body text in the editor change. However the App crashed with this error:\n\n```error\nApp.tsx:71 Uncaught TypeError: Cannot read properties of undefined (reading 'body')\n    at App (App.tsx:71:50)\n```\n\n**AI** \nThe crash happens because currentNote can be undefined, particularly if there are no notes or if currentNoteId doesn’t match any note ID in the notes array. When you access currentNote.body, TypeScript or JavaScript tries to read body from an undefined currentNote, leading to the error.\n\nTo fix this, you can add a check to ensure currentNote is defined before accessing its body. Here’s how to implement it safely:\n\n## Solution\nAdd a check to confirm that `currentNote` exists before attempting to read `currentNote.body`.\nYou can handle cases where `currentNote` is `undefined` by either setting a fallback value or skipping the update entirely.\nHere’s the revised `useEffect`:\n```ts\nuseEffect(() =\u003e {\n    const timeoutId = setTimeout(() =\u003e {\n        // Ensure currentNote is defined and has a body\n        if (currentNote \u0026\u0026 tempNoteText !== currentNote.body) {\n            const updateNote = async (text: string) =\u003e {\n                const docRef = doc(firestore, \"notes\", currentNoteId);\n                await setDoc(\n                    docRef,\n                    { body: text, updatedAt: Date.now() },\n                    { merge: true }\n                );\n            };\n            updateNote(tempNoteText);\n        }\n    }, 500);\n\n    return () =\u003e clearTimeout(timeoutId);\n}, [tempNoteText, currentNoteId, currentNote?.body]); // Add optional chaining\n```\n## Explanation of Changes\n**Check for currentNote:** `if (currentNote \u0026\u0026 tempNoteText !== currentNote.body)` ensures that `currentNote` is defined before comparing `tempNoteText` to `currentNote.body`.\n**Optional chaining in dependencies:** By using `currentNote?.body`, you prevent the dependency array from crashing if `currentNote` is `undefined`.\nThis should prevent the crash while also maintaining the condition you added for updating only when tempNoteText changes relative to `currentNote.body`.\n\u003c/details\u003e\n\u003cdetails open\u003e\u003csummary\u003eRefactoring using Context API\u003c/summary\u003e\n \n## Context API\n### Definition of types in one type\n Refactoring by using the COntext API[^3] simplifies the definition of types in on container type\n\n ```ts\n interface Note {\n    id: string;\n    body: string;\n    createdAt: Date;\n    updatedAt: Date;\n}\n type TypeNoteContext = {\n    notes: Note[];\n    currentNote: Note;\n    setCurrentNoteId: (id: string) =\u003e void;\n    addNote: () =\u003e void;\n    deleteNote: (id: string) =\u003e void;\n    darkMode: boolean;\n    toggleDarkMode: () =\u003e void\n    tempNoteText: string;\n    setTempNoteText: React.Dispatch\u003cReact.SetStateAction\u003cstring\u003e\u003e;\n};\n```\n\n\u003c/details\u003e\n\n---\n\n**Me**\nI learned a lot in this exercise of migrating this ReactJS App to TypeScript. Your an outstanding instructor! I thing I finish with this little App. But surly will start some more and getting more confident as more I use TS.\n\n**AI**\nIt’s great to see you mastering TypeScript with your React projects. You’ve tackled a wide range of important concepts and really elevated the app with both type safety and performance considerations. Keep going with the TypeScript journey—each project will deepen your confidence even more. Feel free to reach out anytime for more insights or questions. Happy coding! \n\n\n[^1]: Thou the course is outdated based on React 17 with js, it's a very good excercise!\n[^2]: For security reasons I did not include my firebase config file.\n[^3]: `useContext()` always looks for the closest provider above the component that calls it. It searches upwards and does not consider providers in the component from which you’re calling `useContext()`. That's why it cannot be any state in the provider.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdavid-gmz%2Fmarkdownnoteblog","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdavid-gmz%2Fmarkdownnoteblog","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdavid-gmz%2Fmarkdownnoteblog/lists"}