{"id":17000142,"url":"https://github.com/mitschabaude/minimal-state","last_synced_at":"2026-05-19T02:10:33.925Z","repository":{"id":57297872,"uuid":"316344012","full_name":"mitschabaude/minimal-state","owner":"mitschabaude","description":"The React State Library to Rule Them All","archived":false,"fork":false,"pushed_at":"2021-07-29T19:44:53.000Z","size":88,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-09T08:34:14.654Z","etag":null,"topics":["bundle-size","react","state-management","typescript"],"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/mitschabaude.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-11-26T21:53:53.000Z","updated_at":"2023-03-17T07:53:59.000Z","dependencies_parsed_at":"2022-09-01T08:41:49.559Z","dependency_job_id":null,"html_url":"https://github.com/mitschabaude/minimal-state","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mitschabaude%2Fminimal-state","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mitschabaude%2Fminimal-state/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mitschabaude%2Fminimal-state/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mitschabaude%2Fminimal-state/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mitschabaude","download_url":"https://codeload.github.com/mitschabaude/minimal-state/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244181367,"owners_count":20411605,"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":["bundle-size","react","state-management","typescript"],"created_at":"2024-10-14T04:11:12.530Z","updated_at":"2026-05-19T02:10:33.888Z","avatar_url":"https://github.com/mitschabaude.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# minimal-state\n\nProbably the only React state management library I ever want to use.\n\n- 🚀 Optimized for fast development. API supports mutable + immutable code styles\n- 💡 Perfect TypeScript support\n- 😎 Your state is a plain JS object. No bloated class, no proxy magic.\n- 🪶 1 kB minzipped - just drop it anywhere\n\n```sh\nyarn add use-minimal-state\n```\n\n```js\nimport React from 'react';\nimport {use, set, update, on} from 'use-minimal-state';\n\n// the state is just an object\nconst state = {count: 0};\n\nfunction App() {\n  // hook which returns fresh values, like useState\n  let count = use(state, 'count');\n\n  // there are two ways to update state:\n\n  // 1. set() with a setState-like API\n  let increment = () =\u003e set(state, 'count', count + 1);\n\n  // 2. update() which only triggers component updates, is more flexible\n  let setTo9000 = () =\u003e {\n    state.count = 1000; // no magic, state is just an object\n    state.count *= 9;\n    update(state, 'count'); // update when you're ready\n  };\n\n  return (\n    \u003c\u003e\n      \u003cdiv\u003e{count}\u003c/div\u003e\n      \u003cbutton onClick={increment}\u003e+1\u003c/button\u003e\n      \u003cbutton onClick={setTo9000}\u003e9000\u003c/button\u003e\n    \u003c/\u003e\n  );\n}\n\n// behind the use() hook is a flexible event emitter API that you can use for other\n// stuff as well:\non(state, 'count', c =\u003e console.log('The count is', c));\n\nstate.count = 10;\nupdate(state, 'count'); // \"The count is 10\", updates component\n\nset(state, 'count', 11); // \"The count is 11\", updates component\n\n// set() and update() are synchronous\nconsole.log(state.count); // \"11\"\n```\n\n## Without React\n\nA version of the library without the `use` hook is also available as separate npm package, which does not depend on React:\n\n```sh\nyarn add minimal-state\n```\n\nIn fact, `minimal-state` has no external dependencies at all (and is only 800 bytes). It can be useful as a general-purpose reactive state / event-emitter. Other than `use`, the packages `use-minimal-state` and `minimal-state` are exactly equivalent.\n\n## API\n\nThe API of `minimal-state` adhers to the philosophy that...\n\n\u003e It is better to have 100 functions operate on one data structure than 10 functions on 10 data structures. — Alan Perlis\n\nIn our case there are two data types, which we call _state_ and _atom_.\n\n- A _state_ is any JS hashmap, like `{}` or `{users: []}`.\n- An _atom_ is any value wrapped in a single-element array, like `[1]` or `[\"wow\"]` or `[{users: []}]`.\n\nBoth of these types can be made **reactive** because they can be changed while still keeping a stable reference (unlike plain strings or numbers).\n\nI worked hard to make a reactive API that is as simple and intuitive as possible.\n\n### Core API\n\n```js\n// use one state attribute\nuse(state, key) // == state[key]\n\n// use list of attributes, in a list\nuse(state, [key1, key2, ...]) // == [state[key1], state[key2], ...]\n\n// use entire state\nuse(state) // == shallow copy of state\n\n// use an atom\nuse(atom) // == atom[0], the atom's value\n\n\n// update state attribute\nupdate(state, key);\n\n// update atom\nupdate(atom)\n\n\n// set attribute (value)\nset(state, key, value);\n\n// set attribute (function)\nset(state, key, oldValue =\u003e value);\n\n// set multiple values at once by merging\nset(state, {key: value, otherKey: otherValue});\n\n// set the value of an atom\nset(atom, value)\n\n// set the value of an atom (function)\nset(atom, oldValue =\u003e value)\n```\n\nTo understand `update` vs `set`, it is best to think of `set(state, key, value)` as a shortcut for\n\n```js\nstate[key] = value;\nupdate(state, key);\n```\n\nSimilarly, `set(atom, value)` is just\n\n```js\natom[0] = value;\nupdate(atom);\n```\n\n### Event Emitter API\n\nThe core API builds on top of four functions `emit, on, off, clear` that implement a simple event emitter.\nEvery `emit` triggers a call to all listeners registered with `on`.\n\n```js\n// emit event\nemit(state, key, ...args);\n\n// atom events don't have keys\nemit(atom, ...args);\n\n// listen to event with specific key\non(state, key, (...args) =\u003e {}); // (...args) are what is passed to emit\n\n// listen to event with any key\non(state, (key, ...args) =\u003e {}); // (...args) are what is passed to emit\n\n// listen to atom event\non(atom, (...args) =\u003e {}); // (...args) are what is passed to emit\n\n// on() returns an unsubscribe function to stop listening\nlet unsubscribe = on(state, key, () =\u003e {});\nunsubscribe();\n\n// or unsubscribe directly (needs reference to the listener function)\nlet listener = (...args) =\u003e {};\noff(state, key, listener);\noff(atom, listener);\n\n// unsubscribe all listeners (for all keys)\nclear(state);\nclear(atom);\n/* TODO: clear(state, key) */\n```\n\nInternally, both `update(state, key)` and `set(state, key, value)` call `emit(state, key, ...args)` _twice_, but in slightly different ways:\n\n```js\nupdate(state, key);\n// calls:\nemit(state, key, state[key]);\nemit(state, undefined, key, state[key]);\n// triggers:\non(state, key, value =\u003e {});\non(state, (key, value) =\u003e {});\n\nset(state, key, value);\n// calls:\nemit(state, key, value, oldValue);\nemit(state, undefined, key, value, oldValue);\n// triggers:\non(state, key, (value, oldValue) =\u003e {});\non(state, (key, value, oldValue) =\u003e {});\n```\n\nThat is, if `on` listeners need access to the value _and_ the previous value, you always have to use `set` for changing it.\n\nThe `undefined` event is an internal \"wildcard\" event that gets triggered for every update.\n\nAtom updates are a bit simpler:\n\n```js\nupdate(atom);\n// calls:\nemit(atom, atom[0]);\n// triggers:\non(atom, value =\u003e {});\n\nset(atom, value);\n// calls:\nemit(atom, value, oldValue);\n// triggers:\non(atom, (value, oldValue) =\u003e {});\n```\n\nSide note: `use` calls `on` internally but does not look at the emitted value, so you can trigger `use(state, key)` either with `update(state, key)` or with `set(state, key, value)` or even with `emit(state, key)`.\n\n### Additional API / helper functions\n\n```js\nonce(state, key, listener);\n```\n\nLike `on`, but unsubscribes when triggered the first time.\n\n```js\nawait next(state, key);\n```\n\nPromise that resolves on the next `emit` (= promisified `once`).\n\n```js\nis(state, key, value);\nis(state, {key: value});\nis(atom, value);\n```\n\n\"Declarative\" version of `set` which does nothing if the value did not change.\n\n### Object-oriented API\n\nThe main cost of the functional approach is that consumers of a `state` object have to import all the functions. This is a burden if you want your state to be encapsulated – e.g. you use `minimal-state` in a library/package and want to expose a `state` object to the outside to emit change events. Requiring your package consumers to import an additional peer dependency would be awkward.\n\nThis is where OO shines, and why we also provide an OO version with the core functions as _methods_ on your `state` object:\n\n```js\nconst state = State({count: 0}); // call State() to add methods to state\nstate.set('count', 1);\n\n// consumers don't need our library now:\nexport {state};\n```\n\nFull OO API:\n\n```js\nimport State, {pure} from 'use-minimal-state';\n\n// create state instance (= shallow copy of initialState plus methods)\nlet state = State(initialState);\n\nstate.set(key, value); // set(state, key, value)\nstate.update(key); // update(state, key)\nstate.on(key, listener); // on(state, key, listener), returns unsubscribe\nstate.emit(key, ...args); // emit(state, key, ...args)\nstate.clear(); // clear(state)\n\n// this is only available if State is imported from use-minimal-state\nstate.use(key); // use(state, key)\n\n// get back a snapshot of the state without methods\npure(state);\n\n// Example:\nlet state = State({count: 0});\nstate.set('count', 1);\nJSON.stringify(pure(state)); // \"{\\\"count\\\": 1}\"\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmitschabaude%2Fminimal-state","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmitschabaude%2Fminimal-state","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmitschabaude%2Fminimal-state/lists"}