{"id":23779291,"url":"https://github.com/ryym/react-hook-shared","last_synced_at":"2026-04-16T03:32:46.487Z","repository":{"id":66203975,"uuid":"187439068","full_name":"ryym/react-hook-shared","owner":"ryym","description":"Let custom hooks share states and effects","archived":false,"fork":false,"pushed_at":"2019-06-07T04:44:50.000Z","size":95,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2026-03-12T03:42:11.631Z","etag":null,"topics":["react","react-hook","state-management"],"latest_commit_sha":null,"homepage":"","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/ryym.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":"2019-05-19T05:49:40.000Z","updated_at":"2019-06-07T04:44:52.000Z","dependencies_parsed_at":null,"dependency_job_id":"fb6a3288-7b71-4243-8ac9-c541cac9e6c6","html_url":"https://github.com/ryym/react-hook-shared","commit_stats":{"total_commits":31,"total_committers":1,"mean_commits":31.0,"dds":0.0,"last_synced_commit":"531883bafb8b8261a27c78680c68c499eeafba20"},"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/ryym/react-hook-shared","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ryym%2Freact-hook-shared","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ryym%2Freact-hook-shared/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ryym%2Freact-hook-shared/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ryym%2Freact-hook-shared/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ryym","download_url":"https://codeload.github.com/ryym/react-hook-shared/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ryym%2Freact-hook-shared/sbom","scorecard":{"id":793282,"data":{"date":"2025-08-11","repo":{"name":"github.com/ryym/react-hook-shared","commit":"531883bafb8b8261a27c78680c68c499eeafba20"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":1.7,"checks":[{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Code-Review","score":0,"reason":"Found 0/30 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"SAST","score":0,"reason":"no SAST tool detected","details":["Warn: no pull requests merged into dev branch"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Vulnerabilities","score":0,"reason":"97 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-968p-4wvh-cqc8","Warn: Project is vulnerable to: GHSA-67hx-6x53-jw92","Warn: Project is vulnerable to: GHSA-6chw-6frg-f759","Warn: Project is vulnerable to: GHSA-v88g-cgmw-v5xw","Warn: Project is vulnerable to: GHSA-93q8-gq69-wqmw","Warn: Project is vulnerable to: GHSA-v6h2-p8h4-qcjw","Warn: Project is vulnerable to: GHSA-grv7-fg5c-xmjg","Warn: Project is vulnerable to: GHSA-x9w5-v3q2-3rhw","Warn: Project is vulnerable to: GHSA-w8qv-6jwh-64r5","Warn: Project is vulnerable to: GHSA-257v-vj4p-3w2h","Warn: Project is vulnerable to: GHSA-3xgq-45jj-v275","Warn: Project is vulnerable to: GHSA-gxpj-cx7g-858c","Warn: Project is vulnerable to: GHSA-w573-4hg7-7wgq","Warn: Project is vulnerable to: GHSA-ff7x-qrg7-qggm","Warn: Project is vulnerable to: GHSA-vh7m-p724-62c2","Warn: Project is vulnerable to: GHSA-r9p9-mrjm-926w","Warn: Project is vulnerable to: GHSA-434g-2637-qmqr","Warn: Project is vulnerable to: GHSA-49q7-c7j4-3p7m","Warn: Project is vulnerable to: GHSA-977x-g7h5-7qgw","Warn: Project is vulnerable to: GHSA-f7q4-pwc6-w24p","Warn: Project is vulnerable to: GHSA-fc9h-whq2-v747","Warn: Project is vulnerable to: GHSA-vjh7-7g9h-fjfh","Warn: Project is vulnerable to: GHSA-fjxv-7rqg-78g4","Warn: Project is vulnerable to: GHSA-8r6j-v8pm-fqw3","Warn: Project is vulnerable to: MAL-2023-462","Warn: Project is vulnerable to: GHSA-w457-6q6x-cgp9","Warn: Project is vulnerable to: GHSA-62gr-4qp9-h98f","Warn: Project is vulnerable to: GHSA-f52g-6jhx-586p","Warn: Project is vulnerable to: GHSA-2cf5-4w76-r9qv","Warn: Project is vulnerable to: GHSA-3cqr-58rm-57f8","Warn: Project is vulnerable to: GHSA-g9r4-xpmj-mj65","Warn: Project is vulnerable to: GHSA-q2c6-c6pm-g3gh","Warn: Project is vulnerable to: GHSA-765h-qjxv-5f44","Warn: Project is vulnerable to: GHSA-f2jv-r9rf-7988","Warn: Project is vulnerable to: GHSA-43f8-2h32-f4cj","Warn: Project is vulnerable to: GHSA-qqgx-2p2h-9c37","Warn: Project is vulnerable to: GHSA-7r28-3m3f-r2pr","Warn: Project is vulnerable to: GHSA-r8j5-h5cx-65gg","Warn: Project is vulnerable to: GHSA-896r-f27r-55mw","Warn: Project is vulnerable to: GHSA-9c47-m6qq-7p4h","Warn: Project is vulnerable to: GHSA-6c8f-qphg-qjgp","Warn: Project is vulnerable to: GHSA-jf85-cpcp-j695","Warn: Project is vulnerable to: GHSA-p6mc-m468-83gw","Warn: Project is vulnerable to: GHSA-29mw-wpgm-hmr9","Warn: Project is vulnerable to: GHSA-35jh-r3h4-6jhm","Warn: Project is vulnerable to: GHSA-952p-6rrq-rcjv","Warn: Project is vulnerable to: GHSA-f8q6-p94x-37v3","Warn: Project is vulnerable to: GHSA-vh95-rmgr-6w4m","Warn: Project is vulnerable to: GHSA-xvch-5gv4-984h","Warn: Project is vulnerable to: GHSA-fhjf-83wg-r2j9","Warn: Project is vulnerable to: GHSA-r683-j2x4-v87g","Warn: Project is vulnerable to: GHSA-92xj-mqp7-vmcj","Warn: Project is vulnerable to: GHSA-wxgw-qj99-44c2","Warn: Project is vulnerable to: GHSA-5rrq-pxf6-6jx5","Warn: Project is vulnerable to: GHSA-8fr3-hfg3-gpgp","Warn: Project is vulnerable to: GHSA-gf8q-jrpm-jvxq","Warn: Project is vulnerable to: GHSA-2r2c-g63r-vccr","Warn: Project is vulnerable to: GHSA-cfm4-qjh2-4765","Warn: Project is vulnerable to: GHSA-x4jg-mjrx-434g","Warn: Project is vulnerable to: GHSA-5fw9-fq32-wv5p","Warn: Project is vulnerable to: GHSA-rp65-9cf3-cjxr","Warn: Project is vulnerable to: GHSA-hj48-42vr-x3v9","Warn: Project is vulnerable to: GHSA-9wv6-86v2-598j","Warn: Project is vulnerable to: GHSA-h7cp-r72f-jxh6","Warn: Project is vulnerable to: GHSA-v62p-rq8g-8h59","Warn: Project is vulnerable to: GHSA-566m-qj78-rww5","Warn: Project is vulnerable to: GHSA-7fh5-64p2-3v2j","Warn: Project is vulnerable to: GHSA-hwj9-h5mp-3pm3","Warn: Project is vulnerable to: GHSA-hrpp-h998-j3pp","Warn: Project is vulnerable to: GHSA-p8p7-x288-28g6","Warn: Project is vulnerable to: GHSA-r3x4-wr4h-pw33","Warn: Project is vulnerable to: GHSA-876r-hj45-fw7g","Warn: Project is vulnerable to: GHSA-v63x-xc9j-hhvq","Warn: Project is vulnerable to: GHSA-c2qf-rxjj-qqgw","Warn: Project is vulnerable to: GHSA-m6fv-jmcg-4jfg","Warn: Project is vulnerable to: GHSA-w5q7-3pr9-x44w","Warn: Project is vulnerable to: GHSA-3fjq-93xj-3f3f","Warn: Project is vulnerable to: GHSA-cm22-4g7w-348p","Warn: Project is vulnerable to: GHSA-4g88-fppr-53pp","Warn: Project is vulnerable to: GHSA-4jqc-8m5r-9rpr","Warn: Project is vulnerable to: GHSA-3jfq-g458-7qm9","Warn: Project is vulnerable to: GHSA-r628-mhmh-qjhw","Warn: Project is vulnerable to: GHSA-9r2w-394v-53qc","Warn: Project is vulnerable to: GHSA-5955-9wpr-37jh","Warn: Project is vulnerable to: GHSA-qq89-hq3f-393p","Warn: Project is vulnerable to: GHSA-f5x3-32g6-xq36","Warn: Project is vulnerable to: GHSA-4wf5-vphf-c2xc","Warn: Project is vulnerable to: GHSA-jgrx-mgxx-jf9v","Warn: Project is vulnerable to: GHSA-72xf-g2v4-qvf3","Warn: Project is vulnerable to: GHSA-662x-fhqg-9p8v","Warn: Project is vulnerable to: GHSA-394c-5j6w-4xmx","Warn: Project is vulnerable to: GHSA-78cj-fxph-m83p","Warn: Project is vulnerable to: GHSA-fhg7-m89q-25r3","Warn: Project is vulnerable to: GHSA-6fc8-4gx4-v693","Warn: Project is vulnerable to: GHSA-3h5v-q93c-6h6q","Warn: Project is vulnerable to: GHSA-c4w7-xm78-47vh","Warn: Project is vulnerable to: GHSA-p9pc-299p-vxgp"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-23T08:11:40.727Z","repository_id":66203975,"created_at":"2025-08-23T08:11:40.728Z","updated_at":"2025-08-23T08:11:40.728Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31870508,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-15T15:24:51.572Z","status":"online","status_checked_at":"2026-04-16T02:00:06.042Z","response_time":69,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["react","react-hook","state-management"],"created_at":"2025-01-01T10:16:27.083Z","updated_at":"2026-04-16T03:32:46.467Z","avatar_url":"https://github.com/ryym.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# React Hook Shared\n\nShare your states and effects among multiple components with ease.\n\n## First of all\n\nI created this library as an experiment, but concluded that this is not a practical idea.\nThough this library provides somewhat useful features, it has several pitfalls and downsides (and probably bugs too. I don’t test much because of the conclusion).\n\n## What it looks like\n\n```javascript\nimport React from 'react';\nimport { SharedProvider, useShared } from 'react-hook-shared';\n\nconst App = () =\u003e (\n  \u003cSharedProvider\u003e\n    \u003cContent /\u003e\n    \u003cThemeEdit /\u003e\n  \u003c/SharedProvider\u003e\n);\n\nconst Content = ({ children }) =\u003e {\n  const { theme } = useTheme();\n  return \u003cTheme with={theme}\u003e{children}\u003c/Theme\u003e;\n}\n\nconst ThemeEdit = () =\u003e {\n  const { theme, changeTheme } = useTheme();\n  return (/* edit form using changeTheme */);\n}\n\nconst spaceId = Symbol();\n\n// Custom hook that uses shared state and effect.\n// States and effects are initialized only once even if multiple components use this hook.\nconst useTheme = () =\u003e {\n  const shared = useShared(spaceId);\n\n  const [theme, setTheme] = shared.useState(lightTheme);\n  const [themeName, changeTheme]  = shared.useState('light');\n\n  shared.useEffect(() =\u003e {\n    api.fetchTheme(themeName).then(setTheme);\n  }, [themeName]);\n\n  return { themse, changeTheme };\n};\n```\n\n## Motivation\n\nInspired by [Application State Management with React][app-state-management-with-react].\n\nThis blog post shows a way of state management without any Flux libraries, just using React Context.\nI enjoyed this blog and thought that it would be useful if we can do the same thing\n— sharing states and effects with multiple components in a UI tree — without Context Providers.  \nA necessity of wrapping certain part by a Provider component could be a maintenance cost.\nWe need to determine where is the smallest root to wrap all components that use the state provided by a Provider every time when a component need the state (otherwise need to wrap the whole tree by a Provider).\nAlso, JavaScript/TypeScript could not warn you if you forgot to wrap it by a Provider.\n\nSo I created this library to share custom hooks among with components that are in arbitrary position of a UI tree, without Provider.\n\n[app-state-management-with-react]: https://kentcdodds.com/blog/application-state-management-with-react\n\n## Usability\n\n- Easy to introduce\n- No Flux\n- No HOC\n- Maybe Code Splitting friendly?\n\nHowever, using this library would encourage you to compose your business logic by custom hooks.\nThis have several disadvantages:\n\n- Less testable - You need to render a component to test your logic, because React Hook cannot run outside of a component lifecycle.\n- Less portable - Your logic will be tightly coupled with React Hook. \n\nEssentially React Hook itself is tightly coupled with components, of course.\nThis is fine and useful but this is not suited for complicated domain logic that should be independent from views and be tested carefully.\n\n## Usage\n\n1. Wrap your component tree by `SharedProvider`.\n2. Use `useShared` hook to obtain shared API.\n\n```javascript\n// You need to pass an id to useShared. This id must be unique per custom hook so Symbol is suited.\nconst spaceId = Symbol();\nconst shared = useShared(spaceId);\n```\n\nThe functions obtained from `useShared` share the _same space_.\nThe space will be initialized when the first component accesses it,\nand cleaned up after the last component is unmounted.\n\n## API\n\n### useState\n\n```javascript\nconst useSharedCount = () =\u003e {\n  const shared = useShared(spaceId);\n  const [count, setCount] = shared.useState(0);\n  const increment = () =\u003e setCount(count + 1);\n  return [count, increment];\n};\n```\n\nMultiple components can share the state.\nFor example, when component A and B use this shared state,\n\n- The state is initialized with the value provided by a first-rendered component.\n  - If A is rendered first, an initial state set by B is ignored, and the current value is returned when B calls `useState(0)` first.\n- If any component changes the state by `setCount`, all components using this hook will be re-rendered.\n- **Pitfall**: You cannot skip rendering only for some components. Always all components are re-rendered when a shared state changed. If you want to share a state but each component uses a part of the state, use `useReducer` and state mapping to avoid unnecessary re-rendering.\n\n### useReducer\n\n```javascript\nconst initState = () =\u003e ({\n  articles: [],\n  category: null,\n});\n\nconst useArticles = mapState =\u003e {\n  const shared = useShared(spaceId);\n  const [state, mappedState, dispatch] = shared.useReducer(reducer, { initState, mapState });\n\n  shared.useEffect(() =\u003e {\n    api.fetchArticles(state.category).then(articles =\u003e {\n        dispatch({ type: 'FETCH_ARTICLES_OK', articles });\n    });\n  }, [state.category]);\n\n  const actions = {\n    changeCategory: category =\u003e dispatch({ type: 'CHANGE_CATEGORY', category }),\n  };\n  return [mappedState, actions];\n};\n\n```\n\nMultiple components can share the state while each component can use some part of the state.\n\n```javascript\nconst ArticleIndex = () =\u003e {\n  const [articles] = useArticles(state =\u003e state.articles);\n  return \u003cArticleList articles={articles} /\u003e;\n};\n\n// This component will be re-rendered only when `state.category` is changed.\nconst Header = () =\u003e {\n  const [category, {changeCategory}] = useArticles(state =\u003e state.cateogry);\n  return (\n    \u003cArticleFilter\n      category={category}\n      onCategoryChange={changeCategory}\n    /\u003e\n  );\n}\n```\n\n- Like `useState`, the state is initialized with te value provided by a first-rendered component.\n- You need to pass two functions for `useReducer` as a second argument:\n  - `initState` - A function that returns an initial state. This is called only once.\n  - `mapState` - A function that maps a state for specific use. Each component can use different partial state by passing  `mapState`.\n- Each component is re-rendered only when its mapped state changes.\n- **Pitfall**: When a shared effect depends on a partial state, at least one component must map and use that state. This is because React Hook cannot run without rendering. For example, if no components use the `state.category` via `mapState` in the above sample code, the effect never runs as no rendering fired when `state.category` changes.\n\n### useEffect\n\n```javascript\nconst useSearch = () =\u003e {\n  const shared = useShared(spaceId);\n  const [result, setResult] = shared.useState(null);\n  const [query, setQuery] = shared.useState('');\n\n  shared.useEffect(() =\u003e {\n    return api.search(query).then(setResult);\n  }, [query]);\n\n  return { result, search: setQuery };\n}\n```\n\nMultiple components can share the effect.\n\n- The effect runs first when a first component uses this hook. If another component uses this hook but the dependencies (`[sharedState]`) does not change, the effect does not run.\n- You can specify the empty dependency (`[]`) to run the effect only once like React Hook’s `useEffect`.\n- **Pitfall**: Do not depends on local states (component’s state and props). It results in unexpected behavior. A shared effect can depend on shared states only. You can use `useEffectPer` to partially depends on a local state.\n\n### useEffectPer\n\n```javascript\nconst reducer = (state = {}, action) =\u003e ({\n  ...state,\n  [action.friendID]: action.isOnline,\n})\n\nconst useFriendStatus = (friendID) =\u003e {\n  const shared = useShared(spaceId);\n  const [_statues, isOnline, dispatch] = shared.useReducer(reducer, {\n    initState: () =\u003e ({}),\n    mapState: s =\u003e s[friendID],\n  });\n\n  shared.useEffectPer(friendID, () =\u003e {\n    const handleChange = status =\u003e dispatch({ friendID, ...status });\n    ChatAPI.subscribeFriendStatus(friendID, handleChange);\n    return () =\u003e {\n      ChatAPI.unsubscribeFriendStatus(friendID, handleChange)\n    };\n  });\n\n  return isOnline;\n}\n```\n\nMultiple component can share multiple effects.\nConsider [`useFriendStatus` example][official-custom-hook-example] in React Hook reference.\nIf multiple components use this `useFriendStatus`, it should be like below:\n\n- Each component can subscribe a different friend status simultaniously.\n- There is always one subscription for a same friend. No duplicate API calls.\n- A subscription for a friend is cancelled when all components are unmounted who subscribe the friend's status.\n\n`useEffectPer` achieves this. You can run different effect per component by providing a key (`friendID`). \n\n- **Pitfall**: Currently you cannot pass dependencies to this hook.\n\n[official-custom-hook-example]: https://reactjs.org/docs/hooks-custom.html\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fryym%2Freact-hook-shared","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fryym%2Freact-hook-shared","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fryym%2Freact-hook-shared/lists"}