{"id":23395729,"url":"https://github.com/theonlybeardedbeast/mobx-react-ts-todo","last_synced_at":"2026-04-13T01:39:43.408Z","repository":{"id":50786256,"uuid":"316050196","full_name":"TheOnlyBeardedBeast/mobx-react-ts-todo","owner":"TheOnlyBeardedBeast","description":"A basic todo app for a tutorial. Built with React,TS,MobX","archived":false,"fork":false,"pushed_at":"2021-06-28T20:07:14.000Z","size":1031,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-02-14T13:37:55.722Z","etag":null,"topics":["mobx","react","todo","typescript"],"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/TheOnlyBeardedBeast.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}},"created_at":"2020-11-25T20:46:38.000Z","updated_at":"2023-03-07T08:22:54.000Z","dependencies_parsed_at":"2022-09-03T07:20:49.456Z","dependency_job_id":null,"html_url":"https://github.com/TheOnlyBeardedBeast/mobx-react-ts-todo","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TheOnlyBeardedBeast%2Fmobx-react-ts-todo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TheOnlyBeardedBeast%2Fmobx-react-ts-todo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TheOnlyBeardedBeast%2Fmobx-react-ts-todo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TheOnlyBeardedBeast%2Fmobx-react-ts-todo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/TheOnlyBeardedBeast","download_url":"https://codeload.github.com/TheOnlyBeardedBeast/mobx-react-ts-todo/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247888568,"owners_count":21013002,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["mobx","react","todo","typescript"],"created_at":"2024-12-22T07:17:07.683Z","updated_at":"2026-04-13T01:39:38.383Z","avatar_url":"https://github.com/TheOnlyBeardedBeast.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Mobx store wit React and TypeScript\n\nDo you remember the pain, the headache when you first tried to learn Redux?\n\nI know, I have been there.\n\nThe first time I used MobX, I was amazed, how easy it is to use and how good the React integration was.\n\n## What is Mobx\n\nAccording to the official documentation, MobX is a battle-tested library that makes state management simple and scalable by transparently applying functional reactive programming (TFRP). The philosophy behind MobX is simple:\n\n### Straightforward\n\nWrite minimalistic, boilerplate free code that captures your intent. Trying to update a record field? Use the good old JavaScript assignment. Updating data in an asynchronous process? No special tools are required, the reactivity system will detect all your changes and propagate them out to where they are being used.\n\n### Effortless optimal rendering\n\nAll changes to and uses of your data are tracked at runtime, building a dependency tree that captures all relations between state and output. This guarantees that computations depending on your state, like React components, run only when strictly needed.\n\n### Architectural freedom\n\nMobX is unopinionated and allows you to manage your application state outside of any UI framework. This makes your code decoupled, portable, and above all, easily testable.\n\nMobX tracks property access, not values.\n\n![Result app](readmeAssets/mobxDiagram.png)\n\nsources:\n\n- https://mobx.js.org/README.html\n- https://doc.ebichu.cc/mobx/best/react.html\n\n## Let's build a todo app\n\nLet's build a todo app, and learn how to use Mobx with React!\n\nIf you follow the guide you will end up with this simple todo app\n\n![Result app](readmeAssets/result.png)\n\nLet initialize our application:\n\n```bash\nyarn create react-app mobx-todo --template typescript\n```\n\nAdd the packages we will use:\n\n```bash\nyarn add mobx mobx-react react-feather node-sass\n```\n\nRename `App.css` to `App.scss` and replace its content to:\n\n```scss\n* {\n  box-sizing: border-box !important;\n  font-family: Arial, Helvetica, sans-serif;\n}\n\nhtml,\nbody,\n#root {\n  height: 100%;\n  width: 100%;\n  margin: 0;\n  padding: 0;\n}\n\n#root {\n  overflow-y: auto;\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n}\n\n.todo {\n  margin-top: 48px;\n  \u0026 {\n    max-width: 400px;\n    width: 100%;\n  }\n\n  \u0026 button {\n    display: block;\n    width: 40px;\n    height: 40px;\n    background: #000;\n    outline: none;\n    border: none;\n    color: #fff;\n    border-radius: 0;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    cursor: pointer;\n\n    \u0026.red {\n      background: #bb0000;\n    }\n\n    \u0026.green {\n      background: #47cf73;\n    }\n  }\n\n  \u0026-form {\n    display: flex;\n    margin-bottom: 8px;\n    \u0026-input {\n      height: 40px;\n      line-height: 40px;\n      font-size: 20px;\n      padding: 0 16px;\n      margin: 0;\n      display: block;\n      border: 1px solid #000;\n      border-radius: 0;\n      flex-grow: 1;\n    }\n  }\n\n  \u0026-list {\n    list-style: none;\n    margin: 0;\n    padding: 0;\n    \u0026-item {\n      display: flex;\n      margin-bottom: 1px;\n      background: #f1f1f1;\n      \u0026 \u003e span {\n        flex-grow: 1;\n        line-height: 40px;\n        padding: 0 16px;\n      }\n\n      \u0026.done \u003e span {\n        text-decoration: line-through;\n      }\n    }\n  }\n\n  \u0026-no-items {\n    text-align: center;\n  }\n}\n```\n\nWith decorators, it will be much easier to work with Mobx so let's enable the decorators in our tsconfig:\n\n```\n\"experimentalDecorators\": true\n```\n\nLets create a types.d.ts and model our todo item:\n\n```typescript\nexport interface ITodoItem {\n  id: number;\n  content: string;\n  done: boolean;\n}\n```\n\nSo now we are able to create our store:\n\n```typescript\nimport { action, computed, observable, makeObservable } from \"mobx\";\nimport { ITodoItem } from \"./types\";\nimport React from \"react\";\n\nexport class TodoStore {\n  constructor() {\n    makeObservable(this);\n  }\n\n  @observable public todoItems: ITodoItem[] = [\n    {\n      id: 1,\n      content: \"Ditch redux\",\n      done: true,\n    },\n    {\n      id: 2,\n      content: \"Learn MobX\",\n      done: false,\n    },\n  ];\n\n  @action public  setTodoItems = (v: ITodoItem[]) =\u003e {\n    this.todoItems = v;\n  }\n}\n```\n\nAs you can see a mobx store is simply just a `class` with properties and methods. Mobx properties are mutable.\n\nIf you are using decorators please use the `makeObserver(this)` inside the stores constructor.\n\nYou can even use abstraction to have a base class for multiple stores...\n\nIf you want to use a property in React, you need to decorate it with the `@observable` decorator. All the observables are mutable. If strict mode the observables are mutable only inside actions. If you take a risk of worse debugging processs, you can disable the strict mode and use setters to mutate the observables outside the store.\n\nFor now we simply created `todoItems` with default values and a getter and setter for our todo store\n\n## Make it possible to use Mobx with React\n\n```typescript\nconst rootStore = { todoStore: new TodoStore() };\n\nconst TodoStoreContext = React.createContext(rootStore);\n\nexport const useStores = () =\u003e React.useContext(TodoStoreContext);\n```\n\nWe will use Reacts context to access the store for our application we won't use a provider and we will directly access our context through a custom hook, but if you prefer using providers, feel free to change the code.\n\nWe create a root store, a root store can hold multiple stores. By default, the stores can not communicate between themselves.\n\nFor example you can achieve multi store communication with a ootstore like this:\n\n```typescript\nclass AuthStore {\n  protected readonly rootStore: RootStore;\n  constructor(rootStore: RootStore) {\n    this.rootStore = rootStore;\n  }\n  // ...\n}\n\nclass RootStore {\n  public authStore = new AuthStore(this);\n  public todoStore = new TodoStore(this);\n}\n\nconst TodoStoreContext = React.createContext(new RootStore());\n```\n\nSo every store can access the instance of the root store and through that, we can access other stores. Don't forget to add a property and change the constructor in every store where you need to share context.\n\nIn our todo app, we won't use a multi-store setup.\n\n## Rendering data from Mobx\n\nWe will create 2 modules to render out our list.\n\n- TodoItemList\n- TodoItem\n\n**TodoItemList**:\n\nThe `TodoItemList` renders a text a message if the source todos array is empty otherwise it renders a list of todo items.\n\n```typescript\nimport React from \"react\";\n\nimport { observer } from \"mobx-react\";\n\nimport { useStores } from \"./TodoStore\";\n\n\nexport const TodoItemList: React.FC = observer(() =\u003e {\n  const { todoStore } = useStores();\n\n  if (!todoStore.todoItems.length) {\n    return \u003cspan\u003ePlease add some todos\u003c/span\u003e;\n  }\n\n  return (\n    \u003cul className=\"todo-list\"\u003e\n      {todoStore.sortedTodoItems.map((item) =\u003e (\n        \u003cTodoItem key={item.id} item={item} /\u003e\n      ))}\n    \u003c/ul\u003e\n  );\n});\n```\n\nInside the `TodoItem` we access our store through the `useStores` hook we created and we render everything as usual. Without the use `observer` HoC the component would render just fine on mounting but it would not rerender on source data change.\n\nNow we will go back to our TodoStore and add the new getter `sortedTodoItems`.\n\n```typescript\n...\n\nexport class TodoStore {\n  ...\n\n  @computed public get sortedTodoItems(): ITodoItem[] {\n    return this.todoItems.slice().sort((a, b) =\u003e a.id - b.id);\n  }\n}\n```\n\nThe `@computed` decorator enables us to access data from one or multiple observable properties, the computed values are recalculated if one of the observables used inside the computed property changes.\n\nWe are using the `sortedTodoItems` getter, which is a `computed` property, to render our items, this getter returns the `todoItems` observable property ordered by the todo items ids. Computed properties are recalculated when any of the tracked observable inside their definition changes.\n\nIf you write anything that should track the changes of the source data use it inside the `observer` HoC.\n\n**TodoItem**:\n\nThe `TodoItem` component renders our todo item and uses its actions. Accessing the store and rendering the data is based on the same principles as in the `TodoItemList` component.\n\n```typescript\nimport React from \"react\";\n\nimport { Trash, Check, Edit2, SkipBack } from \"react-feather\";\nimport { observer } from \"mobx-react\";\nimport { reaction } from \"mobx\";\n\nimport { useStores } from \"./TodoStore\";\nimport { ITodoItem } from \"./types\";\n\ninterface TodoItemProps {\n  item: ITodoItem;\n}\n\nexport const TodoItem: React.FC\u003cTodoItemProps\u003e = observer(({ item }) =\u003e {\n  const className = \"todo-list-item\" + (item.done ? \" done\" : \"\");\n\n  return (\n    \u003cli className={className}\u003e\n      \u003cspan\u003e{item.content}\u003c/span\u003e\n      \u003cbutton\u003e\n        \u003cEdit2 size=\"20\" color=\"#fff\" /\u003e\n      \u003c/button\u003e\n      \u003cbutton className=\"green\"\u003e\n        {item.done ? (\n          \u003cSkipBack size=\"20\" color=\"#fff\" /\u003e\n        ) : (\n          \u003cCheck size=\"20\" color=\"#fff\" /\u003e\n        )}\n      \u003c/button\u003e\n      \u003cbutton className=\"red\"\u003e\n        \u003cTrash size=\"20\" color=\"#fff\" /\u003e\n      \u003c/button\u003e\n    \u003c/li\u003e\n  );\n});\n```\n\n\n\nWe are passing an `item` property to the `TodoItem` component, but this is not just a simple object, it's an observable object. Our component would not rerender without the `observer` HoC on the change of the `item`.\n\nIf we would like to ditch the `observer` in the `TodoItem` component, we would need to pass down primitives through the props, in our case we could pass down each property of the `item` object or a clone of our observable with the help of the `toJS` function provided by Mobx which will recursively convert an (observable) object to a javascript structure. All the observables are proxies, and they are not representing our data directly.\n\nOur app currently should look like this:\n![Result app](readmeAssets/todoItemList.png)\n\n## Modify and Remove a TodoItem\n\nWe can define three functions:\n\n- onCheckClick (toggles the todo state [done=true|false])\n- onTrashClick (removes the item from the store)\n- onEditClick (sets the item as editing source)\n\nAfter defining the functions our component will look like this:\n\n```typescript\nexport const TodoItem: React.FC\u003cTodoItemProps\u003e = observer(({ item }) =\u003e {\n  const { todoStore } = useStores();\n\n  const onCheckClick = () =\u003e {\n    todoStore.toggleState(item);\n  };\n\n  const onTrashClick = () =\u003e {\n    todoStore.removeItem(item);\n  };\n\n  const className = \"todo-list-item\" + (item.done ? \" done\" : \"\");\n\n  return (\n    \u003cli className={className}\u003e\n      \u003cspan\u003e{item.content}\u003c/span\u003e\n      \u003cbutton\u003e\n        \u003cEdit2 size=\"20\" color=\"#fff\" /\u003e\n      \u003c/button\u003e\n      \u003cbutton className=\"green\" onClick={onCheckClick}\u003e\n        {item.done ? (\n          \u003cSkipBack size=\"20\" color=\"#fff\" /\u003e\n        ) : (\n          \u003cCheck size=\"20\" color=\"#fff\" /\u003e\n        )}\n      \u003c/button\u003e\n      \u003cbutton className=\"red\" onClick={onTrashClick}\u003e\n        \u003cTrash size=\"20\" color=\"#fff\" /\u003e\n      \u003c/button\u003e\n    \u003c/li\u003e\n  );\n});\n```\n\nIn `onCheckClick` and `onTrashClick`, we are using actions defined in our store like this.\n\n```typescript\n...\n\nexport class TodoStore {\n  ...\n\n  @action toggleState = (item: ITodoItem) =\u003e {\n    item.done = !item.done;\n  };\n\n  @action public removeItem(item: ITodoItem) {\n    const indexToRemove = this.todoItems.findIndex(\n      (todo) =\u003e todo.id === item.id\n    );\n\n    this.todoItems.splice(indexToRemove, 1);\n  }\n}\n```\n\nEvery callable action should be decorated with the `@action` decorator. The `@action` decorator enables us to change data in the store. Using this decorator is not necessary, but helpful in the debugging process.\n\nAll the observables are mutable, you can see it in our store methods changing object properties, arrays... When you change an observable please do it always in an `@action`;\n\nUsing getters and setters is not a must, you can make your observables public, and use only actions to change data, but it is a nice feature you can use.\n\nWe could even rewrite our actions to `closures` so they could be used directly in the component.\n\nExample:\n\n```typescript\n// In store\n// ...\n@action toggleState = (item: ITodoItem) =\u003e () =\u003e {\n  item.done = !item.done;\n};\n// ...\n// In component\n// ...\n\u003cbutton className=\"green\" onClick={projectstore.toggleState(item)}\u003e\n// ...\n```\n\nNever pass store methods through props to child components, the calling, the call would not have the right context and the method would not be available to access any observable.\n\n## Add a new item to the store and enable editing\n\nFor adding a new item to our store we will create a new component.\n\n```typescript\nexport const TodoForm = () =\u003e {\n  const { todoStore } = useStores();\n\n  const input = React.useRef\u003cHTMLInputElement | null\u003e(null);\n\n  const handleSubmit = (event: React.FormEvent\u003cHTMLFormElement\u003e) =\u003e {\n    event.preventDefault();\n\n    if (input.current \u0026\u0026 input.current.value) {\n      todoStore.addItem(input.current.value);\n      input.current.value = \"\";\n    }\n  };\n\n  return (\n    \u003cform className=\"todo-form\" onSubmit={handleSubmit}\u003e\n      \u003cinput\n        ref={input}\n        type=\"text\"\n        name=\"todo-content\"\n        className=\"todo-form-input\"\n      /\u003e\n      \u003cbutton type=\"submit\"\u003e\n        \u003cPlus size=\"20\" color=\"#fff\" /\u003e\n      \u003c/button\u003e\n    \u003c/form\u003e\n  );\n};\n```\n\nAfter that we update our `TodoStore`\n\n```typescript\n...\n\nexport class TodoStore {\n  ...\n\n  @action addItem(todoContent: string) {\n    this.todoItems.push({\n      id: Date.now(),\n      content: todoContent,\n      done: false,\n    });\n  }\n}\n```\n\nThe result of this component looks like this:\n![Result app](readmeAssets/todoItemInput.png)\n\n\nBasically, it is just a simple form, which on submitting calls the `addItem` action of our store. The `addItem` action adds a new todo to our array.\n\nTo add edit functionality first we need to update our `TodoItem` component with a new function `onEditClick`\n\n```typescript\nexport const TodoItem: React.FC\u003cTodoItemProps\u003e = observer(({ item }) =\u003e {\n  ...\n\n  const onEditClick = () =\u003e {\n    todoStore.setEditItem(item);\n  };\n\n  const className = \"todo-list-item\" + (item.done ? \" done\" : \"\");\n\n  return (\n    \u003cli className={className}\u003e\n      \u003cspan\u003e{item.content}\u003c/span\u003e\n      \u003cbutton onClick={onEditClick} disabled={item.done}\u003e\n        \u003cEdit2 size=\"20\" color=\"#fff\" /\u003e\n      \u003c/button\u003e\n      \u003cbutton className=\"green\" onClick={onCheckClick}\u003e\n        {item.done ? (\n          \u003cSkipBack size=\"20\" color=\"#fff\" /\u003e\n        ) : (\n          \u003cCheck size=\"20\" color=\"#fff\" /\u003e\n        )}\n      \u003c/button\u003e\n      \u003cbutton className=\"red\" onClick={onTrashClick}\u003e\n        \u003cTrash size=\"20\" color=\"#fff\" /\u003e\n      \u003c/button\u003e\n    \u003c/li\u003e\n  );\n});\n```\n\nAfter that we will update our `TodoStore`\n\n```typescript\n...\n\nexport class TodoStore {\n  ...\n\n  @observable public itemToEdit?: ITodoItem;\n\n  @action public setItemToEdit = (v: ITodoItem | undefined)  =\u003e {\n    this.itemToEdit = v;\n  }\n\n  @action addItem(todoContent: string) {\n     if (this.itemToEdit) {\n      this.itemToEdit.content = todoContent;\n      this.itemToEdit = undefined;\n      return;\n    }\n\n    this.todoItems.push({\n      id: Date.now(),\n      content: todoContent,\n      done: false,\n    });\n  }\n}\n```\n\nAnd finally we add reaction to our `TodoForm` component.\n\n```typescript\nexport const TodoForm = () =\u003e {\n  ...\n\n  React.useEffect(() =\u003e {\n    reaction(\n      () =\u003e todoStore.itemToEdit,\n      () =\u003e {\n        if (input.current \u0026\u0026 todoStore.itemToEdit) {\n          input.current.value = todoStore.itemToEdit.content;\n        }\n      }\n    );\n  }, []);\n\n  ...\n};\n```\n\nWhen we click to edit on a todo item we set the `itemToEdit` observable, we cant use observables in a `useEffect` hook. To run side effects we have to use reactions.\n\nWe have three different reactions in Mobx:\n\n- `autorun` (runs every time any observable is changed in the store)\n- `when` (runs when a condition is fulfilled)\n- `reaction` (runs when tracked observable/observables change)\n\nYou can define reactions in a store or the components. If you define reactions in components always use them inside a `useEffect` hook with an empty dependency list.\n\nWe are using a `when` reaction where we check if the `itemToEdit` observable is defined (through the `itemToEdit` getter). When the condition result is true we replace the input value with the value of the item content.\n## Persisting data\n\nLast but not least, to have a fully working todo app we need to persisnt data. Luckilly this is exeptionally easy with MobX Persist Store.\n\nFirst we need to install the library\n\n```\n  yarn add mobx-persist-store\n```\n\nAfter the installation inside our TodoStore constructor we need to add only one function `makePersistable`:\n\n```typescript\n...\nimport { makePersistable } from \"mobx-persist-store\";\n\nexport class TodoStore {\n  constructor() {\n    makeObservable(this);\n\n    makePersistable(this, {\n      name: \"TodoStore\",\n      properties: [\"todoItems\"],\n      storage: localStorage\n    });\n  }\n\n...\n}\n```\n\nFirst argument of `makePersistable` is reference to the store. Second argument is `StorageOptions` object where name is the `name` of the store you want to persist and `properties` is an array with observables you want to persist. Last argument is the storage itself which may be localStorage, sessionStorage, localForage, ect. If you would like to develop an mobile app using React Native you may use AsyncStorage instead of localStorage\n\nAfter that we should have a fully working todo application!\n## Thank you all\n\nsource code available at: https://github.com/TheOnlyBeardedBeast/mobx-react-ts-todo\ntry it out at: https://theonlybeardedbeast.github.io/mobx-react-ts-todo/\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftheonlybeardedbeast%2Fmobx-react-ts-todo","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftheonlybeardedbeast%2Fmobx-react-ts-todo","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftheonlybeardedbeast%2Fmobx-react-ts-todo/lists"}