{"id":48526141,"url":"https://github.com/devyubi/til_react_cra_ts","last_synced_at":"2026-04-07T22:32:40.090Z","repository":{"id":308457342,"uuid":"1032233631","full_name":"devyubi/til_react_cra_ts","owner":"devyubi","description":"React 의 TypeScript 버전","archived":false,"fork":false,"pushed_at":"2025-08-20T09:03:31.000Z","size":587,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-08-20T11:19:59.218Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"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/devyubi.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,"zenodo":null}},"created_at":"2025-08-05T02:47:24.000Z","updated_at":"2025-08-18T08:10:47.000Z","dependencies_parsed_at":"2025-08-06T03:26:02.756Z","dependency_job_id":"003a9418-a54f-4e9c-9fb0-000b005f4b58","html_url":"https://github.com/devyubi/til_react_cra_ts","commit_stats":null,"previous_names":["devyubi/til_react_cra_ts"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/devyubi/til_react_cra_ts","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devyubi%2Ftil_react_cra_ts","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devyubi%2Ftil_react_cra_ts/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devyubi%2Ftil_react_cra_ts/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devyubi%2Ftil_react_cra_ts/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/devyubi","download_url":"https://codeload.github.com/devyubi/til_react_cra_ts/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devyubi%2Ftil_react_cra_ts/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31532327,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-07T16:28:08.000Z","status":"ssl_error","status_checked_at":"2026-04-07T16:28:06.951Z","response_time":105,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5: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":[],"created_at":"2026-04-07T22:32:39.722Z","updated_at":"2026-04-07T22:32:40.082Z","avatar_url":"https://github.com/devyubi.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# react-router-dom\n\n## 1. 설치\n\n- vercel 에서는 v7.0 최신 버전 오류\n- 설치는 v6 를 설치하자.\n\n```bash\nnpm i react-router-dom@6.30.1\n```\n\n## 2. 폴더 및 페이지 구성\n\n- /src/pages 폴더 생성\n\n### 2.1. Home.tsx\n\n### 2.2. TodoReadPage.tsx\n\n```tsx\nimport TodoList from '../components/todos/TodoList';\nimport { NavLink } from 'react-router-dom';\n\nfunction TodoReadPage() {\n  return (\n    \u003csection className=\"space-y-6\"\u003e\n      \u003cdiv className=\"flex items-center justify-between\"\u003e\n        \u003ch2 className=\"text-xl font-semibold\"\u003e할일 목록\u003c/h2\u003e\n        \u003cNavLink\n          to={'/todos/write'}\n          className=\"rounded-md border border-neutral-300 px-3 py-2 hover:bg-neutral-50 dark:border-neutral-700 dark:hover:bg-neutral-800\"\n        \u003e\n          새 할일 작성\n        \u003c/NavLink\u003e\n      \u003c/div\u003e\n      \u003cdiv className=\"rounded-xl2 shadow-card space-y-6 bg-white p-6 dark:bg-neutral-800\"\u003e\n        \u003cTodoList /\u003e\n      \u003c/div\u003e\n    \u003c/section\u003e\n  );\n}\n\nexport default TodoReadPage;\n```\n\n### 2.3. TodoWritePage.tsx\n\n```tsx\nimport TodoWrite from '../components/todos/TodoWrite';\nimport { NavLink } from 'react-router-dom';\n\nfunction TodoWritePage() {\n  return (\n    \u003csection className=\"space-y-6\"\u003e\n      \u003cdiv className=\"flex items-center justify-between\"\u003e\n        \u003ch2 className=\"text-xl font-semibold\"\u003e할일 작성\u003c/h2\u003e\n        \u003cNavLink\n          to={'/todos/read'}\n          className=\"rounded-md border border-neutral-300 px-3 py-2 hover:bg-neutral-50 dark:border-neutral-700 dark:hover:bg-neutral-800\"\n        \u003e\n          목록\n        \u003c/NavLink\u003e\n      \u003c/div\u003e\n      \u003cdiv className=\"rounded-xl2 shadow-card space-y-6 bg-white p-6 dark:bg-neutral-800\"\u003e\n        \u003cTodoWrite /\u003e\n      \u003c/div\u003e\n    \u003c/section\u003e\n  );\n}\n\nexport default TodoWritePage;\n```\n\n### 2.4. TodoEditPage.tsx\n\n```tsx\nimport { useEffect, useState } from 'react';\nimport { useTodoActions, useTodoState } from '../context/todo/hooks';\nimport { NavLink, useNavigate, useParams } from 'react-router-dom';\n\nfunction TodoEditPage() {\n  const navigate = useNavigate();\n  // ts 자리\n  const { id } = useParams\u003c{ id: string }\u003e();\n  const { todos } = useTodoState();\n  const { editTodo } = useTodoActions();\n  const result = todos.find(item =\u003e item.id === id);\n  // 편집 중인 타이틀 보관 state\n  const [title, setTitle] = useState\u003cstring\u003e('');\n  useEffect(() =\u003e {\n    if (result) {\n      setTitle(result.title);\n    }\n  }, [result]);\n\n  if (!id) {\n    return (\n      \u003csection className=\"space-y-4\"\u003e\n        \u003cdiv className=\"flex items-center justify-between\"\u003e\n          \u003ch2 className=\"text-xl font-semibold\"\u003e잘못된 id 요청입니다.\u003c/h2\u003e\n          \u003cNavLink\n            to=\"/todos/read\"\n            className=\"rounded-md border border-neutral-300 px-3 py-2 hover:bg-neutral-50 dark:border-neutral-700 dark:hover:bg-neutral-800\"\n          \u003e\n            목록으로\n          \u003c/NavLink\u003e\n        \u003c/div\u003e\n      \u003c/section\u003e\n    );\n  }\n  if (!result) {\n    return (\n      \u003csection className=\"space-y-4\"\u003e\n        \u003cdiv className=\"flex items-center justify-between\"\u003e\n          \u003ch2 className=\"text-xl font-semibold\"\u003e내용을 찾을 수 없습니다.\u003c/h2\u003e\n          \u003cNavLink\n            to=\"/todos/read\"\n            className=\"rounded-md border border-neutral-300 px-3 py-2 hover:bg-neutral-50 dark:border-neutral-700 dark:hover:bg-neutral-800\"\n          \u003e\n            목록으로\n          \u003c/NavLink\u003e\n        \u003c/div\u003e\n      \u003c/section\u003e\n    );\n  }\n\n  const handleSave = () =\u003e {\n    const trim = title.trim();\n    if (!trim) return;\n    editTodo(result.id, title);\n    navigate('/todos/read');\n  };\n\n  return (\n    \u003csection className=\"space-y-4\"\u003e\n      \u003cdiv className=\"flex items-center justify-between\"\u003e\n        \u003ch2 className=\"text-xl font-semibold\"\u003e할일 수정\u003c/h2\u003e\n        \u003cNavLink\n          to=\"/todos/read\"\n          className=\"rounded-md border border-neutral-300 px-3 py-2 hover:bg-neutral-50 dark:border-neutral-700 dark:hover:bg-neutral-800\"\n        \u003e\n          목록\n        \u003c/NavLink\u003e\n      \u003c/div\u003e\n\n      \u003cdiv className=\"space-y-4 rounded-xl2 bg-white p-6 shadow-card dark:bg-neutral-800\"\u003e\n        \u003clabel\n          htmlFor=\"todo-title\"\n          className=\"block text-sm text-neutral-600 dark:text-neutral-300\"\n        \u003e\n          제목\n        \u003c/label\u003e\n        \u003cinput\n          className=\"w-full rounded-lg border border-neutral-300 bg-white px-3 py-2 outline-none focus:ring-2 focus:ring-brand dark:border-neutral-700 dark:bg-neutral-900\"\n          type=\"text\"\n          value={title}\n          onChange={e =\u003e setTitle(e.target.value)}\n        /\u003e\n        \u003cdiv className=\"flex items-center gap-2 pt-2\"\u003e\n          \u003cbutton\n            className=\"rounded-md bg-brand px-4 py-2 text-white hover:opacity-90\"\n            onClick={handleSave}\n          \u003e\n            저장\n          \u003c/button\u003e\n          \u003cNavLink\n            to={`/todos/read`}\n            className=\"rounded-md border border-neutral-300 px-4 py-2 hover:bg-neutral-50 dark:border-neutral-700 dark:hover:bg-neutral-800\"\n          \u003e\n            취소\n          \u003c/NavLink\u003e\n        \u003c/div\u003e\n      \u003c/div\u003e\n    \u003c/section\u003e\n  );\n}\n\nexport default TodoEditPage;\n```\n\n### 2.5. TodoDetailPage.tsx\n\n```tsx\nimport { NavLink, useParams } from 'react-router-dom';\nimport { useTodoActions, useTodoState } from '../context/todo/hooks';\n\nfunction TodoDetailPage() {\n  // http://localhost:3000/todos/id\n  const { id } = useParams\u003c{ id: string }\u003e();\n  // 전체 todos State 를 가져오겠다.\n  const { todos } = useTodoState();\n  const { toggleTodo } = useTodoActions();\n  // id 를 이용해서 해당 상세 내용 가져오기\n  const result = todos.find(item =\u003e item.id === id);\n  if (!id) {\n    return (\n      \u003csection className=\"space-y-4\"\u003e\n        \u003cdiv className=\"flex items-center justify-between\"\u003e\n          \u003ch2 className=\"text-xl font-semibold\"\u003e잘못된 id 요청입니다.\u003c/h2\u003e\n          \u003cNavLink\n            to=\"/todos/read\"\n            className=\"rounded-md border border-neutral-300 px-3 py-2 hover:bg-neutral-50 dark:border-neutral-700 dark:hover:bg-neutral-800\"\n          \u003e\n            목록으로\n          \u003c/NavLink\u003e\n        \u003c/div\u003e\n      \u003c/section\u003e\n    );\n  }\n  if (!result) {\n    return (\n      \u003csection className=\"space-y-4\"\u003e\n        \u003cdiv className=\"flex items-center justify-between\"\u003e\n          \u003ch2 className=\"text-xl font-semibold\"\u003e내용을 찾을 수 없습니다.\u003c/h2\u003e\n          \u003cNavLink\n            to=\"/todos/read\"\n            className=\"rounded-md border border-neutral-300 px-3 py-2 hover:bg-neutral-50 dark:border-neutral-700 dark:hover:bg-neutral-800\"\n          \u003e\n            목록으로\n          \u003c/NavLink\u003e\n        \u003c/div\u003e\n      \u003c/section\u003e\n    );\n  }\n\n  return (\n    \u003csection className=\"space-y-6\"\u003e\n      \u003cdiv className=\"flex items-center justify-between\"\u003e\n        \u003ch2 className=\"text-xl font-semibold\"\u003e할일 상세\u003c/h2\u003e\n        \u003cdiv className=\"flex items-center gap-2\"\u003e\n          \u003cNavLink\n            to={`/todos/${result.id}/edit`}\n            className=\"rounded-md border border-neutral-300 px-3 py-2 hover:bg-neutral-50 dark:border-neutral-700 dark:hover:bg-neutral-800\"\n          \u003e\n            수정\n          \u003c/NavLink\u003e\n          \u003cNavLink\n            to={'/todos/read'}\n            className=\"rounded-md border border-neutral-300 px-3 py-2 hover:bg-neutral-50 dark:border-neutral-700 dark:hover:bg-neutral-800\"\n          \u003e\n            목록\n          \u003c/NavLink\u003e\n        \u003c/div\u003e\n      \u003c/div\u003e\n      \u003cdiv className=\"space-y-4 rounded-xl bg-white p-6 shadow-card dark:bg-neutral-800\"\u003e\n        \u003cdiv className=\"flex items-center justify-between gap-4\"\u003e\n          \u003cdiv\u003e\n            \u003cdiv className=\"text-sm text-neutral-500\"\u003eID\u003c/div\u003e\n            \u003cdiv className=\"font-mono text-neutral-700 dark:text-neutral-300\"\u003e{result.id}\u003c/div\u003e\n          \u003c/div\u003e\n          \u003cdiv\u003e\n            \u003cspan\n              className={[\n                'inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium',\n                result.completed\n                  ? 'bg-green-100 text-green-700 dark:bg-green-900 dark:text-green-300'\n                  : 'bg-neutral-100 text-neutral-700 dark:bg-neutral-800 dark:text-neutral-300',\n              ].join(' ')}\n            \u003e\n              {result.completed ? '완료' : '진행중'}\n            \u003c/span\u003e\n          \u003c/div\u003e\n\n          \u003cdiv\u003e\n            \u003cdiv className=\"mb-1 text-sm text-neutral-500\"\u003e제목\u003c/div\u003e\n            \u003cdiv className=\"text-lg font-medium\"\u003e{result.title}\u003c/div\u003e\n          \u003c/div\u003e\n\n          \u003cdiv className=\"pt-2\"\u003e\n            \u003cbutton\n              onClick={() =\u003e toggleTodo(result.id)}\n              className=\"rounded-md border border-neutral-300 px-4 py-2 hover:bg-neutral-50 dark:border-neutral-700 dark:hover:bg-neutral-800\"\n            \u003e\n              {result.completed ? '완료 취소' : '완료로 표시'}\n            \u003c/button\u003e\n          \u003c/div\u003e\n        \u003c/div\u003e\n      \u003c/div\u003e\n    \u003c/section\u003e\n  );\n}\n\nexport default TodoDetailPage;\n```\n\n### 2.6. NotFound.tsx\n\n### 2.7. Settings.tsx\n\n## 3. 컴포넌트 수정\n\n- /src/components/todo/TodoItem.tsx\n\n```tsx\nimport { Link } from 'react-router-dom';\nimport { useTodoActions } from '../../context/todo/hooks';\nimport { TodoType } from '../../types/todoType';\n\ntype TodoItemProps = {\n  todo: TodoType;\n};\n\nconst TodoItem = ({ todo }: TodoItemProps) =\u003e {\n  // js 자리\n  const { toggleTodo, deleteTodo } = useTodoActions();\n\n  return (\n    \u003cli\n      className={['flex items-center justify-between gap-2 rounded-lg border px-3 py-2'].join(' ')}\n    \u003e\n      \u003cdiv className=\"flex w-full items-center gap-3\"\u003e\n        \u003cinput\n          type=\"checkbox\"\n          onChange={() =\u003e toggleTodo(todo.id)}\n          checked={todo.completed}\n          className=\"accent-brand h-4 w-4\"\n        /\u003e\n        \u003cLink\n          to={`/todos/${todo.id}`}\n          className={[\n            'flex-1',\n            todo.completed\n              ? 'text-neutral-400 line-through'\n              : 'text-neutral-900 dark:text-neutral-100',\n          ].join(' ')}\n        \u003e\n          {todo.title}\n        \u003c/Link\u003e\n        \u003cdiv className=\"flex items-center gap-2\"\u003e\n          \u003cLink\n            to={`/todos/${todo.id}/edit`}\n            className=\"dark: rounded-md border border-neutral-300 px-3 py-1 hover:bg-neutral-50 dark:border-neutral-700 dark:hover:bg-neutral-800\"\n          \u003e\n            수정\n          \u003c/Link\u003e\n          \u003cbutton\n            onClick={() =\u003e deleteTodo(todo.id)}\n            className=\"rounded-md border border-red-300 px-3 py-1 text-red-600 hover:bg-red-50 dark:hover:bg-red-900/20\"\n          \u003e\n            삭제\n          \u003c/button\u003e\n        \u003c/div\u003e\n      \u003c/div\u003e\n    \u003c/li\u003e\n  );\n};\n\nexport default TodoItem;\n```\n\n## 4. 라우터 구성\n\n- App.tsx 적용\n- 기본구성\n\n```tsx\n\u003cRouter\u003e\n  \u003cRoutes\u003e\n    \u003cRoute\u003e\u003c/Route\u003e\n  \u003c/Routes\u003e\n\u003c/Router\u003e\n```\n\n```tsx\nimport { NavLink, Route, BrowserRouter as Router, Routes } from 'react-router-dom';\nimport { TodoProvider } from './context/todo/TodoProvider';\nimport Home from './pages/Home';\nimport NotFound from './pages/NotFound';\nimport Settings from './pages/Settings';\nimport TodoReadPage from './pages/TodoReadPage';\nimport TodoWritePage from './pages/TodoWritePage';\nimport TodoDetailPage from './pages/TodoDetailPage';\nimport TodoEditPage from './pages/TodoEditPage';\n\nfunction App(): JSX.Element {\n  // ts 자리\n  const toggleDark = () =\u003e {\n    document.documentElement.classList.toggle('dark');\n  };\n  // tsx 자리\n  return (\n    \u003cdiv className=\"bg-bg text-fg min-h-screen\"\u003e\n      \u003cTodoProvider\u003e\n        \u003cRouter\u003e\n          {/* 상단메뉴 */}\n          \u003cheader className=\"border-b border-neutral-200 dark:border-neutral-800\"\u003e\n            \u003cdiv className=\"container-app flex items-center gap-4 py-6\"\u003e\n              \u003ch1 className=\"flex-1 text-2xl font-bold tracking-tighter\"\u003e할일 앱 서비스\u003c/h1\u003e\n              \u003cnav className=\"flex items-center gap-2 text-sm\"\u003e\n                \u003cNavLink to=\"/\"\u003e홈\u003c/NavLink\u003e\n                \u003cNavLink to=\"/todos/read\"\u003e읽기\u003c/NavLink\u003e\n                \u003cNavLink to=\"/todos/write\"\u003e생성\u003c/NavLink\u003e\n                \u003cNavLink to=\"/settings\"\u003e설정\u003c/NavLink\u003e\n              \u003c/nav\u003e\n\n              \u003cbutton\n                onClick={toggleDark}\n                className=\"rounded-md bg-black px-3 py-1 text-sm text-white hover:opacity-90 dark:bg-white dark:text-black\"\n              \u003e\n                \u003cspan className=\"inline dark:hidden\"\u003e다크모드\u003c/span\u003e\n                \u003cspan className=\"hidden dark:inline\"\u003e라이트모드\u003c/span\u003e\n              \u003c/button\u003e\n            \u003c/div\u003e\n          \u003c/header\u003e\n\n          \u003cmain className=\"container-app py-8\"\u003e\n            \u003cRoutes\u003e\n              \u003cRoute path=\"/\" element={\u003cHome /\u003e} /\u003e\n              {/* todos 관련 route ---*/}\n              \u003cRoute path=\"/todos\" element={\u003cTodoReadPage /\u003e} /\u003e\n              \u003cRoute path=\"/todos/read\" element={\u003cTodoReadPage /\u003e} /\u003e\n              \u003cRoute path=\"/todos/write\" element={\u003cTodoWritePage /\u003e} /\u003e\n              \u003cRoute path=\"/todos/:id\" element={\u003cTodoDetailPage /\u003e} /\u003e\n              \u003cRoute path=\"/todos/:id/edit\" element={\u003cTodoEditPage /\u003e} /\u003e\n              {/* todos 관련 route ---- */}\n              \u003cRoute path=\"/settings\" element={\u003cSettings /\u003e} /\u003e\n              \u003cRoute path=\"*\" element={\u003cNotFound /\u003e} /\u003e\n            \u003c/Routes\u003e\n          \u003c/main\u003e\n          \u003cfooter className=\"container-app py-8 text-sm text-neutral-500 dark:text-neutral-400\"\u003e\n            할일 앱 서비스 개발 @ 홍길동\n          \u003c/footer\u003e\n        \u003c/Router\u003e\n      \u003c/TodoProvider\u003e\n    \u003c/div\u003e\n  );\n}\n\nexport default App;\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdevyubi%2Ftil_react_cra_ts","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdevyubi%2Ftil_react_cra_ts","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdevyubi%2Ftil_react_cra_ts/lists"}