{"id":23937221,"url":"https://github.com/vadikoom/react-singleton-hook","last_synced_at":"2026-03-14T05:03:37.715Z","repository":{"id":38007180,"uuid":"247556915","full_name":"vadikoom/react-singleton-hook","owner":"vadikoom","description":"Create singleton hook from regular react hook","archived":false,"fork":false,"pushed_at":"2023-03-06T02:43:46.000Z","size":1900,"stargazers_count":237,"open_issues_count":20,"forks_count":13,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-04-09T23:17:38.204Z","etag":null,"topics":["hooks-api-react","react","state-management"],"latest_commit_sha":null,"homepage":null,"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/vadikoom.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2020-03-15T21:43:55.000Z","updated_at":"2025-03-04T09:41:40.000Z","dependencies_parsed_at":"2024-01-13T19:25:16.555Z","dependency_job_id":"8020f0c5-eb03-4391-9f80-72b9b0dd4484","html_url":"https://github.com/vadikoom/react-singleton-hook","commit_stats":{"total_commits":61,"total_committers":6,"mean_commits":"10.166666666666666","dds":0.3114754098360656,"last_synced_commit":"ede56d01ffac1991095c3343781edb09a88061d5"},"previous_names":["vadikoom/react-singleton-hook"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vadikoom%2Freact-singleton-hook","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vadikoom%2Freact-singleton-hook/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vadikoom%2Freact-singleton-hook/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vadikoom%2Freact-singleton-hook/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/vadikoom","download_url":"https://codeload.github.com/vadikoom/react-singleton-hook/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248125593,"owners_count":21051771,"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":["hooks-api-react","react","state-management"],"created_at":"2025-01-06T02:01:18.375Z","updated_at":"2025-10-07T06:29:09.341Z","avatar_url":"https://github.com/vadikoom.png","language":"JavaScript","readme":"React Singleton Hook\n==========================\n\nManage global state of your React app using hooks. \n\n[![build status](https://img.shields.io/circleci/build/github/Light-Keeper/react-singleton-hook?logo=circleci)](https://app.circleci.com/pipelines/github/Light-Keeper/react-singleton-hook) \n[![npm version](https://img.shields.io/npm/v/react-singleton-hook)](https://www.npmjs.com/package/react-singleton-hook)\n[![npm downloads](https://img.shields.io/npm/dm/react-singleton-hook.svg)](https://www.npmjs.com/package/react-singleton-hook)\n![GitHub last commit](https://img.shields.io/github/last-commit/Light-Keeper/react-singleton-hook)\n\n## Installation\n\nTo use React Singleton Hook with your React app, install it as a dependency:\n\n```bash\n# If you use npm:\nnpm install react-singleton-hook\n\n# Or if you use Yarn:\nyarn add react-singleton-hook\n```\n\nThis assumes that you’re using [npm](http://npmjs.com/) package manager \nwith a module bundler like [Webpack](https://webpack.js.org/) or \n[Browserify](http://browserify.org/) to consume [CommonJS \nmodules](https://webpack.js.org/api/module-methods/#commonjs).\n\n### React 18 breaking change\n`react-singleton-hook` version 4.0.0 starts using new React DOM API and is only compatible with React 18. \nPlease use 3.4.0 if you have to stay on lower React versions.\n\n## What is a singleton hook\n- Singleton hooks very similar to React Context in terms of functionality. Each singleton hook has a body,\nyou might think of it as of Context Provider body. Hook has a return value, it's similar to the value provided by context.\nUsing a singleton hook from a component is like consuming a context.\n\n- Singleton hooks are lazy. The body is not executed until the hook is called by some component or other hook. \nOnce loaded, the hook body remains loaded forever.\nIf you want to eager-load some Singleton hooks, use them at the top-level component of your App.\n\n- Singleton hooks do not require a provider or a special App structure. \nUnder the hood, it uses useState/useRef/useEffect and some less-known react features for performance and portability.\n\n- It's possible to mix into single app Singleton hooks, React-Redux hooks api, React Context hooks and any custom hook. \n\n## Examples\n#### convert any custom hook into singleton hook\n\nIn the code below, the user profile is not fetched until `useUserProfile` used by some component, \nand once fetched it is never reloaded again, the hook remains mounted forever into hidden component. \n\n```javascript\nimport  { useEffect, useState } from 'react';\nimport { singletonHook } from 'react-singleton-hook';\nconst api = { async getMe() { return { name: 'test' }; } };\n\nconst init = { loading: true };\n\nconst useUserProfileImpl = () =\u003e {\n  const [profile, setProfile] = useState(init);\n  useEffect(() =\u003e {\n    api.getMe()\n      .then(profile =\u003e setProfile({ profile }))\n      .catch(error =\u003e setProfile({ error }));\n  }, []);\n\n  return profile;\n};\n\nexport const useUserProfile = singletonHook(init, useUserProfileImpl);\n```\n\n#### dark/light mode switch \nWhenever `Configurator` changes darkMode, all subscribed components are updated.\n\n```javascript\n/***************    file:src/services/darkMode.js    ***************/  \nimport { useState } from 'react';\nimport { singletonHook } from 'react-singleton-hook';\n\nconst initDarkMode = false;\nlet globalSetMode = () =\u003e { throw new Error('you must useDarkMode before setting its state'); };\n\nexport const useDarkMode = singletonHook(initDarkMode, () =\u003e {\n  const [mode, setMode] = useState(initDarkMode);\n  globalSetMode = setMode;\n  return mode;\n});\n\nexport const setDarkMode = mode =\u003e globalSetMode(mode);\n\n\n/***************    file:src/compoents/App.js    ***************/\n\nimport  React from 'react';\nimport { useDarkMode, setDarkMode } from 'src/services/darkMode';\n\nconst Consumer1 = () =\u003e {\n  const mode = useDarkMode();\n  return \u003cdiv className={`is-dark-${mode}`}\u003eConsumer1 - {`${mode}`}\u003c/div\u003e;\n};\n\nconst Consumer2 = () =\u003e {\n  const mode = useDarkMode();\n  return \u003cdiv className={`is-dark-${mode}`}\u003eConsumer2 - {`${mode}`}\u003c/div\u003e;\n};\n\nconst Configurator = () =\u003e {\n  const mode = useDarkMode();\n  return \u003cbutton onClick={() =\u003e setDarkMode(!mode)}\u003eToggle dark/light\u003c/button\u003e;\n};\n\n```\n\n#### imperatively read hook state for non-react code\n```javascript\nimport { useState } from 'react';\nimport { singletonHook } from 'react-singleton-hook';\n\nconst initDarkMode = false;\nlet currentMode = initDarkMode;\nlet globalSetMode = () =\u003e { throw new Error(`you must useDarkMode before setting its state`); };\n\nexport const useDarkMode = singletonHook(initDarkMode, () =\u003e {\n  const [mode, setMode] = useState(initDarkMode);\n  globalSetMode = setMode;\n  currentMode = mode;\n  return mode;\n});\n\nexport const setDarkMode = mode =\u003e globalSetMode(mode);\nexport const getDarkMode = () =\u003e currentMode;\n```\n\n#### use react-redux (or any other context) inside singletonHook\nTo use react-redux or any other context-based functionality, singleton hooks should be mounted under provider in your app.\nTo do that, import `SingletonHooksContainer` from `react-singleton-hook` and mount anywhere in you app. \n**SingletonHooksContainer must be rendered ealier then any component using singleton hook!**\nBy default you are not required to deal with a `SingletonHooksContainer`, we run this component internally in separate react app.\n\n\n```javascript\n/***************    file:src/services/currentUser.js    ***************/\nimport { singletonHook } from 'react-singleton-hook';\nimport { useSelector } from 'react-redux';\n\nconst init = { loading: true };\nconst useCurrentUserImpl = () =\u003e {\n  const session = useSelector(state =\u003e state.session);\n  if (session.loading) return init;\n  return session.user;\n};\n\nexport const useCurrentUser = singletonHook(init, useCurrentUserImpl);\n\n/***************    file:src/App.js    ***************/\n\nimport React from 'react';\nimport ReactDOM from 'react-dom';\nimport { SingletonHooksContainer } from 'react-singleton-hook';\nimport { Provider } from 'react-redux';\nimport store from 'src/store';\nimport App from 'src/views';\n\nconst app = (\n  \u003cProvider store={store}\u003e\n    \u003c\u003e\n      \u003cSingletonHooksContainer/\u003e\n      \u003cApp/\u003e\n    \u003c/\u003e\n  \u003c/Provider\u003e\n);\n\nReactDOM.render(app, document.getElementById('root'));\n```\n\n#### top-level components updated before low-level components\n\n```javascript\n/***************    file:src/services/session.js    ***************/\n\nimport { useEffect, useState } from 'react';\nimport { singletonHook } from 'react-singleton-hook';\n\nconst initState = { loading: true };\nlet setSessionGlobal = () =\u003e { throw new Error('you must useSession before login'); };\n\nconst useSessionImpl = () =\u003e {\n  const [session, setSession] = useState(initState);\n  setSessionGlobal = setSession;\n  useEffect(() =\u003e { setSession({ loggedIn: false }); }, []);\n  return session;\n};\n\nexport const useSession = singletonHook(initState, useSessionImpl);\n\nexport const login = (name, pass) =\u003e {\n  setSessionGlobal({ loggedIn: true, user: { name: 'test' } });\n};\n\n/***************    file:src/index.js    ***************/\nimport React, { useEffect } from 'react';\nimport ReactDOM from 'react-dom';\nimport { login, useSession } from 'src/services/session';\n\nconst LoggedInView = () =\u003e {\n  const session = useSession();\n  console.log(`LoggerInView rendered with ${JSON.stringify(session)}`);\n  return null;\n};\n\nconst LoggedOutView = () =\u003e {\n  const session = useSession();\n  console.log(`LoggedOutView rendered with ${JSON.stringify(session)}`);\n  return null;\n};\n\nconst WaitingForSessionView = () =\u003e {\n  const session = useSession();\n  console.log(`WaitingForSessionView rendered with ${JSON.stringify(session)}`);\n  return null;\n};\n\nconst MainComponent = () =\u003e {\n  const session = useSession();\n\n  useEffect(() =\u003e {\n    setTimeout(() =\u003e { login('testuser'); }, 2000);\n  }, []);\n\n  console.log(`MainComponent rendered with ${JSON.stringify(session)}`);\n\n  if (session.loading) return \u003cWaitingForSessionView/\u003e;\n  if (session.loggedIn) return \u003cLoggedInView/\u003e;\n  return \u003cLoggedOutView/\u003e;\n};\n\n\nReactDOM.render(\u003cMainComponent/\u003e, document.getElementById('root'));\n\n/***************    console.log    ***************/\n/*\n\nMainComponent rendered with {\"loading\":true}\nWaitingForSessionView rendered with {\"loading\":true}\nMainComponent rendered with {\"loggedIn\":false}\nLoggedOutView rendered with {\"loggedIn\":false}\nMainComponent rendered with {\"loggedIn\":true,\"user\":{\"name\":\"test\"}}\nLoggerInView rendered with {\"loggedIn\":true,\"user\":{\"name\":\"test\"}}\n\n*/\n```\n\n### Initial state callback\n\nAs of version 3.0, `singletonHook` accepts a callback that calculates initial state instead of predefined initial state. \nThis callback is called once and only when the value is required. \nYou can use it to calculate expensive initial values or\navoid an extra render (and a state flickering)\nwhen initial state changes before any component consumes the hook: \n \n#### example: subscribe components to pre-existing get/set data module\n \n ```javascript\n/***************    file:src/services/darkMode.js    ***************/\n\nimport { useState } from 'react';\nimport { singletonHook } from 'react-singleton-hook';\n\nlet isDarkMode = false; // the state of the module\nlet updateSubscribers = (mode) =\u003e {}; //update subscribers callback - do nothing by default\n\n// pre-existing functions to manipulate the state\nexport const getDarkMode = () =\u003e isDarkMode;\n\nexport const setDarkMode = (newMode) =\u003e {\n  isDarkMode = newMode;\n  updateSubscribers(isDarkMode); // call updateSubscribers when setting new state\n};\n\n// new function - custom hook for components to subscribe.\n// using getDarkMode as an init callback to get most relevant state\nexport const useDarkMode = singletonHook(getDarkMode, () =\u003e {\n  const [mode, setMode] = useState(getDarkMode);\n  updateSubscribers = setMode; // subscribing for further updates\n  return mode;\n});\n\n/***************    file:src/index.js    ***************/\n\n// you can call setter and getter any time\nsetDarkMode(true);\nsetInterval(() =\u003e setDarkMode(!getDarkMode()), 2000);\n\nconst App = () =\u003e {\n  // component will be updated on darkMode change \n  // on first render \"mode\" is set to the current value getDarkMode returns\n  const mode = useDarkMode();\n  return \u003cdiv className={`is-dark-${mode}`}\u003eApp - {`${mode}`}\u003c/div\u003e;\n};\n\n```     \n\n### Unmounting hooks when there are no consumers\n\nYou can pass the last optional parameter `options` to the `singletonHook` to \nconfigure if the hook should be unmounted when no one consumes it:\n```js\n\nconst useHook = singletonHook(\n  initVal,\n  () =\u003e { /*hook body*/ }, \n  { unmountIfNoConsumers: true }\n);\n```\n\n## React Native\nTo use this library with react-native you always have to mount `SingletonHooksContainer` manually. \nSee how to do it in example: *use react-redux (or any other context) inside singletonHook*\n\n## Server Side Rendering\nSingleton hooks are ignored during SSR and always return initial value.  \n","funding_links":[],"categories":["JavaScript"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvadikoom%2Freact-singleton-hook","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvadikoom%2Freact-singleton-hook","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvadikoom%2Freact-singleton-hook/lists"}