{"id":14987568,"url":"https://github.com/timkindberg/zusteller","last_synced_at":"2025-04-12T00:16:55.696Z","repository":{"id":56225158,"uuid":"294776210","full_name":"timkindberg/zusteller","owner":"timkindberg","description":"Your global state savior. \"Just hooks\" + zustand.","archived":false,"fork":false,"pushed_at":"2023-05-19T13:44:32.000Z","size":286,"stargazers_count":24,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-12T00:16:49.296Z","etag":null,"topics":["constate","global-state","react","zustand"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","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/timkindberg.png","metadata":{"files":{"readme":"README.md","changelog":null,"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,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-09-11T18:16:28.000Z","updated_at":"2025-03-24T17:13:58.000Z","dependencies_parsed_at":"2024-09-25T00:33:04.728Z","dependency_job_id":null,"html_url":"https://github.com/timkindberg/zusteller","commit_stats":{"total_commits":33,"total_committers":3,"mean_commits":11.0,"dds":0.5454545454545454,"last_synced_commit":"183437a6ea78e6a76e00387490fc2c5a160d7f69"},"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/timkindberg%2Fzusteller","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/timkindberg%2Fzusteller/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/timkindberg%2Fzusteller/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/timkindberg%2Fzusteller/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/timkindberg","download_url":"https://codeload.github.com/timkindberg/zusteller/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248497817,"owners_count":21113984,"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":["constate","global-state","react","zustand"],"created_at":"2024-09-24T14:14:56.727Z","updated_at":"2025-04-12T00:16:55.585Z","avatar_url":"https://github.com/timkindberg.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# zusteller\n\nYour global state savior. \"Just hooks\" + [zustand](https://github.com/react-spring/zustand).\n\n## Disclaimer \n\nZusteller is ~~brand new,~~ experimental ~~, and under development~~.\n\nTo enable the use of hooks within zustand **we render a React element into an HTMLElement in memory and it runs the hook.** \nWhen the hook result changes, we update the zustand store. \n\nWe need more validation that this approach is performant and doesn't introduce any unexpected or dangerous bugs.\n\nAt the very minimum it serves as a proposal for how canonical React global state might be handled. \n\n[Update 08/28/21]\n\nThis is a fun experiment but I'm not super comfortable with the overall technical direction; it requires a bit of a hack to make it work.\n\nI love the API though, that was always the driving force. I stubbornly acheived it, but to what end?\n\nAt my work, we chose react-tracked to get a very similar API. It gives me (mostly) what I wanted without any implementation hacks. It uses canonical React Context which will age better and always work well as new React versions come out.\n\n## Motivation:\n\n**It is rare that I need global state. Really rare.** You can fill 99% of your needs with regular React Hooks and a fetch caching library\n(e.g. [react-query](https://react-query.tanstack.com/docs/overview) or [swr](https://github.com/vercel/swr)).\n\nHowever, when you need to use global state you have to learn a new API. Redux, Zustand, Recoil...the APIs are nice but they\nlack one main thing. **They are not \"just hooks\".**\n\nA library that only _exposes_ a hook is nice, but if it cannot _nest_ hooks within, if it can't compose hooks in both\ndirections, then it is what I'm calling a \"Terminal Hook\". **It's the end of the line.**\n\nBeing a \"Terminal Hook\" brings challenges. How do you compose or merge various global states together? Redux has \n`combineReducers`. Recoil has `Selectors`. **Hooks compose naturally.**\n\nZustand was one of the first libraries to figure out how to elegantly share state without Context. It also has\nselectors, a required feature when it's time to optimize performance. Zustand is my go-to global state solution and I consider\nit to be a great accomplishment. **But it uses a custom API and I really like hooks.**\n\n#### So... what if Zustand could work with regular hooks?\n\nIt might look something like this. \n\n----\n\n## \"Just hooks\" + Zustand\n\n### Pass create a hook\n\nFirst import `create` from `zusteller`.\n\n```js\nimport create from 'zusteller'\n```\n\nPass `create` a hook. \n\n```js\nconst useMyState = () =\u003e useState(42)\nconst useStore = create(useMyState)\n```\n\nOr pass an inline-anonymous hook.\n\n```js\nconst useStore = create(() =\u003e useState(42))\n```\n\nNow components can share the same state. When state updates they will all re-render. \n```js\nconst ComponentA = () =\u003e {\n  const [state, setState] = useStore()\n}\nconst ComponentB = () =\u003e {\n  const [state, setState] = useStore()  \n}\n```\n\n### Perform Logic and Compose Other Hooks\n\nUse as many `useState` as you need.\n\n```js\nconst useStore = create(() =\u003e {\n  const [foo, setFoo] = useState()\n  const [bar, setBar] = useState()\n  //... do some logic\n  return //... some things ...\n})\n```\n\nUse other custom hooks together.\n\n```js\nconst useStore = create(() =\u003e {\n  const name = useUserName()\n  const locale = useLocale()\n  return { name, locale }\n})\n```\n\nUse 3rd party hooks.\n\n```js\nimport useImmer from 'use-immer'\nimport usePromise from 'react-use-promise'\nconst useStore = create(() =\u003e {\n  const [person, updatePerson] = useImmer({\n    id: 1,\n    name: \"Michael\",\n    age: 33\n  });\n  const [products, error] = usePromise(fetch('/api/cart' + person.id))\n  return {products, person, updatePerson}\n})\n```\n\n\u003e Note: Contextual hooks will not work, see the section at the bottom.\n\n### The returned zustand hook\n\nThe hook you are returned is a small wrapper around a regular [zustand](https://github.com/react-spring/zustand) hook object.\n\nUse it in multiple React components. The state will be shared.\n\n```js\nconst useStore = create(useState)\n\nconst ComponentA = () =\u003e {\n  const [foo, setFoo] = useStore()\n}\nconst ComponentB = () =\u003e {\n  const [foo, setFoo] = useStore()\n}\n```\n\nUse zustand's selector functionality normally, [reference their docs](https://github.com/react-spring/zustand#selecting-multiple-state-slices) for more info.\n\n```js\nconst useStore = create(useState)\n\n// ComponentA only rerenders if `foo` changes\nconst ComponentA = () =\u003e {\n  const foo = useStore(s =\u003e s[0])\n}\n\n// ComponentA only rerenders if `setFoo` changes\nconst ComponentB = () =\u003e {\n  const setFoo = useStore(s =\u003e s[1])\n}\n```\n\nUse it outside of React, using the `getState` prototype method.\n\n\u003e zustand has a `setState` method on the hook, but zusteller does not.\n\n```js\nconst useStore = create(useState)\n\nconst unsub = useStore.subscribe(console.log, s =\u003e s[0]) // Log anytime foo changes\n\nconst [foo, setFoo] = useStore.getState()\ndocument.getElementById('button').on('click', () =\u003e setFoo('bar'))\n\nunsub()\n```\n\nWhile zustand can only store `object` state, zusteller allows `literals, objects, arrays, and undefined/null`.\n\n```js\nconst useStore = create(() =\u003e {\n  if (false) return { foo: true }\n  return 'a regular string'\n})\n\n// Just be sure to protect your selectors if the return type can be variable\nconst Component = () =\u003e {\n  const msg = useStore(s =\u003e s?.foo)\n}\n```\n\n### Passing Parameters to The Store's Underlying Hook\n\nThis is called atomFamily in Recoil and Jotai. It's the ability to create many forked instances of the store based on\nparameters passed in during usage.\n\nSo if our hook took a parameter, for example an id.\n```js\nconst useUserStore = create(id =\u003e {\n  return useUser(id)\n})\n```\n\nYou can provide the parameters by passing them in an array as the first argument to the store.\n```js\nconst ComponentA = () =\u003e {\n  const user = useUserStore([42])\n}\n```\n\nEach unique combination of parameters gets its own store instance. If two or more components pass the same parameters,\nthey will share a store.\n```js\nconst ComponentA = () =\u003e {\n  const user = useUserStore([42])\n}\nconst ComponentB = () =\u003e {\n  const user = useUserStore([96])\n}\nconst ComponentC = () =\u003e {\n  const user = useUserStore() // undefined is it's own unique parameter\n}\nconst ComponentD = () =\u003e {\n  const user = useUserStore([42]) // D will share a store with A\n}\n```\n\nThe arguments you pass to the hook are safely memo-ized (just like react-query does). For example this is fine.\n```js\nconst ComponentA = () =\u003e {\n  const user = useUserStore([42, { foo: true, bar: [1, 2, 3] }, 'hello', null])\n}\n```\n\n## Examples\n\n\u003cdetails\u003e\n  \u003csummary\u003eMigrate Zustand's Doc Examples\u003c/summary\u003e\n  \n\u003e You'll have to follow along at https://github.com/pmndrs/zustand/blob/master/README.md\n\u003e I only recreate the code blocks not all of the text.\n\n```js\nimport create from 'zusteller'\n\nconst useStore = create(() =\u003e {\n  const [bears, setBears] = useState(0)\n  const increasePopulation = () =\u003e setBears(prev =\u003e prev + 1)\n  const removeAllBears = () =\u003e setBears(0)\n  return { bears, increasePopulation, removeAllBears }\n})\n```\n\n```jsx\nfunction BearCounter() {\n  const bears = useStore(state =\u003e state.bears)\n  return \u003ch1\u003e{bears} around here ...\u003c/h1\u003e\n}\n\nfunction Controls() {\n  const increasePopulation = useStore(state =\u003e state.increasePopulation)\n  return \u003cbutton onClick={increasePopulation}\u003eone up\u003c/button\u003e\n}\n```\n\n### Async actions\n\nI'd just use react-query for this but let's recreate it anyway.\n\n```js\nimport create from 'zusteller'\n\nconst useStore = create(() =\u003e {\n  const [fishies, setFishies] = useState({})\n  const fetch = async pond =\u003e {\n    const response = await fetch(pond)\n    setFishies(await response.json())\n  }\n  return {fishies, fetch}\n})\n```\n\nOh wait, but now we can compose other hooks! So we *can* use react-query. Would you look at that?\n\n```js\nimport create from 'zusteller'\nimport { useQuery } from 'react-query'\n\nconst useStore = create(() =\u003e {\n  const [pond, setPond] = useState('foo')\n  const { data, ...queryInfo } = useQuery('fishies', () =\u003e fetch(`/api/${pond}`))\n  // Maybe you need to alter the response in some way?\n  // Who knows why people need global state... :shrug\n  const fishies = data.map(fish =\u003e fish.slippery = true)\n  return {fishies, queryInfo, setPond}\n})\n\n```\n\n### Reading/writing state and reacting to changes outside of components\n\nWorks just like zustand.\n\n\u003e Except there is no `setState` prototype method. You must use methods exposed by\nyour hook to modify the internal hook's state.\n\n```js\nconst useStore = create(() =\u003e useState({ paw: true, snout: true, fur: true }))\n\n// Getting non-reactive fresh state\nconst paw = useStore.getState().paw\n// Listening to all changes, fires on every change\nconst unsub1 = useStore.subscribe(console.log)\n// Listening to selected changes, in this case when \"paw\" changes\nconst unsub2 = useStore.subscribe(console.log, state =\u003e state.paw)\n// Subscribe also supports an optional equality function\nconst unsub3 = useStore.subscribe(console.log, state =\u003e [state.paw, state.fur], shallow)\n// Updating state, will trigger listeners\nconst [, setState] = useStore.getState()\nsetState(prev =\u003e ({ ...prev, paw: false }))\n// Unsubscribe listeners\nunsub1()\nunsub2()\nunsub3()\n// Destroying the store (removing all listeners)\nuseStore.destroy()\n```\n\n### Using zusteller without React\n\nNot possible. Use zustand. Zusteller uses hooks, and hooks must be run using react and react-dom.\n\n### Want to use immer? \n\nUse a 3rd party immer hook or write your own.\n\n```js\nimport create from 'zusteller'\nimport produce from 'immer'\n\nconst useImmerState = initialState =\u003e {\n    const [state, setState] = useState(initialState)\n    const setImmerState = useCallback(setter =\u003e setState(produce(setter)), [])\n    return [state, setImmerState]\n}\n\nconst useStore = create(() =\u003e useImmerState({ lush: { forrest: { contains: { a: \"bear\" } } } }))\n\nfunction Component() {\n    const [state, setState] = useStore()\n    setState(state =\u003e {\n      state.lush.forrest.contains = null\n    })\n}\n```\n\n### Can't live without redux-like reducers and action types?\n\nNo judgement I guess. Here's how you do it, you just use `useReducer`. Simple.\n\n```js\nimport create from 'zusteller'\nimport { useReducer } from 'react'\n\nconst types = { increase: \"INCREASE\", decrease: \"DECREASE\" }\n\nconst reducer = (state, { type, by = 1 }) =\u003e {\n  switch (type) {\n    case types.increase: return { grumpiness: state.grumpiness + by }\n    case types.decrease: return { grumpiness: state.grumpiness - by }\n  }\n}\n\nconst useStore = create(() =\u003e useReducer(reducer, {grumpiness: 0}))\n\nfunction Component() {\n  const [state, dispatch] = useStore()\n  dispatch({ type: types.increase, by: 2 })\n}\n```\n\u003c/details\u003e\n\n\n\n\n\n\n\n\n\u003cdetails\u003e\n  \u003csummary\u003eMigrate Constate's Doc Examples\u003c/summary\u003e\n  \n\u003e You'll have to follow along at https://github.com/diegohaz/constate/blob/master/README.md\n\u003e I only recreate the code blocks not all of the text.\n\n```jsx\nimport React, { useState } from \"react\";\nimport create from \"zusteller\";\n\n// 1️⃣ Create a custom hook as usual\nfunction useCounter() {\n  const [count, setCount] = useState(0);\n  const increment = () =\u003e setCount(prevCount =\u003e prevCount + 1);\n  return { count, increment };\n}\n\n// 2️⃣ Wrap your hook with the create function\nconst useCounterStore = create(useCounter);\n\nfunction Button() {\n  // 3️⃣ Use store hook instead of custom hook\n  const { increment } = useCounterStore();\n  return \u003cbutton onClick={increment}\u003e+\u003c/button\u003e;\n}\n\nfunction Count() {\n  // 4️⃣ Use store hook in other components\n  const { count } = useCounterStore();\n  return \u003cspan\u003e{count}\u003c/span\u003e;\n}\n\nfunction App() {\n  // 5️⃣ DO NOT wrap your components with Provider\n  return (\n    \u003c\u003e\n      \u003cCount /\u003e\n      \u003cButton /\u003e\n    \u003c/\u003e\n  );\n}\n```\n\nAdvanced Example\n\n```jsx\nimport React, { useState, useCallback } from \"react\";\nimport create from \"zusteller\";\n\n// 1️⃣ Create a custom hook that receives props\nfunction useCounter({ initialCount = 0 }) {\n  const [count, setCount] = useState(initialCount);\n  // 2️⃣ Wrap your updaters with useCallback or use dispatch from useReducer\n  const increment = useCallback(() =\u003e setCount(prev =\u003e prev + 1), []);\n  return { count, increment };\n}\n\n// 3️⃣ Wrap your hook with the constate factory splitting the values\n// 3.5 Pass props to your hook\nconst useCounterStore = create(() =\u003e useCounter({ initialCount: 10 }));\n\nfunction Button() {\n  // 4️⃣ Select just the increment function that will never trigger a re-render\n  // 4.5 we get at it via our selector\n  const increment = useCounterStore(s =\u003e s.increment);\n  return \u003cbutton onClick={increment}\u003e+\u003c/button\u003e;\n}\n\nfunction Count() {\n  // 5️⃣ Use the state in other components\n  // 5.5 Use the selector to only subscribe to the count\n  const count = useCount(s =\u003e s.count);\n  return \u003cspan\u003e{count}\u003c/span\u003e;\n}\n\nfunction App() {\n  // 6️⃣ DO NOT wrap your components with Provider \n  return (\n    \u003c\u003e\n      \u003cCount /\u003e\n      \u003cButton /\u003e\n    \u003c/\u003e\n  );\n}\n```\n\u003c/details\u003e\n\n\n\n\n\n\n\n\n\u003cdetails\u003e\n  \u003csummary\u003eMigrate Recoil's Atoms Tutorial\u003c/summary\u003e\n  \n\u003e You'll have to follow along at https://recoiljs.org/docs/basic-tutorial/atoms\n\u003e I only recreate the code blocks not all of the text.\n\n```js\nconst useTodoListStore = create(() =\u003e {\n  // I'm gonna use this immer hook... because it'll make mutations easier\n  // You can see the implementation up above somewhere\n  const [todoList, setTodoList] = useImmerState([])\n  return { todoList }\n})\n```\n\n```jsx\nfunction TodoList() {\n  const todoList = useTodoListStore(s =\u003e s.todoList);\n\n  return (\n    \u003c\u003e\n      {}\n      {}\n      \u003cTodoItemCreator /\u003e\n\n      {todoList.map((todoItem) =\u003e (\n        \u003cTodoItem key={todoItem.id} item={todoItem} /\u003e\n      ))}\n    \u003c/\u003e\n  );\n}\n```\n\n```jsx\n// Modify our hook to add the \"addTodo\" logic **there**\n// We should keep the business logic together\nconst useTodoListStore = create(() =\u003e {\n  const [todoList, setTodoList] = useImmerState([])\n\n  // Wrap these bad boys in a memo so they won't cause rerenders\n  // when they are selected\n  const todoActions = useMemo(() =\u003e ({\n    add: text =\u003e setTodoList(draft =\u003e {\n      draft.push({ id: getId(), text, isComplete: false })\n    })\n  }), [])\n\n  return { todoList, todoActions }\n})\n    \n\nfunction TodoItemCreator() {\n  const [inputValue, setInputValue] = useState('');\n  const todoActions = useTodoListStore(s =\u003e s.todoActions);\n\n  const addItem = () =\u003e {\n    todoActions.add(inputValue)\n    setInputValue('');\n  };\n\n  const onChange = ({target: {value}}) =\u003e {\n    setInputValue(value);\n  };\n\n  return (\n    \u003cdiv\u003e\n      \u003cinput type=\"text\" value={inputValue} onChange={onChange} /\u003e\n      \u003cbutton onClick={addItem}\u003eAdd\u003c/button\u003e\n    \u003c/div\u003e\n  );\n}\n\nlet id = 0;\nfunction getId() {\n  return id++;\n}\n```\n\n```jsx\n// Modify our hook to add the Edit, Toggle and Delete logic\n// Again, trying to keep busineses logic together\nconst useTodoListStore = create(() =\u003e {\n  const [todoList, setTodoList] = useImmerState([])\n\n  // Wrap these bad boys in a memo so they won't cause rerenders\n  // when they are selected\n  const todoActions = useMemo(() =\u003e ({\n    add: text =\u003e setTodoList(draft =\u003e {\n      draft.push({ id: getId(), text, isComplete: false })\n    }),\n    edit: (todo, text) =\u003e setTodoList(draft =\u003e {\n      const todo = draft.find(t =\u003e t.id === todo.id)\n      todo.text = text\n    }),\n    toggle: todo =\u003e setTodoList(draft =\u003e {\n      const todo = draft.find(t =\u003e t.id === todo.id)\n      todo.isComplete = !todo.isComplete\n    }),\n    delete: todo =\u003e setTodoList(draft =\u003e {\n      return draft.filter(t =\u003e t.id !== todo.id)\n    })\n  }), [])\n\n  return { todoList, todoActions }\n})\n\nfunction TodoItem({item}) {\n  const todoActions = useTodoListStore(s =\u003e s.todoActions)\n\n  return (\n    \u003cdiv\u003e\n      \u003cinput type=\"text\" value={item.text} onChange={e =\u003e todoActions.edit(item, e.target.value)} /\u003e\n      \u003cinput\n        type=\"checkbox\"\n        checked={item.isComplete}\n        onChange={() =\u003e todoActions.toggle(item)}\n      /\u003e\n      \u003cbutton onClick={() =\u003e deleteItem(item)}\u003eX\u003c/button\u003e\n    \u003c/div\u003e\n  );\n}\n```\n\u003c/details\u003e\n\n\n\n\n\n\n\n\n\u003cdetails\u003e\n  \u003csummary\u003eMigrate Recoil's Selectors Tutorial\u003c/summary\u003e\n  \n  \u003e These code example reference variables created in the previous section\n  \n  \u003e You'll have to follow along at https://recoiljs.org/docs/basic-tutorial/selectors\n  \u003e I only recreate the code blocks not all of the text.\n  \n```js\nconst useTodoListFilterStore = create(() =\u003e useState('Show All'));\n```\n\n```js\nconst useFilteredTodoListStore = create(() =\u003e {\n  const [filter] = useFilteredTodoListStore()\n  const { todoList } = useTodoListStore()\n  switch (filter) {\n    case 'Show Completed':\n      return todoList.filter((item) =\u003e item.isComplete);\n    case 'Show Uncompleted':\n      return todoList.filter((item) =\u003e !item.isComplete);\n    default:\n      return todoList;\n  }\n})\n```\n\n\u003e Side Note: In their tutorial they say \"The filteredTodoListState internally keeps track of two dependencies: \n\u003e todoListFilterState and todoListState so that it re-runs if either of those change.\"\n\u003e\n\u003e That is what hooks do!\n\n```jsx\nfunction TodoList() {\n  const todoList = useFilteredTodoListStore();\n\n  return (\n    \u003c\u003e\n      \u003cTodoListStats /\u003e\n      \u003cTodoListFilters /\u003e\n      \u003cTodoItemCreator /\u003e\n\n      {todoList.map((todoItem) =\u003e (\n        \u003cTodoItem item={todoItem} key={todoItem.id} /\u003e\n      ))}\n    \u003c/\u003e\n  );\n}\n```\n\n```jsx\nfunction TodoListFilters() {\n  const [filter, setFilter] = useTodoListFilterStore();\n\n  const updateFilter = ({target: {value}}) =\u003e {\n    setFilter(value);\n  };\n\n  return (\n    \u003c\u003e\n      Filter:\n      \u003cselect value={filter} onChange={updateFilter}\u003e\n        \u003coption value=\"Show All\"\u003eAll\u003c/option\u003e\n        \u003coption value=\"Show Completed\"\u003eCompleted\u003c/option\u003e\n        \u003coption value=\"Show Uncompleted\"\u003eUncompleted\u003c/option\u003e\n      \u003c/select\u003e\n    \u003c/\u003e\n  );\n}\n```\n\n```js\nconst useTodoListStatsStore = create(() =\u003e {\n  const { todoList } = useTodoListStore()\n  const totalNum = todoList.length;\n  const totalCompletedNum = todoList.filter((item) =\u003e item.isComplete).length;\n  const totalUncompletedNum = totalNum - totalCompletedNum;\n  const percentCompleted = totalNum === 0 ? 0 : totalCompletedNum / totalNum;\n\n  return {\n    totalNum,\n    totalCompletedNum,\n    totalUncompletedNum,\n    percentCompleted,\n  };\n})\n```\n\n```jsx\nfunction TodoListStats() {\n  const {\n    totalNum,\n    totalCompletedNum,\n    totalUncompletedNum,\n    percentCompleted,\n  } = useTodoListStatsStore();\n\n  const formattedPercentCompleted = Math.round(percentCompleted * 100);\n\n  return (\n    \u003cul\u003e\n      \u003cli\u003eTotal items: {totalNum}\u003c/li\u003e\n      \u003cli\u003eItems completed: {totalCompletedNum}\u003c/li\u003e\n      \u003cli\u003eItems not completed: {totalUncompletedNum}\u003c/li\u003e\n      \u003cli\u003ePercent completed: {formattedPercentCompleted}\u003c/li\u003e\n    \u003c/ul\u003e\n  );\n}\n```\n\u003c/details\u003e\n\n\n\n\n\n\n\n\n\u003cdetails\u003e\n  \u003csummary\u003eMigrate Recoil's Asynchronous Data Queries Guide\u003c/summary\u003e\n  \n\u003e You'll have to follow along at https://recoiljs.org/docs/guides/asynchronous-data-queries\n\u003e I only recreate the code blocks not all of the text.\n\n### Synchronous Example\n\n```jsx\nconst useCurrentUserIDStore = create(() =\u003e useState(1))\n\nconst useCurrentUserNameStore = create(() =\u003e {\n  const [id] = useCurrentUserIDStore()\n  return tableOfUsers[id].name;\n});\n\nfunction CurrentUserInfo() {\n  const userName = useCurrentUserNameStore();\n  return \u003cdiv\u003e{userName}\u003c/div\u003e;\n}\n\nfunction MyApp() {\n  return (\n    \u003cCurrentUserInfo /\u003e\n  );\n}\n```\n\n### Asynchronous Example\n\nHey let's use react-query my current favorite library! I mean really this example doesn't\neven need global state at all... react-query is all you need here. But I'll show it anyway.\n\n```jsx\nimport { useQuery } from 'react-query'\n\nconst useCurrentUserNameStore = create(() =\u003e {\n  const [id] = useCurrentUserIDStore()\n  const { data } = useQuery(['user/details', id], (_, id) =\u003e myDBQuery({ userID: id }))\n  return data?.name;\n})\n\nfunction CurrentUserInfo() {\n  const userName = useCurrentUserNameStore();\n  return \u003cdiv\u003e{userName}\u003c/div\u003e;\n}\n```\n\nHmm ok so this one... I mean Suspense isn't really supported. It's not NOT supported either.\nLike if you set the `suspense: true` option in react-query it will work. But I have a hot take,\nmaybe suspense is not so great. I prefer managing the loading state inside the component that\nis actually loading!! This way I can show a custom tailored skeleton, or continue showing\nstale data when it's refetching.\n\n```jsx\nfunction MyApp() {\n  return (\n    \u003cRecoilRoot\u003e\n      \u003cReact.Suspense fallback={\u003cdiv\u003eLoading...\u003c/div\u003e}\u003e\n        \u003cCurrentUserInfo /\u003e\n      \u003c/React.Suspense\u003e\n    \u003c/RecoilRoot\u003e\n  );\n}\n```\n\nI'm not gonna talk about ErrorBoundaries, whatever.\n\n### Queries with Parameters\n\nOk now this one is interesting let's see... I'd just react-query for this without zusteller.\n\n```jsx\nfunction UserInfo(id) {\n  const { data } = useQuery(['user/details', id], (_, id) =\u003e myDBQuery({ userID: id }))\n  return \u003cdiv\u003e{data?.name}\u003c/div\u003e;\n}\n```\n\nBut that's cheating... so what if we *needed* to pass parameters to our hook? Hmm...\nok let's just pretend react-query wasn't invented yet.\n\n```jsx\n// So this is basically a poor man's react-query\nconst useUserNameStore = create((userID) =\u003e {\n  const [name, setName] = useState('')\n  const [error, setError] = useState()\n  useEffect(() =\u003e {\n    myDBQuery({userID}).then(response =\u003e {\n      setResponse(response.error ?? response.name)\n    });\n  }, [id])\n  if (error) return error\n  return name\n})\n\nfunction UserInfo({ id }) {\n  // Provide a hookArgs array as the first param to the store hook\n  const { data } = useUserNameStore([id])\n  return \u003cdiv\u003e{data?.name}\u003c/div\u003e;\n}\n```\n\nOr we could use a 3rd party library hook like react-use-promise. It's a little nicer,\nbut again react-query would just be better here. But this illustrates passing in parameters.\n```jsx\nimport usePromise from 'react-use-promise'\n\nconst useUserNameStore = create((userID) =\u003e {\n  const [result, error] = usePromise(myDBQuery({userID}))\n  return error ?? result.name\n})\n\nfunction UserInfo(id) {\n  const { data } = useUserNameStore([id])\n  return \u003cdiv\u003e{data?.name}\u003c/div\u003e;\n}\n```\n\u003c/details\u003e\n\n\n\n\n\n\n\u003cdetails\u003e\n  \u003csummary\u003eWhat about `React.Context`?\u003c/summary\u003e\n  \nThis only partially works. It works ok with global themes or other providers that wrap your whole app.\n\nBut it's can behave badly if you have components sharing the same store but living under different contexts.\n\n```js\n// Make a Context\nconst SomeContext = React.createContext()\n\n// Make a Zusteller store that uses the context\nconst useContextStore = create(() =\u003e useContext(SomeContext))\n\n// Make a component that uses the zusteller store\nconst Component = () =\u003e {\n  const value = useContextStore()\n  return \u003cdiv\u003e{value}\u003c/div\u003e\n}\n\n// Have two providers, each a Component in each\nconst App = () =\u003e {\n  return (\n    \u003c\u003e\n      \u003cSomeContext.Provider value={true}\u003e\n        \u003cComponent/\u003e\n      \u003c/SomeContext.Provider\u003e\n      \u003cSomeContext.Provider value={false}\u003e\n        \u003cComponent/\u003e\n      \u003c/SomeContext.Provider\u003e\n    \u003c/\u003e\n  )\n}\n```\nThat's because the underlying hook is being run only by the first component that uses the store hook.\nSo context will be relative to that first subscribing component. So in the example above, both components\nwould like use the value of `true`. So yeah...\n\nOne way we could fix this in the future is by returning a React element from `create`.\nYou could then place this element anywhere as your Context.Consumer location.\n\n```jsx\nconst SomeContext = React.createContext()\n\n// This is not yet possible, just showing an idea\n// Maybe we have an additional `create.withManualInsert`\n// Idk what name to give it...\nconst useStore = create.withManualInsert(() =\u003e {\n  return useContext(SomeContext)\n})\n\nconst App = () =\u003e {\n  return (\n    \u003cSomeContext.Provider\u003e\n      \u003cuseStore.ContextConsumerPoint /\u003e // Now all useStore usages will pick up the context from an intentional place\n    \u003c/SomeContext.Provider\u003e\n  )\n}\n```\n\nMaybe we also have a way to return it's own context. For a `constate` flavor. \nThis would lower the state from global to contextual.\n\n```jsx\n// This is not yet possible, just showing an idea\n// Maybe we have an additional `create.withContext`\nconst useStore = create.withContext(MyContext =\u003e () =\u003e {\n  return useContext(MyContext)\n})\n\nconst App = () =\u003e {\n  return (\n    // Now useStore has both a Provider AND a Store insertion point :shrug??\n    \u003cuseStore.Provider\u003e\n      \u003cuseStore.Store /\u003e\n    \u003c/useStore.Provider\u003e\n  )\n}\n\n```\n\u003c/details\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftimkindberg%2Fzusteller","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftimkindberg%2Fzusteller","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftimkindberg%2Fzusteller/lists"}