{"id":13495389,"url":"https://github.com/salvoravida/recoil-toolkit","last_synced_at":"2025-04-06T19:11:28.331Z","repository":{"id":41448858,"uuid":"329078193","full_name":"salvoravida/recoil-toolkit","owner":"salvoravida","description":"Helpers for Recoil, the next generation state management library.","archived":false,"fork":false,"pushed_at":"2023-04-20T21:00:41.000Z","size":720,"stargazers_count":145,"open_issues_count":2,"forks_count":5,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-30T18:08:24.684Z","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":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/salvoravida.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2021-01-12T18:35:29.000Z","updated_at":"2024-12-04T15:06:12.000Z","dependencies_parsed_at":"2024-01-16T09:52:52.889Z","dependency_job_id":"9db33af6-b760-45d6-b419-0f98bd98f2fc","html_url":"https://github.com/salvoravida/recoil-toolkit","commit_stats":{"total_commits":126,"total_committers":2,"mean_commits":63.0,"dds":0.007936507936507908,"last_synced_commit":"681d4c851db0de475d81bd87c6bd7340e1ad9dc4"},"previous_names":[],"tags_count":11,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/salvoravida%2Frecoil-toolkit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/salvoravida%2Frecoil-toolkit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/salvoravida%2Frecoil-toolkit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/salvoravida%2Frecoil-toolkit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/salvoravida","download_url":"https://codeload.github.com/salvoravida/recoil-toolkit/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247535517,"owners_count":20954576,"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":[],"created_at":"2024-07-31T19:01:34.223Z","updated_at":"2025-04-06T19:11:28.312Z","avatar_url":"https://github.com/salvoravida.png","language":"TypeScript","funding_links":[],"categories":["TypeScript"],"sub_categories":[],"readme":"# recoil-toolkit\r\n\u003cp align=\"center\"\u003e\r\n  \u003ca href=\"https://www.npmjs.com/package/recoil-toolkit\"\u003e\u003cimg src=\"https://img.shields.io/npm/v/recoil-toolkit.svg?style=flat-square\"\u003e\u003c/a\u003e\r\n  \u003ca href=\"https://www.npmjs.com/package/recoil-toolkit\"\u003e\u003cimg src=\"https://img.shields.io/npm/dt/recoil-toolkit.svg?style=flat-square\"\u003e\u003c/a\u003e\r\n  \u003ca href=\"https://www.npmjs.com/package/recoil-toolkit\"\u003e\u003cimg src=\"./packages/recoil-toolkit/cov-badge.svg\"\u003e\u003c/a\u003e\u003cbr/\u003e\r\n\u003c/p\u003e\r\n\r\nRecoil is the next generation state management library: CM safe, memoized, atomic, transactional. [recoiljs.org](https://recoiljs.org)\r\n\r\n## ℹ️ Abstract\r\n`recoil-toolkit` is a set of helpers, patterns and best practices about app state management (`recoil` based) for writing great apps with less effort.\r\n\r\nWhat you get out of the box:\r\n\r\n- 📈 task manager\r\n- ⌚ loading states / loader stacks\r\n- ❌ error states / error stack\r\n- :atom: immutable updaters\r\n- :boom: RecoilTunnel -\u003e read/update a recoilStore outside of React\r\n- :electron: RecoilReduxBridge -\u003e mix redux and recoil selectors (gradually upgrade redux apps to recoil!)\r\n- :boom: Recoil Toolkit DevTools! https://chrome.google.com/webstore/detail/recoil-toolkit-devtools/mkeicpnjoopkgdhfobdpcepncbnfnnji\r\n\r\n\u003cimg src=\"https://user-images.githubusercontent.com/20126259/187123997-3f6e3a68-0329-4b1a-b041-ce88def23efe.png\"\u003e\r\n\r\n## :boom: Recoil Tookit DevTools\r\n```typescript\r\nimport {RecoilDevTools} from 'recoil-toolkit'\r\n\r\nReactDOM.render(\r\n   \u003cRecoilRoot\u003e\r\n       // enable forceSerialize if you have issues with not serializable data\r\n      \u003cRecoilDevTools forceSerialize={false} /\u003e.\r\n      \u003cApp /\u003e\r\n   \u003c/RecoilRoot\u003e,\r\n   document.getElementById('root'),\r\n);\r\n```\r\n Note using key with dot notation, will allow DevTools to render atoms in tree. Ex: key: 'notifications.items', 'notifications.states',\r\n\r\nand what is coming soon ...\r\n- 🔜 task statistics, kpi\r\n- 🔜 reactive/observable pattern implementation\r\n- :question: any idea? open an issue!\r\n\r\n...stay tuned!\r\n\r\n## 🧰 Installation\r\n\r\n```bash\r\nnpm i recoil recoil-toolkit\r\n# OR\r\nyarn add recoil recoil-toolkit\r\n```\r\n## 📖 Table of contents\r\n\r\n- [Core Concepts](https://github.com/salvoravida/recoil-toolkit#%EF%B8%8F-core-concepts)\r\n- [State Management Pattern](https://github.com/salvoravida/recoil-toolkit#%EF%B8%8F-state-management-pattern)\r\n  + [Tasks](https://github.com/salvoravida/recoil-toolkit#-tasks)\r\n  + [Advanced Tasks](https://github.com/salvoravida/recoil-toolkit#-advanced-tasks)\r\n  + [Immutable updaters](https://github.com/salvoravida/recoil-toolkit#wrench-immutable-updaters)\r\n- [Recoil Tunnel](https://github.com/salvoravida/recoil-toolkit#boom-recoiltunnel)\r\n- [Recoil Redux Bridge](https://github.com/salvoravida/recoil-toolkit#electron-reduxbridge)\r\n- [Recoil vs Redux](https://github.com/salvoravida/recoil-toolkit#-recoil-vs-redux)\r\n- [Demo Todolist CRUD](https://github.com/salvoravida/recoil-toolkit#-demo-todolist-crud)\r\n- [Contributing](https://github.com/salvoravida/recoil-toolkit#-contributing)\r\n\r\n## ❤️ Core Concepts \r\n*First of all: read the official recoil guide [recoiljs.org](https://recoiljs.org)*\r\n\r\n- **Atom**: micro state \r\n- **Selector** : derived state from atoms and other selectors\r\n- **Set**: function(atom, prev =\u003e next) set next atom value\r\n- **Task**: async function that do something and can read(get)/write(set) to/from the store.\r\n\r\nSimple use pattern with hooks:\r\n```javascript\r\nimport { useRecoilState, useRecoilValue, useRecoilCallback } from 'recoil';\r\nimport { useRecoilTask } from 'recoil-toolkit';\r\n\r\nconst task = ({ set, reset, snapshot }) =\u003e async () =\u003e {\r\n   // await do something and update store\r\n};\r\n\r\n//in your component ...\r\nconst [state, setState] = useRecoilState(atom);\r\nconst value = useRecoilValue(atomOrSelector);\r\n//with recoil\r\nconst execute = useRecoilCallback(task,[]);\r\n//with recoil-toolkit\r\nconst { loading, data, error, execute } = useRecoilTask(task, []);\r\n```\r\n\r\n## ⚙️ State Management Pattern\r\n\r\n```\r\n--------------------------------------------------------------------\r\n|                                                                   |\r\n---\u003e atoms -\u003e selectors -\u003e view(hooks) -\u003e set(sync)/tasks(async) ---\u003e\r\n```\r\n\r\n###  🕒 Tasks\r\nTask is a core concept of `recoil-toolkit`.\r\nBasically it's an async function (Promise) that have access to the store with a closure of `({ set, reset, snapshot, refresh })`.\r\n\r\n```javascript\r\nconst task = ({ set, reset, snapshot, refresh }) =\u003e async () =\u003e {\r\n   // await do something and update store\r\n};\r\n   \r\nfunction Component(){ \r\n    //with recoil\r\n    const execute = useRecoilCallback(task,[]);\r\n    //with recoil-toolkit\r\n    const { loading, data, error, execute } = useRecoilTask(task, []);\r\n   \r\n    return ...\r\n}\r\n```\r\n\r\nFetching data example with queries:\r\n```typescript\r\nimport { selector } from 'recoil';\r\nimport { RecoilTaskInterface, useRecoilQuery } from 'recoil-toolkit';\r\n\r\nconst notifications = selector\u003c{ id: number; text: string }[]\u003e({\r\n   key: 'notifications',\r\n   get: async () =\u003e {\r\n      const body = await fetch('/api/notifications');\r\n      return await body.json();\r\n     };\r\n});\r\n\r\nexport const NotificationsView = () =\u003e {\r\n   const { loading, data, error, refresh } = useRecoilQuery(notifications, {\r\n      refreshOnMount: 'error';\r\n      cancelOnUnmount: true\r\n   });\r\n   \r\n   if (loading) return 'Loading…';\r\n   if (error) return 'Sorry! Something went wrong. Please try again.';\r\n   return (\r\n      \u003cdiv\u003e\r\n         \u003cbutton onClick={refresh}\u003eRefresh\u003c/button\u003e\r\n         {data.map(({ id, text }) =\u003e (\r\n            \u003cNotificationItem key={id} text={text} id={id} /\u003e\r\n         ))}\r\n      \u003c/div\u003e\r\n   );\r\n};\r\n```\r\n\r\nFetching data example with tasks:\r\n```typescript\r\nimport { atom } from 'recoil';\r\nimport { RecoilTaskInterface, useRecoilTask } from 'recoil-toolkit';\r\n\r\nconst notifications = atom\u003c{ id: number; text: string }[]\u003e({\r\n   key: 'notifications',\r\n   default: [],\r\n});\r\n\r\nconst getNotificationsTask = ({ set }: RecoilTaskInterface) =\u003e async () =\u003e {\r\n   const body = await fetch('/api/notifications');\r\n   const resp = await body.json();\r\n   set(notifications, resp);\r\n};\r\n\r\nexport const NotificationsView = () =\u003e {\r\n   const { loading, data, error, execute: refresh } = useRecoilTask(getNotificationsTask, [], {\r\n      dataSelector: notifications,\r\n   });\r\n   if (loading) return 'Loading…';\r\n   if (error) return 'Sorry! Something went wrong. Please try again.';\r\n   return (\r\n      \u003cdiv\u003e\r\n         \u003cbutton onClick={refresh}\u003eRefresh\u003c/button\u003e\r\n         {data.map(({ id, text }) =\u003e (\r\n            \u003cNotificationItem key={id} text={text} id={id} /\u003e\r\n         ))}\r\n      \u003c/div\u003e\r\n   );\r\n};\r\n```\r\nSending data example:\r\n```typescript\r\nconst notificationRead = atomFamily\u003cboolean, number\u003e({\r\n   key: 'notificationRead',\r\n   default: false,\r\n});\r\n\r\nconst notifyServerNotificationRead = ({ set }: RecoilTaskInterface) =\u003e async (id: number) =\u003e {\r\n   await fetch('/api/notification-read', { body: JSON.stringify({ id }), method: 'POST' });\r\n   set(notificationRead(id), true);\r\n};\r\n\r\nexport const NotificationItem = ({ id, text }: { id: number; text: string }) =\u003e {\r\n   const read = useRecoilValue(notificationRead(id));\r\n   const { loading, error, execute: notify } = useRecoilTask(notifyServerNotificationRead, []);\r\n   return (\r\n      \u003cdiv style={{ color: read ? 'green' : 'yellow' }}\u003e\r\n         \u003cp\u003e{text}\u003c/p\u003e\r\n         {!read \u0026\u0026 (\r\n            \u003cbutton disabled={loading} onClick={() =\u003e notify(id)}\u003e\r\n               {loading ? 'Sending ...' : 'Send Read'}\r\n            \u003c/button\u003e\r\n         )}\r\n         {error \u0026\u0026 'Sorry, server error while set notification read!'}\r\n      \u003c/div\u003e\r\n   );\r\n};\r\n```\r\nTasks composition: simple create macro tasks by composing micro tasks\r\n```typescript\r\nexport const removeItemTask = (rti: RecoilTaskInterface) =\u003e async (id: number) =\u003e {\r\n   // ...\r\n};\r\n\r\nexport const editItemTask = (rti: RecoilTaskInterface) =\u003e async (item: Item) =\u003e {\r\n   // ...\r\n};\r\n\r\n// task composition example\r\nexport const editAndRemoveTask = (rti: RecoilTaskInterface) =\u003e async (item: Item) =\u003e {\r\n   await editItemTask(rti)(item);\r\n   await removeItemTask(rti)(item.id);\r\n};\r\n```\r\n### 🔨 Advanced Tasks\r\nTask can have options for advanced use case.\r\n```typescript\r\n type TaskOptions = {\r\n   key?: string;\r\n   errorStack?: boolean;\r\n   loaderStack?: boolean | string;\r\n   exclusive?: boolean;\r\n};\r\n```\r\nSend error to global errorStack:\r\n```typescript\r\nimport { useRecoilTask, useLastError } from 'recoil-toolkit';\r\n\r\nexport const useAdvancedTask = () =\u003e\r\n   useRecoilTask(advancedTask, [], {\r\n      errorStack: true,\r\n   });\r\n\r\n// somewhere in your ui ...\r\nconst lastError = useLastError();\r\n```\r\nUse a common loader stack:\r\n```typescript\r\nimport { useRecoilTask, useIsLoading } from 'recoil-toolkit';\r\n\r\nexport const useAdvancedTask1 = () =\u003e\r\n   useRecoilTask(advancedTask1, [], {\r\n      loaderStack: true,\r\n   });\r\n   \r\nexport const useAdvancedTask2 = () =\u003e\r\n   useRecoilTask(advancedTask2, [], {\r\n      loaderStack: true,\r\n   });\r\n\r\n// somewhere in your ui ...\r\nconst isGlobalLoading = useIsLoading();\r\n```\r\nloaderStack can be a string, so you can have many loader stacks if needed. \r\n```typescript\r\nexport const useAdvancedTask1 = () =\u003e\r\n   useRecoilTask(advancedTask1, [], {\r\n      loaderStack: 'widgetA',\r\n   });\r\nexport const useAdvancedTask2 = () =\u003e\r\n   useRecoilTask(advancedTask2, [], {\r\n      loaderStack: 'widgetA',\r\n   });\r\n\r\n// somewhere in your ui ...\r\nconst isWidgetALoading = useIsLoading('widgetA');\r\n```\r\nExclusive tasks (no double run): ComponentA, ComponentB read from the same task instance, that is exclusive. So if componentA already execute the task, componentB, will see the same loading, data, error.\r\n```typescript\r\nexport const useAdvancedExclusiveTask = () =\u003e\r\n   useRecoilTask(advancedTask, [], {\r\n      key:'advancedTask',  \r\n      exclusive:true   // no double run\r\n   });\r\n\r\n\r\nfunction ComponentA(){\r\n   const {loading, data, error, execute} = useAdvancedExclusiveTask();\r\n   // ....\r\n}\r\n\r\nfunction ComponentB(){\r\n   const {loading, data, error, execute} = useAdvancedExclusiveTask();\r\n   // ....\r\n}\r\n```\r\n### :wrench: Immutable updaters\r\nRecoil atom set api need an immutable updater function, `recoil-toolkit` has a built in lib for common cases.\r\nNumber atoms:\r\n```typescript\r\nimport { inc, dec, decAbs, show, hide } from '/recoil-toolkit';\r\n\r\nexport const counter = atom\u003cnumber\u003e({\r\n   key: 'counter',\r\n   default: 0,\r\n});\r\n\r\nset(counter, inc);     // set(counter, s=\u003es+1)\r\nset(counter, dec);     // set(counter, s=\u003es-1)\r\nset(counter, decAbs);  // dec to zero\r\nset(counter, show);    // alias for inc (loader stack)\r\nset(counter, hide);    // alias for decAbs (loader stack)\r\n```\r\nBoolean atoms:\r\n```typescript\r\nimport { and, or, not, toggle } from '/recoil-toolkit';\r\n\r\nexport const flag = atom\u003cboolean\u003e({\r\n   key: 'counter',\r\n   default: false,\r\n});\r\n\r\nset(flag, and(value));    // set(counter, s=\u003es \u0026\u0026 !!value)\r\nset(flag, or(value);      // set(counter, s=\u003es || !!value)\r\nset(flag, not);           // set(counter, s=\u003e!s)\r\nset(flag, toggle);        // alias for not\r\n```\r\nArray atoms:\r\n```typescript\r\nimport { push, pushTop, unshift, reverse, filter, updateObj, removeObj } from '/recoil-toolkit';\r\n\r\nexport const list = atom\u003cany[]\u003e({\r\n   key: 'counter',\r\n   default: [],\r\n});\r\n\r\nset(list, push(value));                 // push value to array and return a new array\r\nset(list, unshift(value);               // insert at top and return a new array\r\nset(list, pushTop(value);               // alias for unshift\r\nset(list, reverse);                     // revers array and return a new array\r\nset(list, filter(predicate));           // set(list, l=\u003el.filter(predicate)\r\nset(list, updateObj(value, match));     // update item, based on match ({id} for example)\r\nset(list, removeObj(value, match));     // remove item, based on match ({id} for example)\r\n```\r\n\r\n## :boom: RecoilTunnel\r\nRecoilTunnel capture the current recoil store instance, and allow you to use it outside of React.\r\nhttps://codesandbox.io/s/k6ri5\r\n\r\n```jsx\r\nimport React from 'react';\r\nimport ReactDOM from 'react-dom';\r\nimport { atom, RecoilRoot, useRecoilValue } from 'recoil';\r\nimport { getRecoilStore, RecoilTunnel } from 'recoil-toolkit';\r\n\r\nconst timeAtom = atom({\r\n   key: 'timeAtom',\r\n   default: new Date(),\r\n});\r\n\r\nexport const CurrentTime = () =\u003e {\r\n   const currentTime = useRecoilValue(timeAtom);\r\n   return \u003cp\u003e{currentTime.toLocaleTimeString()}\u003c/p\u003e;\r\n};\r\n\r\nReactDOM.render(\r\n   \u003cRecoilRoot\u003e\r\n      \u003cRecoilTunnel /\u003e\r\n      \u003cCurrentTime /\u003e\r\n   \u003c/RecoilRoot\u003e,\r\n   document.getElementById('root'),\r\n);\r\n\r\ngetRecoilStore().then(store =\u003e {\r\n   console.log('RecoilTunnel captured Recoil store:', store);\r\n   setInterval(() =\u003e {\r\n      store.set(timeAtom, new Date());\r\n   }, 999);\r\n});\r\n```\r\n\r\n## :electron: RecoilReduxBridge\r\nRead, Write from/to Redux. Mix redux and recoil selectors (gradually upgrade redux apps to recoil!)\r\nhttps://zhb1x.csb.app/ - src: https://codesandbox.io/s/zhb1x\r\n\r\n```typescript\r\nimport React from 'react';\r\nimport ReactDOM from 'react-dom';\r\nimport { atom, RecoilRoot, useRecoilValue, useRecoilState, selector } from 'recoil';\r\nimport { inc, reduxSelector, RecoilReduxBridge, useDispatch, useSelector } from 'recoil-toolkit';\r\nimport { store, State } from './store';\r\n\r\nconst getReduxCount = (s: State) =\u003e s.count;\r\n\r\nconst counterAtom = atom({ key: 'counter', default: 0 });\r\n\r\nconst maxCounterType = selector\u003cstring\u003e({\r\n  key: 'maxCounter',\r\n  get: ({ get }) =\u003e {\r\n    const re = get(counterAtom);\r\n    const rx = get(reduxSelector(getReduxCount)) as number;\r\n    return re === rx ? '' : re \u003e rx ? 'recoil' : 'redux';\r\n  },\r\n});\r\n\r\nfunction App() {\r\n  const reduxCount = useSelector(getReduxCount);\r\n  const dispatch = useDispatch();\r\n  const [counter, setCounter] = useRecoilState(counterAtom);\r\n  const maxType = useRecoilValue(maxCounterType);\r\n  return (\r\n          \u003c\u003e\r\n            \u003cdiv\u003e\r\n              reduxCounter : {reduxCount}\r\n              \u003cbutton onClick={() =\u003e dispatch({ type: 'INCREMENT' })}\u003edispatch\u003c/button\u003e\r\n            \u003c/div\u003e\r\n            \u003cdiv\u003e\r\n              recoilCounter : {counter}\r\n              \u003cbutton onClick={() =\u003e setCounter(inc)}\u003edispatch\u003c/button\u003e\r\n            \u003c/div\u003e\r\n            \u003cdiv\u003e{maxType}\u003c/div\u003e\r\n          \u003c/\u003e\r\n  );\r\n}\r\n\r\nReactDOM.render(\r\n        \u003cRecoilRoot\u003e\r\n          \u003cRecoilReduxBridge store={store}\u003e\r\n            \u003cApp /\u003e\r\n          \u003c/RecoilReduxBridge\u003e\r\n        \u003c/RecoilRoot\u003e,\r\n        document.getElementById('root'),\r\n);\r\n```\r\nNote: you can use `react-redux` useSelector/useDispatch to access store, instead of useSelector from `recoil-toolkit`, or both at same time.\r\nhttps://codesandbox.io/s/czobq\r\n## ⚡ Recoil vs Redux\r\n\r\n|  | Recoil | Redux |\r\n| --- |--- |---  |\r\n|Performance|          ✅  **O(1)** |    ❌ O(n)|\r\n|Concurrent Mode        |  ✅  **yes**       |      ❌ no|\r\n|Combine states   |  ✅  **graph**      |     ❌ tree|\r\n|Boilerplate       | ✅  **1x**     |       ❌  5x|\r\n|Hooks           |✅  **built in**     |    💡 `react-redux`\r\n|Async         |  ✅  **built in**     |   💡 `redux-saga`|\r\n|Memoized       | ✅  **built in**     |💡 `reselect`|\r\n|Dynamic store  | ✅  **built in**     |    💡 `injectReducer`|\r\n|Can read Recoil states |  ✅  **yes**   | ❌ no|\r\n|Can read Redux states |  💡 `recoil-toolkit`  | ✅  **yes**|\r\n|Use outside React  |  💡 `recoil-toolkit`   | ✅  **yes**|\r\n|Dev Tools |  ✅  **yes**   | ✅  **yes**|\r\n\r\nPerformance: Recoil subscriptions are on atom/selector updaters, while in Redux are on all actions. So if you have N connected component and dispatch an action that should trigger only one component, even if re-render is stopped by useSelector optimization, redux have to execute N subscribtion callbacks.\r\n\r\nRedux will never be cm safe, without performance issue (like re-rendering everything) because it hasn't the commit phase. \r\n`dispatch` is sync (instantly executed) while `set` is async, it enqueues updaters.\r\n\r\nAtomic states design allow you more flexiblity while thinking your app as small autonomous building blocks (widgets) eventually interconnected if needed (atoms, selectors, graph connection)\r\n\r\nYou could have less than 5x boilerplate with redux, with many wrappers like RTK or redux-query, but even that you will write less code more powerful with recoil. Set(atom, value), Execute Task are much more easy concepts to managed with , than dispatch, actions, reducers, sagas, etc...\r\n\r\nRecoilReduxBridge (read redux states from recoil selectors) helps you to gradually migrate your redux monolithic app to recoil atomic states.\r\n\r\n## 💥 Demo Todolist CRUD\r\nlive: https://8u0zc.csb.app  src: [codesandbox](https://codesandbox.io/s/recoil-toolkit-main-demo-8u0zc) - [github](https://github.com/salvoravida/recoil-toolkit/tree/master/packages/demo-main)\r\n\r\n### atoms - selectors\r\n\r\n```typescript\r\nimport { atom, atomFamily, selectorFamily } from 'recoil';\r\n\r\nexport const todoList = atom\u003cItem[]\u003e({\r\n   key: 'todoList',\r\n   default: [],\r\n});\r\n\r\nexport const itemStatus = atomFamily\u003cItemStatus, number\u003e({\r\n   key: 'itemStatus',\r\n   default: ItemStatus.Idle,\r\n});\r\n\r\nexport const itemLocked = selectorFamily\u003cboolean, number\u003e({\r\n   key: 'itemLocked',\r\n   get: (id: number) =\u003e ({ get }) =\u003e get(itemStatus(id)) \u003e ItemStatus.Editing,\r\n```\r\n\r\n### tasks\r\n```typescript\r\nimport { delay, push, removeObj, updateObj, RecoilTaskInterface } from 'recoil-toolkit';\r\nimport { itemStatus, todoList } from './atoms';\r\n\r\nexport const getTodoListTask = ({ set }: RecoilTaskInterface) =\u003e async () =\u003e {\r\n   const items = (await getRemoteTodoList()) as Item[];\r\n   set(todoList, items);\r\n};\r\n\r\nexport const addItemTask = ({ set }: RecoilTaskInterface) =\u003e async (text: string) =\u003e {\r\n   const item = (await postRemoteTodoItem(text)) as Item;\r\n   set(todoList, push(item));\r\n};\r\n\r\nexport const removeItemTask = ({ set }: RecoilTaskInterface) =\u003e async (id: number) =\u003e {\r\n   try {\r\n      set(itemStatus(id), ItemStatus.Deleting);\r\n      await delRemoteTodoItem(id);\r\n      set(itemStatus(id), ItemStatus.Deleted);\r\n      await delay(1000);\r\n      set(todoList, removeObj\u003cItem\u003e({ id }));\r\n   } catch (e) {\r\n      set(itemStatus(id), ItemStatus.Idle);\r\n      throw e;\r\n   }\r\n};\r\n\r\nexport const editItemTask = ({ set }: RecoilTaskInterface) =\u003e async (item: Item) =\u003e {\r\n   try {\r\n      set(itemStatus(item.id), ItemStatus.Saving);\r\n      await putRemoteTodoItem(item);\r\n      set(itemStatus(item.id), ItemStatus.Idle);\r\n      set(todoList, updateObj(item, { id: item.id }));\r\n   } catch (e) {\r\n      set(itemStatus(item.id), ItemStatus.Editing);\r\n      throw e;\r\n   }\r\n};\r\n\r\n// task composition example\r\nexport const editAndRemoveTask = (cb: RecoilTaskInterface) =\u003e async (item: Item) =\u003e {\r\n   await editItemTask(cb)(item);\r\n   await removeItemTask(cb)(item.id);\r\n};\r\n```\r\n\r\n### hooks\r\n```typescript\r\nimport { useRecoilState, useRecoilValue } from 'recoil';\r\nimport { useRecoilTask } from 'recoil-toolkit';\r\n\r\nexport const useTodoList = () =\u003e\r\n   useRecoilTask(getTodoListTask, [], {\r\n      dataSelector: todoList,\r\n   });\r\n\r\nexport const useAddItemTask = () =\u003e\r\n   useRecoilTask(addItemTask, [], {\r\n      loaderStack: 'addItemTask',\r\n      errorStack: true,\r\n   });\r\n\r\nexport const useRemoveItemTask = () =\u003e useRecoilTask(removeItemTask, []);\r\nexport const useEditItemTask = () =\u003e useRecoilTask(editItemTask, []);\r\n```\r\n\r\n### view\r\n```typescript\r\n function Todolist() {\r\n   const { loading, data, error } = useTodoList();\r\n   return \r\n      //...\r\n }    \r\n\r\nfunction TodoItemAdd() {\r\n   const addItemTask = useAddItemTask();\r\n   const inputRef = useRef\u003cHTMLInputElement\u003e(null);\r\n   const addItem = () =\u003e {\r\n      if (inputRef.current \u0026\u0026 inputRef.current.value) {\r\n         addItemTask.execute(inputRef.current.value);\r\n         inputRef.current.value = '';\r\n      }\r\n   };\r\n   return \r\n      //...\r\n }\r\n\r\nexport function TodoItem({ id, text }: Item) {\r\n   const editTask = useEditItemTask();\r\n   const locked = useItemLocked(id);\r\n   const [status, setStatus] = useItemStatus(id);\r\n   return\r\n   //...\r\n}\r\n```   \r\n\r\n\r\n## 👏 Contributing\r\n\r\nIf you are interested in contributing to `recoil-toolkit`, open an issue or a pr!\r\n\r\n## 🎉 Thanks\r\n\r\nThank You, Open Source!\r\n\r\n## 📜 License\r\n\r\n`recoil-toolkit` is 100% free and open-source, under [MIT](LICENSE).\r\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsalvoravida%2Frecoil-toolkit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsalvoravida%2Frecoil-toolkit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsalvoravida%2Frecoil-toolkit/lists"}