{"id":17540303,"url":"https://github.com/pabra/react-hooksack","last_synced_at":"2025-04-23T22:41:36.732Z","repository":{"id":34527736,"uuid":"179928123","full_name":"pabra/react-hooksack","owner":"pabra","description":"Fully typed \u0026 lightweight react store entirely based on hooks.","archived":false,"fork":false,"pushed_at":"2022-10-14T00:02:29.000Z","size":811,"stargazers_count":6,"open_issues_count":8,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-30T04:11:45.350Z","etag":null,"topics":["global-state","hooks","reactjs","reducer","state-management","store","typescript"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/react-hooksack","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/pabra.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}},"created_at":"2019-04-07T06:47:08.000Z","updated_at":"2022-04-17T00:28:35.000Z","dependencies_parsed_at":"2023-01-15T07:35:42.100Z","dependency_job_id":null,"html_url":"https://github.com/pabra/react-hooksack","commit_stats":null,"previous_names":[],"tags_count":26,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pabra%2Freact-hooksack","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pabra%2Freact-hooksack/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pabra%2Freact-hooksack/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pabra%2Freact-hooksack/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pabra","download_url":"https://codeload.github.com/pabra/react-hooksack/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250528681,"owners_count":21445511,"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":["global-state","hooks","reactjs","reducer","state-management","store","typescript"],"created_at":"2024-10-20T22:09:00.484Z","updated_at":"2025-04-23T22:41:36.712Z","avatar_url":"https://github.com/pabra.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![npm version](https://img.shields.io/npm/v/react-hooksack?label=version\u0026logo=npm)](https://www.npmjs.com/package/react-hooksack)\n[![npm bundle size (scoped)](https://img.shields.io/bundlephobia/min/react-hooksack?logo=data%3Aimage%2Fpng%3Bbase64%2CiVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAACSUlEQVRYw%2B2XP2gTcRTH459BEKSiQkH8%2FcmlaUpJEESECpKh0IKiU2b%2FDNKxDkrp0rjZUXBSHERcBEEX7eRQhVKcBBfrIgriYDEoorVJzs%2FL%2FdpexbSXXq4g3MHjLne%2F3%2Ft%2B3vu9y%2B9dJhPjsNb2G5O9YUzBZLbrKJVKexG9qLV9aYz9zPW01mbRGPM0m82eqVarOxMRRuAEorc5f0X4CdGfrVQqu4Jn5T3cP8%2F9eew9UBO5XO5QbNF8Pn8QZ1eI8A3i78SxMYO9G83xPO8YMHcBqXF%2BwO%2BhjkQlhUwcwcHDwIm9R7SnOs%2FY0R6Ax5m%2FgL3mekyWb6OC0gy6TqQfsVcygTTui5tF3%2Fd3UBvDBPVIlg%2Fft7TuG1g3iBT7QSHZmwwuJlXALOlhCVKKVzRXimvKATSgewHA8aQAqIcjStnHaNVFU2DCAA7CNLEvwFzt1hIENSWFbBpKtTT8dgBhE5A6xDNbKcJisbhfKe8aPmqhwNZpbAawCqKUXnZZmYz4Gsob9NsJt%2FUdFSBsDef4OfPOlcvl3Wt%2FRN4FAN%2FyfOlf0XYLIGw%2FmVsLXivzg%2BtfnfqICxDbUoAUIAVIAVKAFoDriGrBBmKXkxalH5BmpEng3%2F5qy%2FQQe%2F%2Bs61bqUXe06Nba0ptBh913uu1%2BLr0%2BgyfJxqcuZKXhWj12Sn2%2F428F%2BsO86%2BGW1pxFj5Z0f6BBudyVhhKnl4BZ2CQrreVjfZ8JfCKdbaFQOKC1dweI70A1XXakqBYRnVr5XNuWA9FRQOaU6j%2BZ%2BV%2BPP5ZRWPOOMBtbAAAAAElFTkSuQmCC)](https://bundlephobia.com/package/react-hooksack)\n[![Codecov](https://img.shields.io/codecov/c/github/pabra/react-hooksack/master?logo=codecov)](https://codecov.io/gh/pabra/react-hooksack)\n[![unit-tests](https://github.com/pabra/react-hooksack/workflows/unit-tests/badge.svg?branch=master)](https://github.com/pabra/react-hooksack/actions?query=branch%3Amaster+workflow%3Aunit-tests)\n[![npm-publish](https://github.com/pabra/react-hooksack/workflows/npm-publish/badge.svg)](https://github.com/pabra/react-hooksack/actions?query=workflow%3Anpm-publish)\n\n# React HookSack\n\nA lightweight, fully typed store for react, based entirely on hooks.\n\nKeep re-rendering of involved components at a minimum. See this codesandbox with\nnested components. (thanks to [@andrewgreenh](https://github.com/andrewgreenh)\nfor creating [#3](https://github.com/pabra/react-hooksack/issues/3))\n\n[![Edit 487k2wzpq4](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/minimum-rerender-2nvpy?file=/src/index.tsx\u0026expanddevtools=1)\n\n## Install\n\n```bash\nnpm install --save react-hooksack\n# or\nyarn add react-hooksack\n```\n\n## Usage\n\n### Import\n\n```typescript\nimport makeStore from 'react-hooksack';\n```\n\n### initialize new store (hook)\n\n```typescript\nconst useStore = makeStore(initialValue, reducer);\n```\n\n| object         | required | type                                                                                         | description                                                                                                                                                                                              |\n| -------------- | :------: | -------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `initialValue` |   yes    | `State`                                                                                      | Whatever you pass as initial value becomes the store\\`s state type (we will refer as`State`).`makeStore`is type generic, so you could set more complex types like this:`makeStore\u003cnull \\| number\u003e(null)` |\n| `reducer`      |    no    | \u003cpre\u003e(\u003cbr\u003e\u0026nbsp;\u0026nbsp;state: State,\u003cbr\u003e\u0026nbsp;\u0026nbsp;action: ReducerAction\u003cbr\u003e) =\u003e State\u003c/pre\u003e | If passed, `setState` will only accept `ReducerAction` later.                                                                                                                                            |\n\n### use within components\n\n```typescript\n// get state and setter\nconst [state, setState] = useStore();\n\n// or just get state\nconst state = useStore('justState');\n\n// or just get setter (this one avoid re-renders)\nconst setState = useStore('justSetter');\n```\n\n| object                  | type                                                                                                                                                          | required | description                                                                                                                                                                                                                                                     |\n| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------: | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `useStore`              | \u003cpre\u003e(\u003cbr\u003e\u0026nbsp;\u0026nbsp;just?: 'justState' \\| 'justSetter'\u003cbr\u003e) =\u003e\u003cbr\u003e\u0026nbsp;\u0026nbsp;\\| [State, setState]\u003cbr\u003e\u0026nbsp;\u0026nbsp;\\| State\u003cbr\u003e\u0026nbsp;\u0026nbsp;\\| setState\u003c/pre\u003e |          | Without arguments, `useStore` behaves like `useState` of React (except for the initial value). Otherwise you get what you asked for (see below).                                                                                                                |\n| `state`                 | `State`                                                                                                                                                       |          | Will be of the type you set as `initialValue` on `makeStore`. Every component that consumes `state` will re-render whenever `state` changes.                                                                                                                    |\n| `setState` (no reducer) | \u003cpre\u003e(\u003cbr\u003e\u0026nbsp;\u0026nbsp;argument:\u003cbr\u003e\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\\| State\u003cbr\u003e\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\\| ((currentState: State) =\u003e State)\u003cbr\u003e) =\u003e void\u003c/pre\u003e         |          | Hooksack is using React\\`s `useState` hook under the hood. Thus, you can just pass the new state of type `State` or pass a function that gets the current state and has to return the new state.                                                                |\n| `setState` (reducer)    | \u003cpre\u003e(\u003cbr\u003e\u0026nbsp;\u0026nbsp;action: ReducerAction\u003cbr\u003e) =\u003e void\u003c/pre\u003e                                                                                                |          | If `makeStore` was initialized with a reducer, `setState` expects that reducer\\`s `ReducerAction` as argument.                                                                                                                                                  |\n| `just`                  | `'justState'`                                                                                                                                                 |    no    | `useStore` will just return the current state of type `State`.                                                                                                                                                                                                  |\n| `just`                  | `'justSetter'`                                                                                                                                                |    no    | `useStore` will just return the state setter `setState` depending on if you passed a reducer to `makeStore` or not (see above). Components that just get the state setter through literal `'justSetter'` **will not get re-rendered** whenever `state` changes. |\n\n## Example\n\n### use without reducer\n\n[![Edit 487k2wzpq4](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/487k2wzpq4)\n\n```tsx\nimport * as React from 'react';\nimport * as ReactDOM from 'react-dom';\nimport makeStore from 'react-hooksack';\n\n// util for counting rerenders\nimport { LogTable, useLogStore } from './logRender';\n\n// make a new store and set it's initial value (to 0)\nconst useClickStore = makeStore(0);\n\n// a component that subscribes to the current state of \"clicks\"\n// und uses the setter \"setClicks\"\nconst ViewAndUpdate = () =\u003e {\n  // by consuming the state \"clicks\" this component will rerender\n  // every time, \"clicks\" gets updated\n  const [clicks, setClicks] = useClickStore();\n\n  // just to count rerenderings\n  const logRender = useLogStore('justSetter');\n  React.useEffect(() =\u003e logRender('ViewAndUpdate'));\n\n  return (\n    \u003cbutton\n      title=\"ViewAndUpdate\"\n      onClick={() =\u003e setClicks(currentClicks =\u003e currentClicks + 1)}\n    \u003e\n      add click (currently {clicks})\n    \u003c/button\u003e\n  );\n};\n\n// a simple component to view the store's state\nconst ViewOnly = () =\u003e {\n  // just subsribing to the state of \"clicks\" - no setter required here\n  // this component will rerender with every update of \"clicks\" too\n  const clicks = useClickStore('justState');\n\n  // just to count rerenderings\n  const logRender = useLogStore('justSetter');\n  React.useEffect(() =\u003e logRender('ViewOnly'));\n\n  return (\n    \u003cdiv title=\"ViewOnly\"\u003e\n      \u003cspan\u003eclicks: {clicks}\u003c/span\u003e\n    \u003c/div\u003e\n  );\n};\n\n// a component that will only set the new state for \"clicks\"\nconst UpdateOnly = () =\u003e {\n  // by just using the setter for \"clicks\" this component will not\n  // rerender every time \"clicks\" updates\n  const setClicks = useClickStore('justSetter');\n\n  // just to count rerenderings\n  const logRender = useLogStore('justSetter');\n  React.useEffect(() =\u003e logRender('UpdateOnly'));\n\n  return (\n    \u003cbutton title=\"UpdateOnly\" onClick={() =\u003e setClicks(clicks =\u003e clicks + 1)}\u003e\n      add click\n    \u003c/button\u003e\n  );\n};\n\nconst App = () =\u003e (\n  \u003cdiv\u003e\n    \u003cViewOnly /\u003e\n    \u003cViewAndUpdate /\u003e\n    \u003cUpdateOnly /\u003e\n    \u003chr /\u003e\n    \u003cLogTable /\u003e\n  \u003c/div\u003e\n);\n\nReactDOM.render(\u003cApp /\u003e, document.getElementById('root'));\n```\n\n### use with reducer\n\n[![Edit rmj4vyyn04](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/rmj4vyyn04)\n\n```tsx\nimport * as React from 'react';\nimport * as ReactDOM from 'react-dom';\nimport makeStore from 'react-hooksack';\n\n// shape a ToDo\ninterface Todo {\n  id: number;\n  name: string;\n  done: boolean;\n}\n\n// define the ToDo store reducer\nconst reducer = (\n  state: Todo[],\n  action: { type: 'add' | 'del' | 'toggle'; todo: Todo },\n) =\u003e {\n  switch (action.type) {\n    case 'add':\n      // ensure to always return a new object\n      return [...state, action.todo];\n\n    case 'del':\n      // ensure to always return a new object\n      return state.filter(todo =\u003e todo !== action.todo);\n\n    case 'toggle':\n      // ensure to always return a new object\n      return state.map(todo =\u003e\n        todo === action.todo ? { ...todo, done: !todo.done } : todo,\n      );\n\n    default:\n      throw new Error();\n  }\n};\n\n// make a new store, set it's initial value and pass reducer\nconst useTodoStore = makeStore(\n  [\n    { id: 1, done: false, name: 'remember the milk' },\n    { id: 2, done: false, name: 'feed the cat' },\n    { id: 3, done: true, name: 'walk the dog' },\n    { id: 4, done: true, name: \"order a table at Luigi's\" },\n  ],\n  reducer,\n);\n\n// component to render and toggle a Todo\nconst Todo: React.FC\u003c{ todo: Todo }\u003e = ({ todo }) =\u003e {\n  const setTodos = useTodoStore('justSetter');\n  const style = {\n    textDecoration: todo.done ? 'line-through' : undefined,\n  };\n  const handleChange = () =\u003e {\n    setTodos({ type: 'toggle', todo });\n  };\n\n  return (\n    \u003cli\u003e\n      \u003cinput type=\"checkbox\" checked={todo.done} onChange={handleChange} /\u003e\n      \u003cspan style={style}\u003e{todo.name}\u003c/span\u003e\n    \u003c/li\u003e\n  );\n};\n\n// component to render a list of Todos\nconst TodoList: React.FC\u003c{ todos: Todo[] }\u003e = ({ todos }) =\u003e (\n  \u003cul\u003e\n    {todos.map(todo =\u003e (\n      \u003cTodo key={todo.id} todo={todo} /\u003e\n    ))}\n  \u003c/ul\u003e\n);\n\n// component to tell appart already done Todos from to be tone ones\nconst AllTodos = () =\u003e {\n  const todos = useTodoStore('justState');\n  const toBeDone = todos.filter(todo =\u003e todo.done === false);\n  const done = todos.filter(todo =\u003e todo.done);\n\n  return (\n    \u003cdiv\u003e\n      {toBeDone.length \u003e 0 \u0026\u0026 (\n        \u003cdiv\u003e\n          \u003ch1\u003eto be done\u003c/h1\u003e\n          \u003cTodoList todos={toBeDone} /\u003e\n        \u003c/div\u003e\n      )}\n      {done.length \u003e 0 \u0026\u0026 (\n        \u003cdiv\u003e\n          \u003ch1\u003ealready done\u003c/h1\u003e\n          \u003cTodoList todos={done} /\u003e\n        \u003c/div\u003e\n      )}\n    \u003c/div\u003e\n  );\n};\n\n// component to add a new, undone todo\nconst AddTodo = () =\u003e {\n  const dispatchTodos = useTodoStore('justSetter');\n  const todoInput = React.useRef\u003cHTMLInputElement\u003e(null);\n  const addTodo = () =\u003e {\n    if (todoInput \u0026\u0026 todoInput.current) {\n      const name = todoInput.current.value;\n      dispatchTodos({\n        type: 'add',\n        todo: { name, done: false, id: Date.now() },\n      });\n      todoInput.current.value = '';\n    }\n  };\n\n  return (\n    \u003cdiv\u003e\n      \u003cinput ref={todoInput} placeholder=\"new Todo name\" /\u003e\n      \u003cbutton onClick={addTodo}\u003eadd\u003c/button\u003e\n    \u003c/div\u003e\n  );\n};\n\nconst App = () =\u003e (\n  \u003cdiv\u003e\n    \u003cAllTodos /\u003e\n    \u003cAddTodo /\u003e\n  \u003c/div\u003e\n);\n\nReactDOM.render(\u003cApp /\u003e, document.getElementById('root'));\n```\n\n## Tests\n\n```bash\nnpm run test\n# or\nnpm run test:coverage\n```\n\n## Why\n\nI got inspired by [a blog post of Jhonny Michel](https://blog.usejournal.com/global-state-management-with-react-hooks-5e453468c5bf).\nHe also released [react-hookstore](https://github.com/jhonnymichel/react-hookstore) but I:\n\n- don't like to register a new store with a string passed\n- prefer functions over classes\n- like Typescript / type support\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpabra%2Freact-hooksack","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpabra%2Freact-hooksack","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpabra%2Freact-hooksack/lists"}