├── .editorconfig ├── .gitignore ├── README.md ├── package-lock.json ├── package.json ├── postcss.config.js ├── public ├── android-chrome-192x192.png ├── android-chrome-512x512.png ├── apple-touch-icon.png ├── favicon-16x16.png ├── favicon-32x32.png ├── favicon.ico ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json └── robots.txt ├── sandbox.config.json ├── src ├── api │ ├── index.js │ ├── notes.json │ └── serializer.js ├── components │ ├── application.jsx │ ├── edit.jsx │ ├── empty.jsx │ ├── loading.jsx │ ├── note-view.jsx │ ├── note.jsx │ └── notes-list.jsx ├── global.d.ts ├── index.css ├── index.tsx ├── lib │ ├── capitalize.ts │ ├── sleep.ts │ └── update-note.ts └── react-app-env.d.ts ├── tailwind.config.js └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | 7 | [*.{js,ts,jsx,tsx}] 8 | indent_style = space 9 | indent_size = 2 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | 5 | /node_modules 6 | /.pnp 7 | .pnp.js 8 | 9 | # testing 10 | 11 | /coverage 12 | 13 | # production 14 | 15 | /build 16 | 17 | # misc 18 | 19 | .DS_Store 20 | .env.local 21 | .env.development.local 22 | .env.test.local 23 | .env.production.local 24 | 25 | npm-debug.log* 26 | yarn-debug.log* 27 | yarn-error.log\* 28 | .vscode/settings.json 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React and TypeScript Example 2 | 3 | This is an exmaple application from Steve's React and TypeScript course. 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "issues", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@ngneat/falso": "^6.2.0", 7 | "@reduxjs/toolkit": "^1.9.0", 8 | "clsx": "^1.2.1", 9 | "lodash.shuffle": "^4.2.0", 10 | "lodash.uniqueid": "^4.0.1", 11 | "miragejs": "^0.1.46", 12 | "prop-types": "^15.8.1", 13 | "react": "^18.2.0", 14 | "react-dom": "^18.2.0", 15 | "react-redux": "^8.0.5", 16 | "react-scripts": "^5.0.1", 17 | "uuid": "^9.0.0" 18 | }, 19 | "scripts": { 20 | "start": "react-scripts start", 21 | "build": "react-scripts build", 22 | "test": "react-scripts test", 23 | "eject": "react-scripts eject" 24 | }, 25 | "eslintConfig": { 26 | "extends": [ 27 | "react-app", 28 | "react-app/jest" 29 | ] 30 | }, 31 | "browserslist": { 32 | "production": [ 33 | ">0.2%", 34 | "not dead", 35 | "not op_mini all" 36 | ], 37 | "development": [ 38 | "last 1 chrome version", 39 | "last 1 firefox version", 40 | "last 1 safari version" 41 | ] 42 | }, 43 | "devDependencies": { 44 | "@testing-library/jest-dom": "^5.16.5", 45 | "@testing-library/react": "^13.4.0", 46 | "@testing-library/user-event": "^14.4.3", 47 | "@types/jest": "^29.2.3", 48 | "@types/lodash.shuffle": "^4.2.7", 49 | "@types/lodash.uniqueid": "^4.0.7", 50 | "@types/node": "^18.11.9", 51 | "@types/react": "^18.0.24", 52 | "@types/react-dom": "^18.0.8", 53 | "autoprefixer": "^10.4.13", 54 | "postcss": "^8.4.19", 55 | "prettier": "^2.7.1", 56 | "prettier-plugin-tailwindcss": "^0.1.13", 57 | "tailwindcss": "^3.2.4", 58 | "typescript": "^4.9.3" 59 | }, 60 | "prettier": { 61 | "printWidth": 80, 62 | "tabWidth": 2, 63 | "useTabs": false, 64 | "semi": true, 65 | "singleQuote": true, 66 | "trailingComma": "all", 67 | "bracketSpacing": true, 68 | "jsxBracketSameLine": false, 69 | "fluid": false 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /public/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stevekinney/project-notes/a69946b2468627e64d211f13c42110c32c4fb6cd/public/android-chrome-192x192.png -------------------------------------------------------------------------------- /public/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stevekinney/project-notes/a69946b2468627e64d211f13c42110c32c4fb6cd/public/android-chrome-512x512.png -------------------------------------------------------------------------------- /public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stevekinney/project-notes/a69946b2468627e64d211f13c42110c32c4fb6cd/public/apple-touch-icon.png -------------------------------------------------------------------------------- /public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stevekinney/project-notes/a69946b2468627e64d211f13c42110c32c4fb6cd/public/favicon-16x16.png -------------------------------------------------------------------------------- /public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stevekinney/project-notes/a69946b2468627e64d211f13c42110c32c4fb6cd/public/favicon-32x32.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stevekinney/project-notes/a69946b2468627e64d211f13c42110c32c4fb6cd/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | TypeScript Example 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stevekinney/project-notes/a69946b2468627e64d211f13c42110c32c4fb6cd/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stevekinney/project-notes/a69946b2468627e64d211f13c42110c32c4fb6cd/public/logo512.png -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "An Example Application", 3 | "short_name": "Example", 4 | "icons": [ 5 | { 6 | "src": "/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/android-chrome-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "theme_color": "#ffffff", 17 | "background_color": "#ffffff", 18 | "display": "standalone" 19 | } 20 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /sandbox.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "infiniteLoopProtection": true, 3 | "hardReloadOnChange": false, 4 | "view": "browser" 5 | } 6 | -------------------------------------------------------------------------------- /src/api/index.js: -------------------------------------------------------------------------------- 1 | import { 2 | randBoolean, 3 | randCatchPhrase, 4 | randParagraph, 5 | randPriority, 6 | randStatus, 7 | randVerb, 8 | } from '@ngneat/falso'; 9 | import { createServer, Factory, Model, RestSerializer } from 'miragejs'; 10 | import { capitalize } from '../lib/capitalize'; 11 | 12 | const ApplicationSerializer = RestSerializer.extend({}); 13 | 14 | export function makeServer({ environment = 'development' }) { 15 | return createServer({ 16 | environment, 17 | 18 | serializers: { 19 | application: ApplicationSerializer.extend({ 20 | root: false, 21 | embed: true, 22 | }), 23 | }, 24 | 25 | factories: { 26 | issue: Factory.extend({ 27 | title: () => 28 | capitalize(`${randVerb()} ${randCatchPhrase().toLowerCase()}`), 29 | content: () => randParagraph({ length: 4 }).join('\n\n'), 30 | priority: () => randPriority(), 31 | status: () => randStatus(), 32 | completed: () => randBoolean(), 33 | }), 34 | }, 35 | 36 | models: { 37 | issue: Model, 38 | }, 39 | 40 | routes() { 41 | this.timing = 50; 42 | this.namespace = 'api'; 43 | 44 | this.get('issues'); 45 | this.post('issues'); 46 | this.get('issues/:id'); 47 | this.patch('issues/:id'); 48 | this.del('issues/:id'); 49 | }, 50 | 51 | seeds(server) { 52 | server.createList('issue', 10); 53 | console.log(server.db.dump()); 54 | }, 55 | }); 56 | } 57 | -------------------------------------------------------------------------------- /src/api/notes.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "completed": false, 4 | "status": "On hold", 5 | "priority": "critical", 6 | "content": "Deserunt laboriosam quas autem repellat aspernatur ipsa accusamus pariatur deserunt. Nam aut eum vel ut. Sunt dicta id eveniet minus. Debitis temporibus quod.\n\nExercitationem similique magni voluptates. Amet et asperiores quidem error. Commodi nostrum hic suscipit fuga consequatur nobis veritatis sit.\n\nPerspiciatis distinctio quia est magni. Aliquid id sed qui quis eum amet ut iusto. Et eos repellat nisi doloremque. Non est aut dolores accusamus pariatur placeat amet dolor.\n\nUt a voluptas labore et dolores magnam. Dolor deleniti dolores temporibus non autem. Voluptatibus numquam reiciendis nesciunt ipsa numquam enim. Unde velit optio quia.", 7 | "title": "Input fundamental multi-tasking protocol", 8 | "id": "1" 9 | }, 10 | { 11 | "completed": true, 12 | "status": "Priority", 13 | "priority": "minor", 14 | "content": "Et illo dolor cupiditate beatae. Eius eum recusandae odit placeat. Quibusdam error quisquam culpa pariatur praesentium et.\n\nVeniam commodi autem voluptatibus eos dolor quas reprehenderit. Praesentium cupiditate tempore et reprehenderit. Deleniti exercitationem illum maiores. Reprehenderit odio in ea voluptatem ut ut ullam.\n\nRepellendus et iste dolorem iste et perspiciatis occaecati vero eius. Vel ipsa officia quidem in maiores. Fugiat similique aliquam est eveniet ullam laborum qui. Et a maxime et magnam in.\n\nDucimus omnis numquam. Eos ut quis. Autem numquam nihil ut quo est nam eius. Laboriosam sint nihil in dolorum et recusandae est. Inventore consequuntur at ratione dolores quas doloribus autem et. Qui atque delectus consectetur praesentium doloribus corporis expedita soluta.", 15 | "title": "Hack de-engineered eco-centric installation", 16 | "id": "2" 17 | }, 18 | { 19 | "completed": false, 20 | "status": "Not started", 21 | "priority": "critical", 22 | "content": "Tenetur doloremque at fuga eligendi mollitia modi placeat. Dolores corrupti repellendus et quos eos modi sunt. Quae non molestiae earum iusto magni. Molestiae quo fugit quisquam sed. Quia culpa rem minus distinctio.\n\nMolestias in reprehenderit molestias quam doloribus tenetur. Cupiditate enim ad est nemo et quos. Minus non et voluptatem magni voluptatibus consectetur temporibus ad. Molestiae sed voluptate et dolor eaque sequi minima. Quisquam atque distinctio culpa distinctio rerum labore vero assumenda voluptate.\n\nDucimus omnis numquam. Eos ut quis. Autem numquam nihil ut quo est nam eius. Laboriosam sint nihil in dolorum et recusandae est. Inventore consequuntur at ratione dolores quas doloribus autem et. Qui atque delectus consectetur praesentium doloribus corporis expedita soluta.\n\nCupiditate voluptas cumque aspernatur. Adipisci voluptatibus vel eos. Doloremque commodi aliquid occaecati quia provident. Voluptatem tempore doloribus architecto rem quidem quaerat ipsam possimus. Laboriosam quisquam aut illo necessitatibus quo ducimus. Eum cupiditate sint a placeat dolores nemo.", 23 | "title": "Generate mandatory regional complexity", 24 | "id": "3" 25 | }, 26 | { 27 | "completed": true, 28 | "status": "Todo", 29 | "priority": "low", 30 | "content": "Tempora sunt enim. Sint ullam deleniti ut. Consequatur unde error odio quod fugit. Expedita unde commodi ratione sequi velit. Qui reprehenderit et tempora tenetur rerum. Veritatis consequatur odit sequi explicabo.\n\nVeritatis fuga sit ut explicabo ab eos repellendus. Ipsa praesentium dolor. Tempora ipsum est dolorum nihil.\n\nQuo voluptatem quia numquam laudantium sit quibusdam aut. Veritatis omnis neque ea saepe hic enim. Nam odit dolor non consequuntur perspiciatis inventore ut sint. Velit quod praesentium adipisci modi.\n\nTempora sunt enim. Sint ullam deleniti ut. Consequatur unde error odio quod fugit. Expedita unde commodi ratione sequi velit. Qui reprehenderit et tempora tenetur rerum. Veritatis consequatur odit sequi explicabo.", 31 | "title": "Navigate expanded didactic methodology", 32 | "id": "4" 33 | }, 34 | { 35 | "completed": false, 36 | "status": "Needs Confirmation", 37 | "priority": "critical", 38 | "content": "Et illo dolor cupiditate beatae. Eius eum recusandae odit placeat. Quibusdam error quisquam culpa pariatur praesentium et.\n\nDeleniti dolor aliquam qui saepe officia nisi. Omnis sit molestiae ea rerum ratione. Dolorum ut corporis eligendi id dolorem totam et architecto voluptatem. Laudantium et vel. Dolores laborum sed quis sed et soluta. Et odio voluptate amet.\n\nInventore natus explicabo qui adipisci laborum voluptate molestias suscipit. Ullam quisquam assumenda nesciunt voluptatem in. Similique facere debitis mollitia autem fugit a quo et impedit.\n\nInventore natus explicabo qui adipisci laborum voluptate molestias suscipit. Ullam quisquam assumenda nesciunt voluptatem in. Similique facere debitis mollitia autem fugit a quo et impedit.", 39 | "title": "Bypass up-sized eco-centric pricing structure", 40 | "id": "5" 41 | }, 42 | { 43 | "completed": false, 44 | "status": "In Discussion", 45 | "priority": "major", 46 | "content": "Quaerat officia voluptatum officiis. Quo velit numquam qui sint voluptatem eos magnam quas hic. Excepturi reprehenderit totam reprehenderit et fugit dolorum perferendis est. Quae repudiandae quisquam veniam maxime qui. Rerum aut dolores voluptates corrupti modi ducimus pariatur error tempore.\n\nSed quam quo nesciunt et laboriosam. Aspernatur et eum voluptas nesciunt omnis distinctio occaecati eum aut. Occaecati mollitia et est. Reiciendis dolor et ut commodi est repellat ipsa iure. Minus laudantium ut sed earum odit. Laudantium et non iusto et aliquid.\n\nBeatae officiis nihil similique soluta non voluptas totam ad. Quam nobis enim vel qui ratione quos voluptatem molestiae est. Ipsum voluptate illo aliquid beatae blanditiis dolorem. Adipisci non libero laudantium. A aperiam distinctio tempora aspernatur.\n\nAsperiores labore tempore quam. Ut voluptatem unde tempore fuga non repellendus omnis maxime. Quia soluta quibusdam. Commodi animi eum dolorem placeat sit. Quam nihil doloremque eligendi rem quibusdam iusto consequatur quae. Modi quaerat labore laboriosam quaerat sint nulla.", 47 | "title": "Bypass user-centric optimizing implementation", 48 | "id": "6" 49 | }, 50 | { 51 | "completed": false, 52 | "status": "On hold", 53 | "priority": "major", 54 | "content": "Quo nihil assumenda corrupti nobis provident tenetur et. Molestiae unde explicabo nihil maxime. Quidem molestiae velit laborum amet rerum tenetur. Error non aspernatur suscipit asperiores voluptas ipsa dolor. Similique itaque omnis.\n\nDistinctio facere fugit vel nobis dolor voluptas vel quod in. Molestiae et velit. Maiores voluptatem ut qui eligendi repellat eos quia. Tempore ipsa voluptatem minus. In reiciendis dolorem deserunt consequatur at.\n\nDolor officia a fuga omnis sit. Ut atque est nostrum. Quos aut quo eos vel velit autem et aspernatur.\n\nConsequatur perferendis itaque dolor corporis vel voluptatem quaerat. Ex numquam sed. Reiciendis eveniet ducimus nobis et necessitatibus qui. Sit veritatis temporibus nostrum eius laborum voluptatum deleniti optio. Aperiam vel laborum eos odit ut veritatis. Eos tempora enim sed.", 55 | "title": "Back up exclusive fresh-thinking workforce", 56 | "id": "7" 57 | }, 58 | { 59 | "completed": false, 60 | "status": "Canceled", 61 | "priority": "moderate", 62 | "content": "Repellendus et iste dolorem iste et perspiciatis occaecati vero eius. Vel ipsa officia quidem in maiores. Fugiat similique aliquam est eveniet ullam laborum qui. Et a maxime et magnam in.\n\nSunt velit facere fuga et voluptas inventore itaque. Necessitatibus ratione in esse. Quasi dignissimos quia est sequi incidunt enim reiciendis. At omnis iure in doloremque. Aut tempore consequatur facilis est ut distinctio est quas. Autem sunt est saepe quasi sed reprehenderit error magnam.\n\nSimilique molestiae id officia corporis quidem. Aliquam et ut eos ut nemo est voluptatem. Possimus ut quo labore. Alias amet quia enim. Quia ipsum pariatur facere illum esse recusandae veniam. Nihil enim fugit porro nam et quis sunt.\n\nVel et molestiae quis ea modi quas tempore dolorum fuga. Aut dolore numquam et. Amet sit quibusdam ea blanditiis consectetur velit.", 63 | "title": "Bypass profit-focused coherent application", 64 | "id": "8" 65 | }, 66 | { 67 | "completed": true, 68 | "status": "Needs Confirmation", 69 | "priority": "medium", 70 | "content": "Deleniti dolor aliquam qui saepe officia nisi. Omnis sit molestiae ea rerum ratione. Dolorum ut corporis eligendi id dolorem totam et architecto voluptatem. Laudantium et vel. Dolores laborum sed quis sed et soluta. Et odio voluptate amet.\n\nRerum aut expedita ad nam rerum. Animi sed in sunt enim. Rerum aspernatur ipsam quia consequatur sit est excepturi quidem voluptatem. Eum est et autem ducimus eius quod ipsa officia vero.\n\nVel facere dolorem sit hic non. Veniam nihil cumque sed et delectus. Maiores minus quisquam nostrum. Eius quasi nostrum. Molestiae recusandae ut. Suscipit natus aliquam eos sit aut.\n\nError quos aperiam et dolor et sit occaecati. Qui minima officia pariatur dolorem sit. Et incidunt consequatur eaque unde sunt sit dolore. Et quia ut rerum. Fugit sunt architecto cupiditate voluptas.", 71 | "title": "Back up decentralized human-resource system engine", 72 | "id": "9" 73 | }, 74 | { 75 | "completed": true, 76 | "status": "Not started", 77 | "priority": "critical", 78 | "content": "Distinctio facere fugit vel nobis dolor voluptas vel quod in. Molestiae et velit. Maiores voluptatem ut qui eligendi repellat eos quia. Tempore ipsa voluptatem minus. In reiciendis dolorem deserunt consequatur at.\n\nAccusantium aliquid non neque dicta eum. Molestias nesciunt odit. Quis rerum et cumque distinctio a pariatur vel ea dicta.\n\nError quos aperiam et dolor et sit occaecati. Qui minima officia pariatur dolorem sit. Et incidunt consequatur eaque unde sunt sit dolore. Et quia ut rerum. Fugit sunt architecto cupiditate voluptas.\n\nQuisquam at dolorem cupiditate enim ut recusandae porro aut quae. In nostrum et velit maiores dolores in architecto natus delectus. Aspernatur possimus libero velit omnis beatae. Libero adipisci et consequatur ullam. Aliquam est nam repudiandae odio. Eligendi vitae in beatae sint saepe ut eaque esse.", 79 | "title": "Parse vision-oriented holistic protocol", 80 | "id": "10" 81 | } 82 | ] 83 | -------------------------------------------------------------------------------- /src/api/serializer.js: -------------------------------------------------------------------------------- 1 | import { Serializer } from 'miragejs'; 2 | 3 | import { dasherize, camelize } from 'miragejs/lib/utils/inflector'; 4 | 5 | export default Serializer.extend({ 6 | root: false, 7 | 8 | normalize(payload) { 9 | let type = Object.keys(payload)[0]; 10 | let attrs = payload[type]; 11 | let modelName = camelize(type); 12 | let modelClass = this.schema.modelClassFor(modelName); 13 | let { belongsToAssociations, hasManyAssociations } = modelClass; 14 | let belongsToKeys = Object.keys(belongsToAssociations); 15 | let hasManyKeys = Object.keys(hasManyAssociations); 16 | 17 | let jsonApiPayload = { 18 | data: { 19 | type: this._container.inflector.pluralize(type), 20 | attributes: {}, 21 | }, 22 | }; 23 | 24 | if (attrs.id) { 25 | jsonApiPayload.data.id = attrs.id; 26 | } 27 | 28 | let relationships = {}; 29 | 30 | Object.keys(attrs).forEach((key) => { 31 | if (key !== 'id') { 32 | if (this.normalizeIds) { 33 | if (belongsToKeys.includes(key)) { 34 | let association = belongsToAssociations[key]; 35 | let associationModel = association.modelName; 36 | relationships[dasherize(key)] = { 37 | data: { 38 | type: associationModel, 39 | id: attrs[key], 40 | }, 41 | }; 42 | } else if (hasManyKeys.includes(key)) { 43 | let association = hasManyAssociations[key]; 44 | let associationModel = association.modelName; 45 | let data = attrs[key].map((id) => { 46 | return { 47 | type: associationModel, 48 | id, 49 | }; 50 | }); 51 | relationships[dasherize(key)] = { data }; 52 | } else { 53 | jsonApiPayload.data.attributes[dasherize(key)] = attrs[key]; 54 | } 55 | } else { 56 | jsonApiPayload.data.attributes[dasherize(key)] = attrs[key]; 57 | } 58 | } 59 | }); 60 | 61 | if (Object.keys(relationships).length) { 62 | jsonApiPayload.data.relationships = relationships; 63 | } 64 | 65 | return jsonApiPayload; 66 | }, 67 | 68 | getCoalescedIds(request) { 69 | return request.queryParams && request.queryParams.ids; 70 | }, 71 | }); 72 | -------------------------------------------------------------------------------- /src/components/application.jsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import NoteView from './note-view'; 3 | import NotesList from './notes-list'; 4 | 5 | import data from '../api/notes.json'; 6 | 7 | const Application = () => { 8 | const [notes, setNotes] = useState(data); 9 | const [currentNote, setCurrentNote] = useState(); 10 | 11 | return ( 12 |
13 |
14 |
15 | 20 | 26 |
27 |
28 |
29 | ); 30 | }; 31 | 32 | export default Application; 33 | -------------------------------------------------------------------------------- /src/components/edit.jsx: -------------------------------------------------------------------------------- 1 | import updateNote from '../lib/update-note'; 2 | 3 | const Editing = ({ note, setNotes }) => { 4 | return ( 5 |
6 | 18 |