{"id":15648317,"url":"https://github.com/gadingnst/swr-global-state","last_synced_at":"2025-04-16T03:48:59.250Z","repository":{"id":61118322,"uuid":"457708314","full_name":"gadingnst/swr-global-state","owner":"gadingnst","description":"♻️ Zero-setup \u0026 simple global state management for React Components. It's similiar `useState` hooks like we use usual!","archived":false,"fork":false,"pushed_at":"2023-07-28T23:44:37.000Z","size":784,"stargazers_count":43,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-29T04:51:12.180Z","etag":null,"topics":["cache","context-api","context-api-react","global-state","global-state-management","global-state-store","hacktoberfest","hacktoberfest2022","javascript","react","react-state-management","redux-alternative","state-management","state-management-in-react","state-manager","swr","typescript","use-state","use-swr"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/swr-global-state","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/gadingnst.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","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},"funding":{"ko_fi":"gadingnst","custom":["https://trakteer.id/gadingnst","https://karyakarsa.com/gadingnst"]}},"created_at":"2022-02-10T09:16:13.000Z","updated_at":"2024-11-04T04:48:48.000Z","dependencies_parsed_at":"2024-10-03T12:24:45.191Z","dependency_job_id":"cd0beff7-074e-4f15-80f9-c5184d650cc2","html_url":"https://github.com/gadingnst/swr-global-state","commit_stats":{"total_commits":122,"total_committers":3,"mean_commits":"40.666666666666664","dds":"0.39344262295081966","last_synced_commit":"67a9fce98d6ab9b1ecc1c0b4bf0ba4a5540447e0"},"previous_names":[],"tags_count":22,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gadingnst%2Fswr-global-state","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gadingnst%2Fswr-global-state/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gadingnst%2Fswr-global-state/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gadingnst%2Fswr-global-state/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/gadingnst","download_url":"https://codeload.github.com/gadingnst/swr-global-state/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249192458,"owners_count":21227792,"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":["cache","context-api","context-api-react","global-state","global-state-management","global-state-store","hacktoberfest","hacktoberfest2022","javascript","react","react-state-management","redux-alternative","state-management","state-management-in-react","state-manager","swr","typescript","use-state","use-swr"],"created_at":"2024-10-03T12:24:30.738Z","updated_at":"2025-04-16T03:48:59.232Z","avatar_url":"https://github.com/gadingnst.png","language":"TypeScript","funding_links":["https://ko-fi.com/gadingnst","https://trakteer.id/gadingnst","https://karyakarsa.com/gadingnst"],"categories":[],"sub_categories":[],"readme":"# ♻️ SWR Global State\n\n[![npm](https://img.shields.io/npm/v/swr-global-state.svg)](https://www.npmjs.com/package/swr-global-state)\n[![npm](https://img.shields.io/npm/dt/swr-global-state.svg)](https://npm-stat.com/charts.html?package=swr-global-state)\n[![GitHub issues](https://img.shields.io/github/issues/gadingnst/swr-global-state.svg)](https://github.com/gadingnst/swr-global-state/issues)\n[![Rate this package](https://badges.openbase.com/js/rating/swr-global-state.svg?token=zPIpBONkJ6OOQJcWjHNPStKx99B8TV6v9QKQ7ObwBlg=)](https://openbase.com/js/swr-global-state?utm_source=embedded\u0026amp;utm_medium=badge\u0026amp;utm_campaign=rate-badge)\n\nZero-setup \u0026 simple global state management for React Components based on [SWR](https://swr.vercel.app) helpers. With this library, you can focus on your awesome React Project and not waste another afternoon on the setup \u0026 configuring your global state. 🌄\n\n# Table of Contents\n- [♻️ SWR Global State](#️-swr-global-state)\n- [Table of Contents](#table-of-contents)\n- [Getting Started](#getting-started)\n  - [Install](#install)\n    - [NPM](#npm)\n    - [Yarn](#yarn)\n  - [Usage](#usage)\n    - [Creating a Store](#creating-a-store)\n    - [Using store on your component](#using-store-on-your-component)\n    - [Persisted State](#persisted-state)\n      - [Creating Persisted State](#creating-persisted-state)\n      - [Reusable Persistor (Example in TypeScript)](#reusable-persistor-example-in-typescript)\n      - [Asynchronous Persistor](#asynchronous-persistor)\n      - [Best Practice with Persistor](#best-practice-with-persistor)\n    - [Custom hooks](#custom-hooks)\n- [Demo](#demo)\n- [FAQ](#faq)\n  - [Why should I use this?](#why-should-i-use-this)\n  - [If this library can cover `Redux`, how about asynchronous state management like `redux-saga`, `redux-thunk`, or `redux-promise`?](#if-this-library-can-cover-redux-how-about-asynchronous-state-management-like-redux-saga-redux-thunk-or-redux-promise)\n  - [React Native](#react-native)\n- [Publishing](#publishing)\n- [License](#license)\n- [Feedbacks and Issues](#feedbacks-and-issues)\n- [Support](#support)\n  - [Global](#global)\n  - [Indonesia](#indonesia)\n\n# Getting Started\n## Install\n### NPM\n```bash\nnpm i swr swr-global-state\n```\n### Yarn\n```bash\nyarn add swr swr-global-state\n```\n\n## Usage\n### Creating a Store\nCreate a new file for your global state on your root directory. And then, use `createStore`. Example: `stores/counter.js`\n```js\n// file: stores/counter.js\n\nimport { createStore } from \"swr-global-state\";\n\nconst useCounter = createStore({\n  key: \"@app/counter\", // (Required) state key with unique string\n  initial: 0 // \u003c- (Required) initial state\n});\n\nexport default useCounter;\n```\n\n### Using store on your component\nYou just import stores that you have created into your any components, then use it like you use `useState` as usual.\n```jsx\n// file: components/SetCountComponent.js\n\nimport useCounter from \"stores/counter\";\n\nfunction SetCountComponent() {\n  const [, setCount] = useCounter(); // \u003c- `[, ]` skipping first index of the array.\n  return (\n    \u003cdiv\u003e\n      \u003cbutton onClick={() =\u003e setCount(prev =\u003e prev - 1)}\u003e\n        (-) Decrease Count\n      \u003c/button\u003e\n      \u0026nbsp;\n      \u003cbutton onClick={() =\u003e setCount(prev =\u003e prev + 1)}\u003e\n        (+) Increase Count\n      \u003c/button\u003e\n    \u003c/div\u003e\n  );\n}\n\nexport default SetCountComponent;\n```\n\n```jsx\n// file: components/GetCountComponent.js\n\nimport useCounter from \"stores/counter\";\n\nfunction GetCountComponent() {\n  const [count] = useCounter();\n  return (\n    \u003cdiv\u003e\n      \u003cp\u003eCurrent Count: {count}\u003c/p\u003e\n    \u003c/div\u003e\n  );\n}\n\nexport default GetCountComponent;\n```\n\n### Persisted State\n#### Creating Persisted State\nOptionally, you can define `persistor` object to create custom persistor to hold your state even user has closing app/browser, and re-opened it.\nIn this example, we use `localStorage` to hold our state.\n```js\n// file: stores/counter.js\n\nimport { createStore } from \"swr-global-state\";\n\nconst useCounter = createStore({\n  key: \"@app/counter\",\n  initial: 0,\n  persistor: { // \u003c- Optional, use this if you want hold the state\n    onSet: (key, data) =\u003e {\n      window.localStorage.setItem(String(key), data);\n    },\n    onGet: (key) =\u003e {\n      const cachedData = window.localStorage.getItem(String(key));\n      return Number(cachedData);\n    }\n  }\n});\n\nexport default useCounter;\n```\n\n#### Reusable Persistor (Example in TypeScript)\nWe can create reusable `persistor` to re-use in every stores that we have created. Example:\n```ts\n// file: persistors/local-storage.ts\n\nimport type { StatePersistor, StateKey } from \"swr-global-state\";\n\nconst withLocalStoragePersistor = \u003cT = any\u003e(): StatePersistor\u003cT\u003e =\u003e ({\n  onSet(key: StateKey, data: T) {\n    const stringifyData = JSON.stringify(data);\n    window.localStorage.setItem(String(key), stringifyData);\n  },\n  onGet(key: StateKey) {\n    const cachedData = window.localStorage.getItem(String(key)) ?? \"null\";\n    try {\n      return JSON.parse(cachedData);\n    } catch {\n      return cachedData;\n    }\n  }\n});\n\nexport default withLocalStoragePersistor;\n```\n\nNow, we can use that `withLocalStoragePersistor` in that like this:\n```ts\n// file: stores/counter.ts\n\nimport { createStore } from \"swr-global-state\";\nimport withLocalStoragePersistor from \"persistors/local-storage\";\n\nconst useCounter = createStore\u003cnumber\u003e({\n  key: \"@app/counter\",\n  initial: 0,\n  persistor: withLocalStoragePersistor()\n});\n\nexport default useCounter;\n```\n\n```ts\n// file: stores/theme.ts\n\nimport { createStore } from \"swr-global-state\";\nimport withLocalStoragePersistor from \"persistors/local-storage\";\n\nconst useTheme = createStore\u003cstring\u003e({\n  key: \"@app/theme\",\n  initial: \"light\",\n  persistor: withLocalStoragePersistor()\n});\n\nexport default useTheme;\n```\n\n#### Asynchronous Persistor\nJust use ***async function or promise*** as usual in `onSet` and `onGet`.\n```js\n// file: stores/counter.js\n\nimport AsyncStorage from '@react-native-async-storage/async-storage';\nimport { createStore } from \"swr-global-state\";\n\nconst useCounter = createStore({\n  key: \"@app/counter\",\n  initial: 0,\n  persistor: {\n    async onSet(key, data) {\n      try {\n        await AsyncStorage.setItem(String(key), data);\n      } catch (err) {\n        // handle saving error, default throw an error\n        throw new Error(err);\n      }\n    },\n    async onGet(key) {\n      try {\n        const value = await AsyncStorage.getItem(String(key));\n        return Number(value);\n      } catch (err) {\n        // handle error reading value\n        throw new Error(err);\n      }\n    }\n  }\n});\n\nexport default useCounter;\n```\n\n#### Best Practice with Persistor\nBest practice in using `persistor` is use [Debouncing Technique](https://www.google.com/search?q=debounce+technique+programming). This example is using `debouncing` in `onSet` callback. So, it will not spamming to call the callback request every state changes.\n```js\nimport AsyncStorage from '@react-native-async-storage/async-storage';\nimport { createStore } from \"swr-global-state\";\n\nconst withDebounce = (fn, time) =\u003e {\n  let timeoutId;\n  const wrapper = (...args) =\u003e {\n    if (timeoutId) {\n      clearTimeout(timeoutId);\n    }\n    timeoutId = setTimeout(() =\u003e {\n      timeoutId = null;\n      fn(...args);\n    }, time);\n  };\n  return wrapper;\n};\n\nconst useUser = createStore({\n  key: \"@app/user\",\n  initial: null,\n  persistor: {\n    onSet: withDebounce(async(key, user) =\u003e {\n      try {\n        const stringifyUser = JSON.stringify(user)\n        await AsyncStorage.setItem(String(key), stringifyUser);\n      } catch (err) {\n        // handle saving error, default throw an error\n        throw new Error(err);\n      }\n    }, 1000), // debounce-effect in 1 second.\n    ...\n  }\n});\n\nexport default useUser;\n```\n\n### Custom hooks\nCan't find your cases in this documentation examples? You can create custom hooks by yourself.\nHere is complex example you can refer the pattern to create another custom hooks cases.\n```js\n// file: stores/account.js\n...\nimport useStore from \"swr-global-state\";\n\nconst KEY = \"@app/account\";\n\nfunction useAccount() {\n  const [loading, setLoading] = useStore({\n    key: `${KEY}-loading`,\n    initial: true\n  });\n  const [account, setAccount, swrDefaultResponse] = useStore(\n    {\n      key: KEY,\n      initial: null,\n      persistor: {\n        onSet: (key, accountData) =\u003e {\n          window.localStorage.setItem(String(key), JSON.stringify(accountData));\n        },\n        onGet: async(key) =\u003e {\n          if (window.navigator.onLine) {\n            const remoteAccount = await fetch('/api/account');\n            return remoteAccount.json();\n          }\n          const cachedAccount = window.localStorage.getItem(String(key));\n          setLoading(false);\n          return JSON.parse(cachedAccount);\n        }\n      }\n    },\n    {\n      /**\n       * set another SWR config here\n       * @see https://swr.vercel.app/docs/options#options\n       * @default on `swr-global-state`:\n       * revalidateOnFocus: false\n       * revalidateOnReconnect: false\n       * refreshWhenHidden: false\n       * refreshWhenOffline: false\n       */\n      revalidateOnFocus: true,\n      revalidateOnReconnect: true\n    }\n  );\n\n  /**\n   * Destructuring response from SWR Default response\n   * @see https://swr.vercel.app/docs/options#return-values\n   */\n  const { mutate, error } = swrDefaultResponse;\n\n  const destroyAccount = async () =\u003e {\n    setLoading(true);\n    await fetch('/api/account/logout');\n    window.localStorage.removeItem(KEY);\n    // use default `mutate` from SWR to avoid `onSet` callback in `persistor`\n    mutate(null);\n    setLoading(false);\n  };\n\n  const updateAccount = async (newAccountData) =\u003e {\n    setLoading(true);\n    await fetch('/api/account', {\n      method: 'POST',\n      body: JSON.stringify(newAccountData)\n      ...\n    })\n    setAccount(newAccountData);\n    setLoading(false);\n  };\n\n  // your very custom mutator/dispatcher\n\n  return {\n    loading,\n    error,\n    account,\n    updateAccount,\n    destroyAccount\n  };\n}\n\nexport default useAccount;\n```\nThen, use that custom hooks in your component as usual.\n```jsx\n// file: App.js\n...\nimport useAccount from \"stores/account\";\n\nfunction App() {\n  const {\n    account,\n    updateAccount,\n    destroyAccount,\n    loading,\n    error\n  } = useAccount();\n\n  const onLogout = async () =\u003e {\n    await destroyAccount()\n    // your very logic\n  }\n\n  if (loading) {\n    return \u003cdiv\u003eLoading...\u003c/div\u003e;\n  }\n\n  if (error) {\n    return \u003cdiv\u003eAn Error occured\u003c/div\u003e;\n  }\n\n  return (\n    \u003cdiv\u003e\n      \u003cp\u003eAccount Detail: {JSON.stringify(account)}\u003c/p\u003e\n      \u003cbutton onClick={onLogout}\u003eLogout\u003c/button\u003e\n      {/* your very component to update account */}\n    \u003c/div\u003e\n  );\n}\n\nexport default App;\n```\n\n# Demo\nYou can see live demo [here](https://swr-global-state-demo.gading.dev/)\n\n# FAQ\n## Why should I use this?\n- If you want to manage your global state like `useState` as usual.\n- If you want to manage your global state without involving in setting up Provider Component, Dispatcher, Reducer, etc.\n- If you want to see `Redux` or `Context API` alternative.\n- If you're already use `SWR`, but you have no idea how to manage synchronous global state with `SWR` on client-side.\n- If you're still use `Redux` or `Context API`, but you are overwhelmed with their flow.\n\n## If this library can cover `Redux`, how about asynchronous state management like `redux-saga`, `redux-thunk`, or `redux-promise`?\n[SWR](https://swr.vercel.app) can cover this. [see](https://github.com/vercel/swr/discussions/587).\n\nAt this point, `swr-global-state` is based and depends on [SWR](https://www.npmjs.com/package/swr). After version `\u003e2` or later, `swr-global-state` now can handle *async state* too. Just wraps your *very async state logic* into a function like in [Custom Hooks](#custom-hooks) or [Asynchronous Persistor](#asynchronous-persistor).\n\nSo, you basically don't need to use `Redux` or `Context API` anymore. Alternatively, you can choose [TanStack Query](https://tanstack.com/query) or default [SWR](https://swr.vercel.app) itself.\n\n## React Native\nSince [SWR itself supports React Native](https://swr.vercel.app/docs/advanced/react-native), of course `swr-global-state` supports it too. [This example](#asynchronous-persistor) is using `Async Storage` in React Native.\n\n***Things to note***, you must install `swr-global-state` version `\u003e2` or later, because it has customizable `persistor`. So, you can customize the `persistor` with `React Native Async Storage`.\n\nUnder version `\u003c2`, `swr-global-state` still use `localStorage` and we can't customize it. So, it doesn't support React Native.\n\n# Publishing\n- Before pushing your changes to Github, make sure that `version` in `package.json` is changed to newest version. Then run `npm install` for synchronize it to `package-lock.json`\n- After your changes have been merged on branch `main`, you can publish the packages by creating new Relase here: https://github.com/gadingnst/swr-global-state/releases/new\n- Create new `tag`, make sure the `tag` name is same as the `version` in `package.json`.\n- You can write Release title and notes here. Or you can use auto-generated release title and notes.\n- Click `Publish Release` button, then wait the package to be published.\n\n# License\n`swr-global-state` is freely distributable under the terms of the [MIT license](https://github.com/gadingnst/swr-global-state/blob/master/LICENSE).\n\n# Feedbacks and Issues\nFeel free to open issues if you found any feedback or issues on `swr-global-state`. And feel free if you want to contribute too! 😄\n\n# Support\n## Global\n[![ko-fi](https://www.ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/gadingnst)\n## Indonesia\n- [Trakteer](https://trakteer.id/gadingnst)\n- [Karyakarsa](https://karyakarsa.com/gadingnst)\n\n---\n\nBuilt with ❤️ by [Sutan Gading Fadhillah Nasution](https://github.com/gadingnst) on 2022\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgadingnst%2Fswr-global-state","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgadingnst%2Fswr-global-state","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgadingnst%2Fswr-global-state/lists"}