{"id":20452828,"url":"https://github.com/axtk/react-keenstore","last_synced_at":"2026-02-11T01:03:16.579Z","repository":{"id":187907008,"uuid":"677794370","full_name":"axtk/react-keenstore","owner":"axtk","description":"SSR-compatible React store for straightforward shared state management","archived":false,"fork":false,"pushed_at":"2024-05-21T05:18:18.000Z","size":170,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-13T09:45:06.359Z","etag":null,"topics":["react","react-state","react-store","shared-state","state-management"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/react-keenstore","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/axtk.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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":"2023-08-12T16:49:15.000Z","updated_at":"2024-05-21T05:18:22.000Z","dependencies_parsed_at":"2023-08-12T17:39:22.589Z","dependency_job_id":"a05d8ea4-6892-4519-8613-989be6560593","html_url":"https://github.com/axtk/react-keenstore","commit_stats":null,"previous_names":["axtk/react-keenstore"],"tags_count":31,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/axtk%2Freact-keenstore","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/axtk%2Freact-keenstore/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/axtk%2Freact-keenstore/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/axtk%2Freact-keenstore/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/axtk","download_url":"https://codeload.github.com/axtk/react-keenstore/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244717361,"owners_count":20498283,"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":["react","react-state","react-store","shared-state","state-management"],"created_at":"2024-11-15T11:10:30.974Z","updated_at":"2025-03-21T00:42:07.480Z","avatar_url":"https://github.com/axtk.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![npm](https://img.shields.io/npm/v/react-keenstore?labelColor=royalblue\u0026color=royalblue\u0026style=flat-square)](https://www.npmjs.com/package/react-keenstore) [![GitHub](https://img.shields.io/badge/-GitHub-royalblue?labelColor=royalblue\u0026color=royalblue\u0026style=flat-square\u0026logo=github)](https://github.com/axtk/react-keenstore) ![React](https://img.shields.io/badge/%23-React-345?labelColor=345\u0026color=345\u0026style=flat-square) [![SSR](https://img.shields.io/badge/%23-SSR-345?labelColor=345\u0026color=345\u0026style=flat-square)](#server-side-rendering-ssr) ![TypeScript](https://img.shields.io/badge/%23-TypeScript-345?labelColor=345\u0026color=345\u0026style=flat-square)\n\n# react-keenstore\n\n*SSR-compatible React store for straightforward shared state management*\n\nDealing with shared state similarly to React's `useState()`.\n\n## Installation\n\n```\nnpm i react-keenstore\n```\n\n## Usage example\n\nThis package exports the `Store` class and the `useStore()` React hook that are sufficient to set up shared state without bulky boilerplate.\n\nIn the example below, the data shared across components by means of React Context is wrapped into an instance of the `Store` class. It allows to make data updates occurring in one component (`\u003cPlusButton/\u003e`) immediately visible to other components subscribed to the store via the `useStore()` hook (`\u003cDisplay/\u003e`).\n\nWhile a plain object as a Context value requires devising additional [value setters](https://react.dev/reference/react/useContext#updating-an-object-via-context) to update the shared data, the store exposes a method to update the data out of the box and triggers a re-render only in the components subscribed to this store.\n\n```jsx\nimport {createContext, useContext} from 'react';\nimport {Store, useStore} from 'react-keenstore';\n\nconst AppContext = createContext(new Store({counter: 0}));\n\nconst Display = () =\u003e {\n    let store = useContext(AppContext);\n    let [state] = useStore(store);\n\n    return \u003cspan\u003e{state.counter}\u003c/span\u003e;\n};\n\nconst PlusButton = () =\u003e {\n    let store = useContext(AppContext);\n    // We're not using the store state value here, so the subscription\n    // to its updates is not required, hence the `false` parameter.\n    let [, setState] = useStore(store, false);\n\n    let handleClick = () =\u003e {\n        // Updating the store state via `setState()` triggers updates\n        // in all components subscribed to this store.\n        setState(prevState =\u003e ({\n            counter: prevState.counter + 1,\n        }));\n    };\n\n    return \u003cbutton onClick={handleClick}\u003e+\u003c/button\u003e;\n};\n\nexport const App = () =\u003e \u003cdiv\u003e\u003cPlusButton/\u003e \u003cDisplay/\u003e\u003c/div\u003e;\n```\n\n```jsx\nimport {createRoot} from 'react-dom/client';\nimport {Store} from 'react-keenstore';\nimport {App} from './App';\n\ncreateRoot(document.querySelector('#app')).render(\n    \u003cAppContext.Provider value={new Store({counter: 42})}\u003e\n        \u003cApp/\u003e\n    \u003c/AppContext.Provider\u003e,\n);\n```\n\n([Live demo](https://codesandbox.io/s/react-keenstore-demo-npu6rb))\n\n## Multiple stores\n\nAn application can have as many stores as needed, whether on a single Context or multiple Contexts. Splitting the app data into multiple stores can make the scopes of the stores clearer and it can help reduce irrelevant update notifications in the components requiring only a limited portion of the data.\n\n```js\nconst AppContext = createContext({\n    users: new Store(/* ... */),\n    services: new Store(/* ... */),\n});\n\nconst UserInfo = ({userId}) =\u003e {\n    let [users, setUsers] = useStore(useContext(AppContext).users);\n\n    // ...\n};\n```\n\n## Painless transition from local state to shared state\n\nThe similarity of the interfaces of `useStore()` and `useState()` allows to easily switch from local state to shared state without major code rewrites when it becomes necessary to make the state available to multiple components:\n\n```diff\n+ const AppContext = createContext(new Store({counter: 0}));\n\nconst CounterButton = () =\u003e {\n    // Local state:\n    // `state` is only available in the current component\n-   const [state, setState] = useState({counter: 0});\n\n    // Shared state:\n    // `state` is available inside and outside of the component\n+   const [state, setState] = useStore(useContext(AppContext));\n\n    const handleClick = () =\u003e {\n        setState(prevState =\u003e ({\n            counter: prevState.counter + 1\n        }));\n    };\n\n    return \u003cbutton onClick={handleClick}\u003e{state.counter}\u003c/button\u003e;\n};\n```\n\nAs seen from this example, we only have to switch the source of the state to a Context, with the rest of the code (reading and updating the state) remaining the same.\n\n## Direct subscription to store updates\n\nFor some purposes (like logging or debugging the data flow), it might be helpful to directly subscribe to state updates via the store's `onUpdate()` method:\n\n```js\nconst App = () =\u003e {\n    let store = useContext(AppContext);\n\n    useEffect(() =\u003e {\n        // `onUpdate()` returns an unsubscription function which\n        // works as a cleanup function in the effect.\n        return store.onUpdate((nextState, prevState) =\u003e {\n            console.log({nextState, prevState});\n        });\n    }, [store]);\n\n    // ...\n};\n```\n\n## Persistent local state\n\nMaintaining local state of a component with the React's `useState()` hook is commonplace and works fine for many cases, but it has its downsides in the popular scenarios:\n\n- the updated state from `useState()` is lost whenever the component unmounts, and\n- setting the state in an asynchronous callback after the component gets unmounted causes an error that requires extra handling.\n\nBoth of these issues can be addressed by using a store created outside of the component instead of `useState()`. Such a store doesn't have to be shared with other components (although it's also possible) and it will act as:\n\n- local state persistent across remounts, and\n- unmount-safe storage for asynchronously fetched data.\n\n```diff\n+ const itemStore = new Store();\n\nconst List = () =\u003e {\n-   const [items, setItems] = useState();\n+   const [items, setItems] = useStore(itemStore);\n\n    useEffect(() =\u003e {\n        if (items !== undefined)\n            return;\n\n        fetch('/items')\n            .then(res =\u003e res.json())\n            .then(items =\u003e setItems(items));\n    }, [items]);\n\n    // Rendering\n};\n```\n\nIn the example above, if the request completes after the component has unmounted the fetched data will be safely put into `itemStore` and this data will be reused when the component remounts without fetching it again.\n\n### Connecting a store to external storage\n\n`itemStore` from the example above can be further upgraded to make the component state persistent across page reloads without affecting the component's internals.\n\n```js\nlet initialState;\n\ntry {\n    initialState = JSON.parse(localStorage.getItem('list'));\n}\ncatch {}\n\nexport const itemStore = new Store(initialState);\n\nitemStore.onUpdate(nextState =\u003e {\n    localStorage.setItem('list', JSON.stringify(nextState));\n});\n```\n\n```js\nimport {itemStore} from './itemStore';\n\nexport const List = () =\u003e {\n    let [items, setItems] = useStore(itemStore);\n\n    // ...\n};\n```\n\n## Adding *immer*\n\n*immer* is not part of this package, but it can be used with `useStore()` just the same way as [with `useState()`](https://immerjs.github.io/immer/example-setstate#usestate--immer) to facilitate deeply nested data changes. (See [live demo](https://codesandbox.io/s/react-keenstore-demo-with-immer-q9jykm?file=/src/PlusButton.jsx).)\n\n## See also\n\n- [*keenstore*](https://github.com/axtk/keenstore), the `Store` class without the React hook\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faxtk%2Freact-keenstore","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Faxtk%2Freact-keenstore","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faxtk%2Freact-keenstore/lists"}