{"id":20452843,"url":"https://github.com/axtk/react-store","last_synced_at":"2025-03-05T09:47:09.789Z","repository":{"id":57100095,"uuid":"336710124","full_name":"axtk/react-store","owner":"axtk","description":"Compact shared-state management in React","archived":false,"fork":false,"pushed_at":"2022-02-23T01:22:11.000Z","size":198,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-05T09:45:53.125Z","etag":null,"topics":["react","react-hooks","shared-state","store"],"latest_commit_sha":null,"homepage":"","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}},"created_at":"2021-02-07T05:38:58.000Z","updated_at":"2021-12-05T20:19:45.000Z","dependencies_parsed_at":"2022-08-22T23:10:45.088Z","dependency_job_id":null,"html_url":"https://github.com/axtk/react-store","commit_stats":null,"previous_names":[],"tags_count":58,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/axtk%2Freact-store","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/axtk%2Freact-store/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/axtk%2Freact-store/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/axtk%2Freact-store/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/axtk","download_url":"https://codeload.github.com/axtk/react-store/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":242005696,"owners_count":20056430,"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-hooks","shared-state","store"],"created_at":"2024-11-15T11:10:33.407Z","updated_at":"2025-03-05T09:47:09.766Z","avatar_url":"https://github.com/axtk.png","language":"TypeScript","readme":"[![npm](https://img.shields.io/npm/v/@axtk/react-store?labelColor=royalblue\u0026color=royalblue\u0026style=flat-square)](https://www.npmjs.com/package/@axtk/react-store) [![GitHub](https://img.shields.io/badge/-GitHub-royalblue?labelColor=royalblue\u0026color=royalblue\u0026style=flat-square\u0026logo=github)](https://github.com/axtk/react-store) ![browser](https://img.shields.io/badge/browser-✓-345?labelColor=345\u0026color=345\u0026style=flat-square) [![SSR](https://img.shields.io/badge/SSR-✓-345?labelColor=345\u0026color=345\u0026style=flat-square)](#server-side-rendering-ssr) ![TypeScript](https://img.shields.io/badge/TypeScript-✓-345?labelColor=345\u0026color=345\u0026style=flat-square)\n\n# @axtk/react-store\n\n*Compact shared-state management in React*\n\nTaking inspiration from the easiness of using local state with the `useState` hook.\n\n## Usage example\n\nThis package provides the `Store` class and the `useStoreListener` hook. The `Store` class is a container for shared data that allows for subscription to its updates, and the `useStoreListener` hook makes these subscriptions from within React components. The sharing of the stores across components is performed by means of React's Context in a pretty straightforward manner, as shown in the example below.\n\n```jsx\nimport {createContext, useContext} from 'react';\nimport ReactDOM from 'react-dom';\nimport {Store, useStoreListener} from '@axtk/react-store';\n\n// Creating a React Context that will be furnished with a\n// store in a `StoreContext.Provider` component below.\nconst StoreContext = createContext();\n\n// Making up a helper hook that picks the store from the Context\n// and makes a subscription to the store updates.\nconst useStore = () =\u003e {\n    const store = useContext(StoreContext);\n    useStoreListener(store);\n    return store;\n};\n\n// Both `PlusButton` and `Display` below subscribe to the same\n// store and thus share the value of `n` contained in the store.\n\nconst PlusButton = () =\u003e {\n    const store = useStore();\n    return (\n        \u003cbutton onClick={\n            () =\u003e store.set('n', store.get('n') + 1)\n        }\u003e\n            +\n        \u003c/button\u003e\n    );\n};\n\nconst Display = () =\u003e {\n    const store = useStore();\n    return \u003cspan\u003e{store.get('n')}\u003c/span\u003e;\n};\n\nconst App = () =\u003e \u003cdiv\u003e\u003cPlusButton/\u003e \u003cDisplay/\u003e\u003c/div\u003e;\n\nReactDOM.render(\n    // Initializing the context with a store.\n    // The constructor of the Store class accepts an (optional)\n    // initial state.\n    \u003cStoreContext.Provider value={new Store({n: 42})}\u003e\n        \u003cApp/\u003e\n    \u003c/StoreContext.Provider\u003e,\n    document.querySelector('#app')\n);\n```\n\nThis example covers much of what is needed to deal with a store in a React app, although there are in fact another couple of [methods](https://github.com/axtk/store/blob/master/README.md#store-api) in the Store API.\n\nFrom the context's perspective, the store as a data container never changes after it has been initialized concealing its updates under the hood. All interactions with the shared context data are left to the store itself, without the need to come up with additional utility functions to mutate the data in order to trigger a component update.\n\n## Multi-store setup\n\nThe shape of a React's context can be virtually anything. It means a single context can accommodate several stores. The task is still to pick the store from the context and to subscribe to its updates by means of the `useStoreListener` hook.\n\nHaving multiple stores can help to convey the semantic separation of data in the application and to avoid component subscriptions to updates of irrelevant chunks of data.\n\n```jsx\nimport {createContext, useContext} from 'react';\nimport ReactDOM from 'react-dom';\nimport {Store, useStoreListener} from '@axtk/react-store';\n\nconst StoreContext = createContext({});\n\n// A helper hook for quicker access to the specific store\n// from within the components\nconst useTaskStore = () =\u003e {\n    const {taskStore} = useContext(StoreContext);\n    useStoreListener(taskStore);\n    return taskStore;\n};\n\nconst Task = ({id}) =\u003e {\n    const taskStore = useTaskStore();\n    const task = taskStore.get(id);\n    return task \u0026\u0026 \u003cdiv class=\"task\"\u003e{task.name}: {task.status}\u003c/div\u003e;\n};\n\nconst App = () =\u003e {\n    // Fetching, pushing to the store and rendering multiple tasks\n};\n\nReactDOM.render(\n    \u003cStoreContext.Provider value={{\n        taskStore: new Store(),\n        userStore: new Store()\n    }}\u003e\n        \u003cApp/\u003e\n    \u003c/StoreContext.Provider\u003e,\n    document.querySelector('#app')\n);\n```\n\n## Server-side rendering (SSR)\n\nOn the server, the stores can be pre-filled and passed to a React Context in essentially the same way as in the client-side code.\n\n```jsx\n// On an Express server\napp.get('/', prefetchAppData, (req, res) =\u003e {\n    const html = ReactDOMServer.renderToString(\n        \u003cStoreContext.Provider value={new Store(req.prefetchedAppData)}\u003e\n            \u003cApp/\u003e\n        \u003c/StoreContext.Provider\u003e\n    );\n\n    const serializedAppData = JSON.stringify(req.prefetchedAppData)\n        .replace(/\u003c/g, '\\\\x3c');\n\n    res.send(`\n        \u003c!doctype html\u003e\n        \u003chtml\u003e\n            \u003chead\u003e\u003ctitle\u003eApp\u003c/title\u003e\u003c/head\u003e\n            \u003cbody\u003e\n                \u003cdiv id=\"app\"\u003e${html}\u003c/div\u003e\n                \u003cscript\u003e\n                    window._prefetchedAppData = ${serializedAppData};\n                \u003c/script\u003e\n                \u003cscript src=\"/index.js\"\u003e\u003c/script\u003e\n            \u003c/body\u003e\n        \u003c/html\u003e\n    `);\n});\n```\n\nWhen the application is rendered in the browser, the browser store instance can be filled with the serialized data to match the rendered state.\n\n```jsx\n// On the client\nReactDOM.hydrate(\n    \u003cStoreContext.Provider value={new Store(window._prefetchedAppData)}\u003e\n        \u003cApp/\u003e\n    \u003c/StoreContext.Provider\u003e,\n    document.querySelector('#app')\n);\n```\n\n## Local store\n\nA component-scoped store can act as a local state persistent across remounts and as an unmount-safe storage for async data.\n\n```jsx\nimport {useEffect} from 'react';\nimport {Store, useStoreListener} from '@axtk/react-store';\n\nconst itemStore = new Store();\n\nconst Item = ({id}) =\u003e {\n    useStoreListener(itemStore);\n\n    useEffect(() =\u003e {\n        if (itemStore.get(id)) return;\n\n        itemStore.set(id, {loading: true});\n\n        fetch(`/items/${id}`)\n            .then(res =\u003e res.json())\n            .then(data =\u003e itemStore.set(id, {data, loading: false}));\n        // If the request completes after the component has unmounted\n        // the fetched data will be safely put into `itemStore` to be\n        // reused when/if the component remounts.\n\n        // Data fetching error handling was not added to this example\n        // only for the sake of focusing on the interaction with the\n        // store.\n    }, [itemStore]);\n\n    let {data, loading} = itemStore.get(id) || {};\n\n    // Rendering\n};\n\nexport default Item;\n```\n\n## Optional fine-tuning\n\nBy default, each store update will request a re-render of the component subscribed to the particular store, which is then optimized by React with its virtual DOM reconciliation algorithm before affecting the real DOM (and this can be sufficient in many cases). The function passed to the `useStoreListener` hook as the optional second parameter can prevent the component from some re-renders at an even earlier stage if its returned value hasn't changed.\n\n```js\nuseStoreListener(store, store =\u003e store.get([taskId, 'timestamp']));\n// In this example, a store update won't request a re-render if the\n// timestamp of the specific task hasn't changed.\n```\n\n```js\nuseStoreListener(store, null);\n// With `null` as the second argument, the store updates won't cause\n// any component re-renders.\n```\n\nThe optional third parameter allows to specify the value equality function used to figure out whether the update trigger value has changed. By default, it is `Object.is`.\n\n```js\nuseStoreListener(\n    store,\n    store =\u003e store.get([taskId, 'timestamp']),\n    (prev, next) =\u003e next - prev \u003c 1000\n);\n// In this example, a store update won't request a re-render if the\n// timestamp of the specific task has increased by less than a\n// second compared to the previous timestamp value.\n```\n\n## Also\n\n- *[@axtk/store](https://github.com/axtk/store)*, the `Store` class without React\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faxtk%2Freact-store","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Faxtk%2Freact-store","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faxtk%2Freact-store/lists"}