{"id":13683720,"url":"https://github.com/deamme/laco","last_synced_at":"2025-07-20T07:33:22.435Z","repository":{"id":88302176,"uuid":"121621589","full_name":"deamme/laco","owner":"deamme","description":"⚡️Ultra lightweight state management for React and Inferno","archived":false,"fork":false,"pushed_at":"2019-01-24T19:51:16.000Z","size":93,"stargazers_count":209,"open_issues_count":2,"forks_count":10,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-06-29T06:04:02.450Z","etag":null,"topics":["inferno","react","state-management"],"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/deamme.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}},"created_at":"2018-02-15T11:24:21.000Z","updated_at":"2025-03-25T01:25:46.000Z","dependencies_parsed_at":null,"dependency_job_id":"418da23c-be04-4426-8c8c-208113a44f84","html_url":"https://github.com/deamme/laco","commit_stats":{"total_commits":117,"total_committers":2,"mean_commits":58.5,"dds":0.06837606837606836,"last_synced_commit":"08d022f8906152abd5fb559a3acd700c4b79ef47"},"previous_names":[],"tags_count":24,"template":false,"template_full_name":null,"purl":"pkg:github/deamme/laco","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/deamme%2Flaco","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/deamme%2Flaco/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/deamme%2Flaco/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/deamme%2Flaco/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/deamme","download_url":"https://codeload.github.com/deamme/laco/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/deamme%2Flaco/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":266085632,"owners_count":23874477,"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":["inferno","react","state-management"],"created_at":"2024-08-02T13:02:26.183Z","updated_at":"2025-07-20T07:33:22.389Z","avatar_url":"https://github.com/deamme.png","language":"TypeScript","readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://i.imgur.com/7RgJ5nV.png\" width=\"80%\"\u003e\n\u003c/p\u003e\n\n# Laco\n\n[![npm version](https://badge.fury.io/js/laco.svg)](https://badge.fury.io/js/laco)\n[![travis](https://travis-ci.org/deamme/laco.svg?branch=master)](https://travis-ci.org/deamme/laco)\n\nVery simple and powerful state management solution for React and Inferno.\n\n(Inferno doesn't have hooks support but you can use the [Subscribe component](https://github.com/deamme/laco#subscribe-).)\n\nSet up your stores and subscribe to them. Easy as that!\n\n[Check out the introductory blog post](https://medium.com/@Deam/laco-intro-5db2077ec829).\n\n`npm install laco`\n\n`npm install laco-react` or `npm install laco-inferno`\n\n## Summary\n\n- :rocket: Simple to use\n- :tada: Lightweight (under 2kb minified)\n- :sparkles: Partial [Redux DevTools Extension](https://github.com/zalmoxisus/redux-devtools-extension) support (time travel)\n\n## Example\n\n```jsx\nimport { Store } from 'laco'\nimport { useStore } from 'laco-react'\n\n// Creating a new store with an initial state { count: 0 }\nconst CounterStore = new Store({ count: 0 })\n\n// Implementing some actions to update the store\nconst increment = () =\u003e CounterStore.set(state =\u003e ({ count: state.count + 1 }))\nconst decrement = () =\u003e CounterStore.set(state =\u003e ({ count: state.count - 1 }))\n\nconst Counter = () =\u003e {\n  const state = useStore(CounterStore) // Takes a single store\n  return (\n    \u003cdiv\u003e\n      \u003cbutton onClick={decrement}\u003e-\u003c/button\u003e\n      \u003cspan\u003e{state.count}\u003c/span\u003e\n      \u003cbutton onClick={increment}\u003e+\u003c/button\u003e\n    \u003c/div\u003e\n  )\n}\n```\n\nFor more examples check the following code sandboxes below or the examples folder.\n\nCode sandboxes using hooks:\n- [Todo MVC](https://codesandbox.io/s/74yq01ovl1)\n- [Counter and toggle](https://codesandbox.io/s/jvly033v63)\n\nCode sandboxes using render props:\n- [Todo MVC](https://codesandbox.io/s/207504xx1y)\n- [Counter and toggle](https://codesandbox.io/s/6l7on1m473)\n\n## Redux DevTools Extension\n\nCheck out [Redux DevTools Extension](https://github.com/zalmoxisus/redux-devtools-extension).\n\n[Check out my post on dev.to about debugging etc.](https://dev.to/deamlabs/laco---debugging-actions-and-the-global-state-object-100a).\n\n### Time travel\n\nJust click on the stopwatch icon and you will get a slider which you can play with.\nThat's it! :)\n\n## React Native Debugger\n\nCheck out [React Native Debugger](https://github.com/jhen0409/react-native-debugger).\n\n### Time travel\n\nWorks as you would expect :)!\n\n## API\n\n### `Store(initialState: Object, name?: String)`\n\n```javascript\n// Initializing a new store with an initial state and a name:\nconst NewStore = Store({ count: 0 }, 'Counter')\n```\n\nThe name is optional and is used to get an overview of action and store relationship in Redux DevTools Extension. Action names for the Store will now show up as `Counter - ${actionType}` in DevTools Extension where as before only `${actionType}` was shown.\n\n### `Store.get()`\n\n```javascript\n// Getting the state of the store\nStore.get()\n```\n\nReturns an object which could be something like `{ count: 0 }` following the example.\n\n### `Store.set(state: Function, info?: String)`\n\n```javascript\n// Setting a new state and passing an optional action name \"increment\"\nStore.set(state =\u003e {\n  count: state.count + 1\n}, 'increment')\n```\n\n### `Store.replace(state: Function, info?: String)`\n\nImmutability is taking care of to a certain extent behind the scenes with the spread operator with `Store.set()` but you might want more control over the state. You can do this by using `Store.replace()` like so:\n\n```javascript\n// Setting a new state and passing an optional action name \"increment\"\nStore.replace(state =\u003e {\n  /* return modified state */\n}, 'increment')\n```\n\n### `Store.setCondition(condition: Function)`\n\n```javascript\n// Setting a condition to prevent count from going below 0 when `actionType` is `Decrement`\nCounterStore.setCondition((state, actionType) =\u003e {\n  if (state.count \u003c 0 \u0026\u0026 actionType === 'Decrement') {\n    // Returning a falsy value will prevent the state from changing\n    return false\n  }\n\n  // For every other `actionTypes` such as `SudoDecrement` will change the state\n  return state\n})\n```\n\nSetting a condition on a store will make every `Store.set()` call go through the condition first.\n\n### `Store.reset()`\n\n```javascript\n// Resets the store to initial state\nStore.reset()\n```\n\nA good practice when testing is to call `reset()` on a store before using the store in a test. This takes care of some edge cases that you might run into. The reason for this is that Laco is using a global object behind the scenes to store all of your stores states into one big object. Redux also operates on one global object which makes time travel possible.\n\n### `Store.dispatch(value: any, info: String)`\n\n```javascript\n// Dispatching an action that does not change the state of the store\nStore.dispatch(changeLocation(), 'Location change')\n```\n\nYou might want to dispatch an action that is associated with a certain store but don't want to change the state. The action will in this case be shown as `StoreName - Location change`.\n\n### `dispatch(value: any, info: String)`\n\n```javascript\nimport { dispatch } from 'laco'\n\n// Dispatching a global action that does not change any state\ndispatch(changeLocation(), 'Location change')\n```\n\nYou might want to dispatch a global action that is **NOT** associated with any store. The action will in this case just be shown as `Location change`.\n\n### `getGlobalState()`\n\n```javascript\nimport { getGlobalState } from 'laco'\n\ngetGlobalState()\n```\n\nReturns the global object that holds every state - mostly used for [rehydration](https://github.com/deamme/laco#Rehydration) when doing server-side rendering (SSR).\n\n### `resetGlobalState()`\n\n```javascript\nimport { resetGlobalState } from 'laco'\n\nresetGlobalState()\n```\n\nResets the global state to an empty object.\n\n### `replaceGlobalState()`\n\n```javascript\nimport { replaceGlobalState } from 'laco'\n\nconst newGlobalState = { 0: { test: true } }\n\nreplaceGlobalState(newGlobalState)\n```\n\nReplaces the global state completely - mostly used for [rehydration](https://github.com/deamme/laco#Rehydration) when doing server-side rendering (SSR).\n\n### `\u003cSubscribe /\u003e`\n\n#### Props\n\n- `to` - Array of stores you want to subscribe to\n\n```jsx\nimport { Subscribe } from 'laco-react' // or 'laco-inferno'\n\n\u003cSubscribe to={[CounterStore]}\u003e\n  {({ count }) =\u003e (\n    \u003cdiv\u003e\n      \u003cbutton onClick={decrement}\u003e-\u003c/button\u003e\n      \u003cspan\u003e{count}\u003c/span\u003e\n      \u003cbutton onClick={increment}\u003e+\u003c/button\u003e\n    \u003c/div\u003e\n  )}\n\u003c/Subscribe\u003e\n```\nThe `Subscribe` component is making use of the new render prop idea. Related articles:\n- [Apollo Query Component](https://dev-blog.apollodata.com/whats-next-for-react-apollo-4d41ba12c2cb)\n- [Use a render prop!](https://cdb.reacttraining.com/use-a-render-prop-50de598f11ce)\n\n### `useStore()`\n```jsx\nimport { Store } from 'laco'\nimport { useStore } from 'laco-react'\n\nconst CounterStore = new Store({ count: 0 })\n\nconst Counter = () =\u003e {\n  const state = useStore(CounterStore) // Takes a single store\n  return \u003cdiv\u003e{state.count}\u003c/div\u003e\n}\n```\n\n### `useStores()`\n```jsx\nimport { Store } from 'laco'\nimport { useStores } from 'laco-react'\n\nconst CounterStore = new Store({ count: 0 })\nconst AnotherStore = new Store({ test: \"hello\" })\n\nconst Counter = () =\u003e {\n                                    // Takes an array of stores\n  const [counterState, anotherState] = useStores([CounterStore, AnotherStore])\n  return \u003cdiv\u003e{anotherState.test + counterState.count}\u003c/div\u003e\n}\n```\n\n## Rehydration\n\nWhen doing server-side rendering (SSR) it's important to preserve the state from the server to the client.\n\nPlease follow [this](https://redux.js.org/recipes/serverrendering) Redux guide.\n\nOn the server: Instead of doing `store.getState()` you will just use `getGlobalState()`.\n\nOn the client: Instead of doing `createStore(counterApp, preloadedState)` you can do `replaceGlobalState(preloadedState)`\n\nKeep in mind that trying to do SSR rehydration can introduce JS injections if you don't do it right.\n\nThe Redux guide solves it by doing `JSON.stringify(preloadedState).replace(/\u003c/g, '\\\\u003c')`. For another solution look [here](https://github.com/deamme/laco/pull/2#issuecomment-417880218).\n\n## Testing\n\nTesting using [tape](https://github.com/substack/tape):\n\n```javascript\nimport * as test from 'tape'\nimport { CounterStore, increment, decrement } from './CounterStore'\n\ntest('counter', t =\u003e {\n  CounterStore.reset()\n  t.assert(CounterStore.get().count === 0)\n\n  increment()\n  t.assert(CounterStore.get().count === 1)\n\n  decrement()\n  t.assert(CounterStore.get().count === 0)\n\n  t.end()\n})\n```\n\n## Credits\n\nHeavily inspired by:\n\n- [Unstated](https://github.com/jamiebuilds/unstated)\n- [Redux](https://github.com/reactjs/redux)\n","funding_links":[],"categories":["TypeScript","List"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdeamme%2Flaco","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdeamme%2Flaco","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdeamme%2Flaco/lists"}