{"id":28394924,"url":"https://github.com/alexiii/whoosh","last_synced_at":"2025-09-11T01:07:53.207Z","repository":{"id":57410466,"uuid":"443340689","full_name":"AlexIII/whoosh","owner":"AlexIII","description":"Whoosh - minimalistic React state manager","archived":false,"fork":false,"pushed_at":"2023-10-03T11:15:55.000Z","size":91,"stargazers_count":9,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-08-16T19:33:12.671Z","etag":null,"topics":[],"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/AlexIII.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,"zenodo":null}},"created_at":"2021-12-31T12:27:53.000Z","updated_at":"2022-11-07T09:05:55.000Z","dependencies_parsed_at":"2023-10-03T19:03:58.346Z","dependency_job_id":null,"html_url":"https://github.com/AlexIII/whoosh","commit_stats":null,"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"purl":"pkg:github/AlexIII/whoosh","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AlexIII%2Fwhoosh","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AlexIII%2Fwhoosh/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AlexIII%2Fwhoosh/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AlexIII%2Fwhoosh/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/AlexIII","download_url":"https://codeload.github.com/AlexIII/whoosh/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AlexIII%2Fwhoosh/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":274557583,"owners_count":25307516,"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","status":"online","status_checked_at":"2025-09-10T02:00:12.551Z","response_time":83,"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":[],"created_at":"2025-05-31T19:09:10.690Z","updated_at":"2025-09-11T01:07:53.176Z","avatar_url":"https://github.com/AlexIII.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cimg align=\"right\" width=\"110\" src=\"./whoosh-logo.png\" /\u003e\n\n# Whoosh - minimalistic React state manager\n\n**Whoosh** is a React state manager which entire API consists of exactly _one_ function - `createShared()`.\n\n[TL;DR version of the docs](tldr.md)\n\n### Navigation\n\n- [Mindset](#mindset)\n- [Installation](#installation)\n- [Examples](#examples)\n  - [Counter](#counter)\n  - [Counter with Reducer](#counter-with-reducer)\n  - [Reducer advanced usage](docs/reducer-advanced.md) [separate file]\n  - [Shared State Interaction](docs/shared-state-interaction.md) [separate file]\n- [Reducer library](docs/reducer-lib.md) [separate file]\n  - [Reducer `toLocalStorage()`](docs/reducer-lib.md#reducer-tolocalstorage)\n  - [Reducer `arrayOp` and `setOp`](docs/reducer-lib.md#reducer-arrayop-and-setop)\n  - [Reducer `partialUpdate`](docs/reducer-lib.md#reducer-partialUpdate)\n  - [Reducer composition](docs/reducer-lib.md#reducer-composition)\n- [Shared State object API](#shared-state-object-api)\n- [`createShared()` function API](#createshared-function-api)\n- [Usage with class components](#usage-with-class-components)\n- [Changelog](#changelog)\n- [License](#license)\n\n## Mindset\n\nWhoosh aims to be\n\n- easy to use and reason about,\n- general and extendable,\n- compact*.\n\n*Whoosh is _very_ small. Its entire source code is under 80 lines (code readability is not sacrificed)\nand it takes less than 1 Kbyte in minimized form.\n\n## Installation\n\n`npm install --save whoosh-react`\n\n## Examples\n\n### Counter\n\n[This example on codesandbox.io](https://codesandbox.io/s/whoosh-counter-simple-yejzt?file=/src/App.tsx)\n\n1. Create Shared State\n\n```tsx\n// AppState.ts\nimport { createShared } from 'whoosh-react';\n\nexport const appCounter = createShared\u003cnumber\u003e(0);\n```\n`createShared()` accepts an initial value and returns an object that represents Shared State.\n\n2. Use Shared State in React components\n\n```tsx\n// Counter.tsx\nimport { appCounter } from './AppState.ts';\n\nconst CounterValue = () =\u003e {\n    const counter = appCounter.use();\n    return \u003cp\u003e { counter } \u003c/p\u003e;\n};\n\nconst CounterControls = () =\u003e {\n    const reset = () =\u003e appCounter.set(0);\n    const addOne = () =\u003e appCounter.set(previousValue =\u003e previousValue + 1);\n    return (\u003c\u003e\n        \u003cbutton onClick={reset} \u003e Reset \u003c/button\u003e\n        \u003cbutton onClick={addOne} \u003e Add 1 \u003c/button\u003e\n    \u003c/\u003e);\n};\n```\n\nIn this example we call two function from the `appCounter`:\n\n- `use()` returns current value of the Shared State.\n It is a React Hook that will trigger component re-render every time Shared State changes.\n\n- `set()` is a plain JS function that updates Shared State.\n It accepts either a new value or a function that returns the new value.\n\n3. Render the components. They can be anywhere in the tree.\n\n```tsx\nconst RootComponent = () =\u003e (\n    \u003c\u003e\n        \u003cA\u003e\n            \u003cCounterValue/\u003e\n        \u003c/A\u003e\n        \u003cB\u003e\n            \u003cCounterControls/\u003e\n        \u003c/B\u003e\n    \u003c/\u003e\n);\n```\n\n### Counter with Reducer\n\n[This example on codesandbox.io](https://codesandbox.io/s/whoosh-counter-reducer-4kwrn?file=/src/App.tsx)\n\n`createShared()` has the second optional parameter which is a Reducer function.\n\nReducer is a function of type `(previousValue: S, input: A) =\u003e S`. \nIt describes how Shared State of type `S` should be modified based on the `previousValue` and the `input`.\n`input` is the value that was passed to the `appCounter.set()`.\nNotice, that if an invalid input is passed to `set()` a `Error` will be thrown to the caller of `set()`.\n\n```tsx\n// AppState.ts\nexport const appCounter = createShared\u003cnumber, { operation: 'add' | 'subtract' | 'set'; arg: number; }\u003e(\n    0,\n    (previousValue, { operation, arg }) =\u003e {\n        switch(operation) {\n            case 'add': return previousValue + arg;\n            case 'subtract': return previousValue - arg;\n            case 'set': return arg;\n        }\n        // This Error will be thrown to the caller of `appCounter.set(__invalid_parameter__)`\n        throw new Error(`appCounter Reducer: operation ${operation} is not supported!`)\n    }\n);\n```\n\n```tsx\n// Counter.tsx\nconst CounterControls = () =\u003e {\n    const reset = () =\u003e appCounter.set({operation: 'set', arg: 0});\n    const addOne = () =\u003e appCounter.set({operation: 'add', arg: 1});\n    return (\u003c\u003e\n        \u003cbutton onClick={reset} \u003e Reset \u003c/button\u003e\n        \u003cbutton onClick={addOne} \u003e Add 1 \u003c/button\u003e\n    \u003c/\u003e);\n};\n```\n\nPassing a function to `appCounter.set()` is still possible:\n\n```tsx\nconst toggleBetween0and1 = () =\u003e appCounter.set(\n    previousValue =\u003e ({\n        operation: (previousValue \u003e 0? 'subtract' : 'add'),\n        arg: 1\n    })\n);\n```\n\nRefer to [this tutorial](docs/reducer-advanced.md) on advanced reducer usage in Whoosh.\n\nMost common reducers are available (but completely optional) in the [Reducer library](docs/reducer-lib.md)\n\n## Shared State object API\n\n`createShared()` returns a Shared State object with the next interface\n\n```ts\n// S - State type\n// A - Reducer input type (if Reducer is present)\n\ninterface SharedState\u003cS, A = S\u003e {\n    use(): S;                                   // React Hook that returns current state value\n    get(): S;                                   // Getter\n    set(a: A | ((s: S) =\u003e A)): void;            // Setter / Dispatcher\n    setRaw(s: S): void;                         // Sets state bypassing reducer\n    on(cb: (state: S) =\u003e void): () =\u003e void;     // Subscribe on the state change, returns unsubscribe function\n    off(cb: (state: S) =\u003e void): void;          // Unsubscribe off the state change\n}\n```\n\n- `use()` is a React Hook that will trigger component re-render when the Shared State changes.\n It is subject to React Hook usage rules, just like any other Hook.\n It can only be called from the inside of a functional component.\n\nAll other functions are plain js functions. They can be called from anywhere.\n\n- `get()` gets current Shared State value. Mutation of the returned value will modify the underlying Shared State value,\n but won't trigger re-renders or calls of the subscribers.\n\n- `set()` updates Shared State value. Accepts either a new value or a function that accepts previous value and returns the new value. \n The new value should be of type `S` if no reducer is passed to `createShared()` or of type `A` if there is.\n (Of course, nothing prevents you having `S === A` which is a very useful case by itself.)\n The call of the function will trigger re-render of the components that are mounted and `use()` this Shared State.\n\n- `setRaw()` updates Shared State value bypassing the reducer completely. Can accept only the new value itself, not a function that produces the new value.\n Primarily used for extensions and libraries. Direct usage from normal user code is not recommended. \n\n- `on()` and `off()` allow to manually subscribe and unsubscribe to/from Shared State changes.\n See [Shared State Interaction](docs/shared-state-interaction.md) for usage example.\n\nAll `SharedState` functions are guaranteed to be _stable_. It’s safe to omit them from the `useEffect` or other hooks dependency lists.\n\nAll `SharedState` functions do NOT require bind. They are really just _functions_ and NOT class _methods_.\n\n## `createShared()` function API\n\n```ts\n// S - State type\n// A - Reducer input type (if Reducer is present)\n// I - Initializer input type (if Reducer and Initializer are both present)\n\ntype Reducer\u003cS, A\u003e = (previousState: S, input: A) =\u003e S;\ntype ReducerAndInit\u003cS, A, I\u003e = [ Reducer\u003cS, A\u003e, (initArg: I) =\u003e S ];\ntype ReducerOrReducerWithInit\u003cS, A\u003e = Reducer\u003cS, A\u003e | ReducerAndInit\u003cS, A, S\u003e;\n\nfunction createShared\u003cS\u003e(initValue: S, reducer?: ReducerOrReducerWithInit\u003cS, S\u003e): SharedState\u003cS, S\u003e;\nfunction createShared\u003cS, A\u003e(initValue: S, reducer: ReducerOrReducerWithInit\u003cS, A\u003e): SharedState\u003cS, A\u003e;\nfunction createShared\u003cS, A, I\u003e(initValue: I, reducer: ReducerAndInit\u003cS, A, I\u003e): SharedState\u003cS, A\u003e;\n```\n\n`createShared()` takes two arguments: an `initialValue` (required) and a `reducer` (optional).\n\nThe `reducer` is either a Reducer function or a tuple (an array) of two functions,\nfirst of which is a Reducer and second is an Initializer.\n\n## Usage with class components\n\nWhoosh primarily targets the \"new way\" of doing thins in React.\nThat is, when the entire application is build using only functional components.\nBut, if you have to support a class component, you can manually subscribe `on()` the Shared State change in `componentWillMount()` \nand unsubscribe `off()` the Shared State change in `componentWillUnmount()`.\n\n## Changelog\n\n`1.0.12`\n\n- Add `setRaw()` method to `SharedState`\n\n`1.0.11`\n\n- Fix rare bug of component no-update when SharedState changes between component first render and effect call\n\n`1.0.10`\n\n- Remove freeze for the underlying state object. That was a bad idea...\n\n`1.0.9`\n\n- Improve `createShared()` typing\n\n`1.0.8`\n\n- Improve `toLocalStorage()` typing\n\n`1.0.8`\n\n- Update docs\n- Add reducer partialUpdate\n\n`1.0.6`\n\n- Improve reducer-compose typing\n\n`1.0.5`\n\n- Add reducers `arrayOp` and `setOp` to the reducer library and update docs accordingly\n- Underlying state object is now freezed in order to prevent modifications\n\n`1.0.4`\n\n- Add initializer function to the reducer argument of `createShared()`\n- Add reducer library:\n  - `toLocalStorage()` reducer\n  - `compose()` - a function for reducer composition\n- Build with rollup\n\n`1.0.1`\n\n- Initial release\n\n## License\n\n© github.com/AlexIII\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falexiii%2Fwhoosh","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Falexiii%2Fwhoosh","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falexiii%2Fwhoosh/lists"}