{"id":21998165,"url":"https://github.com/simmor-store/simmor","last_synced_at":"2025-04-30T21:08:20.777Z","repository":{"id":35127694,"uuid":"210175882","full_name":"simmor-store/simmor","owner":"simmor-store","description":"Simple immutable boilerplate-free state management","archived":false,"fork":false,"pushed_at":"2023-01-04T11:05:32.000Z","size":639,"stargazers_count":3,"open_issues_count":14,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-30T21:08:12.898Z","etag":null,"topics":["angular","immer","react","rxjs","store"],"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/simmor-store.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-09-22T16:08:20.000Z","updated_at":"2020-09-24T08:37:45.000Z","dependencies_parsed_at":"2023-01-15T14:23:01.400Z","dependency_job_id":null,"html_url":"https://github.com/simmor-store/simmor","commit_stats":null,"previous_names":[],"tags_count":14,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/simmor-store%2Fsimmor","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/simmor-store%2Fsimmor/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/simmor-store%2Fsimmor/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/simmor-store%2Fsimmor/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/simmor-store","download_url":"https://codeload.github.com/simmor-store/simmor/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251782774,"owners_count":21642987,"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":["angular","immer","react","rxjs","store"],"created_at":"2024-11-29T22:20:17.131Z","updated_at":"2025-04-30T21:08:20.754Z","avatar_url":"https://github.com/simmor-store.png","language":"TypeScript","readme":"Simmor is a simple immutable boilerplate-free framework-agnostic store with support of middlewares and state slicing.\n\n\n# Install\n`npm install simmor`\n\n# Examples\n\n* [React](https://github.com/simmor-store/react-simmor) \n* [Angular](https://github.com/simmor-store/angular-simmor-examples)\n\n# Online demo\n\n[https://codesandbox.io/s/github/simmor-store/react-simmor/tree/master/examples](https://codesandbox.io/s/github/simmor-store/react-simmor/tree/master/examples)\n\n# React examples\n\nThe simplest way to use simmor is by creating a localStore. \n\nHere an example of counter store that has state `{value: number}`.\n\nState can be modified throw `draft` field. Simmor uses [immer](https://github.com/immerjs/immer) that can update immutable state by mutating it.\n\n```ts\n  const [state, dispatch] = useLocalStore({value: 0}, ctx =\u003e ({\n    increase() {\n      ctx.draft.value += 1\n    },\n    decrease() {\n      const newValue = ctx.draft.value - 1\n      if (newValue \u003e= 0) {\n        ctx.draft.value = newValue\n      }\n    },\n    increaseWithDelay() {\n      setTimeout(() =\u003e this.increase(), 300)\n    },   \n    setValue(value: number) {\n      ctx.draft.value = value\n    }\n  }))\n\n```\n```ts\n  \u003cdiv className=\"counter\"\u003e\n    \u003cspan\u003e{state.value}\u003c/span\u003e\n    \u003cbutton onClick={() =\u003e dispatch.increase()}\u003e+\u003c/button\u003e\n    \u003cbutton onClick={() =\u003e dispatch.decrease()}\u003e-\u003c/button\u003e\n    \u003cbutton onClick={() =\u003e dispatch.setValue(0)}\u003ereset\u003c/button\u003e\n    \u003cbutton onClick={() =\u003e dispatch.increaseWithDelay()}\u003eIncrease with delay\u003c/button\u003e\n  \u003c/div\u003e\n\n```\n\n# Store class\nWe can define store as class.\n\n```ts\nexport type CounterState = { value: number }\n\nexport class CounterStore extends ReducerStore\u003cCounterState\u003e {\n\n  increase() {\n    this.draft.value += 1\n  }\n\n  decrease() {\n    const newValue = this.draft.value - 1\n    if (newValue \u003e= 0) {\n      this.draft.value = newValue\n    }\n  }\n\n  setValue(value: number) {\n    this.draft.value = value\n  }\n}\n\n```\n\n```ts\nexport const Counter = ({store}: { store: CounterStore }) =\u003e {\n  const value = useStore(store, x =\u003e x.value)\n  return (\n    \u003cdiv className=\"counter\"\u003e\u003cspan\u003e{value}\u003c/span\u003e\n      \u003cbutton onClick={() =\u003e store.increase()}\u003e+\u003c/button\u003e\n      \u003cbutton onClick={() =\u003e store.decrease()}\u003e-\u003c/button\u003e\n      \u003cbutton onClick={() =\u003e store.setValue(0)}\u003eReset\u003c/button\u003e\n    \u003c/div\u003e\n  )\n}\n\n```\n```ts\nconst store = new CounterStore({value: 0})\n\u003cCounter store={store}/\u003e\n```\n\n# Middleware\nSimmor supports middlewares. Here an example of middleware that saves state to localStorage.\n```ts\nexport function createLocalStorageMiddleware(key: string): Middleware {\n  return next =\u003e action =\u003e {\n    const newState = next(action)\n    if (action.methodName === \"constructor\") {\n      const savedState = localStorage.getItem(key)\n      if (savedState) {\n        return JSON.parse(savedState)\n      }\n    }\n    localStorage.setItem(key, JSON.stringify(newState))\n    return newState\n  }\n}\n\n```\nWe can pass middlewares in the constructor of the store and our component can now save its state between sessions.\n\n\n```ts\nconst persistentStore = new CounterStore({value: 0}, {\n    middlewares: [createLocalStorageMiddleware('counter')]\n})\n\n\u003cCounter store={persistentStore}/\u003e\n```\n\n# State slicing\nIt is possible to slice a part of the state.\nFor example if we need two counters and we want to swap values between them.\n\n```ts\n\ntype CounterPairState = {\n  left: CounterState\n  right: CounterState\n}\n\nexport class CounterPairStore extends ReducerStore\u003cCounterPairState\u003e {\n  leftStore = new CounterStore(this.slice('left'))\n  rightStore = new CounterStore(this.slice('right'))\n\n  constructor() {\n    super({left: {value: 100}, right: {value: 200}})\n  }\n\n  swap() {\n    const [leftValue, rightValue] = [this.state.left.value, this.state.right.value]\n    this.leftStore.setValue(rightValue)\n    this.rightStore.setValue(leftValue)\n  }\n\n  static sum(state: CounterPairState) {\n    return state.left.value + state.right.value\n  }\n}\n\n```\nAnd the component\n```ts\nconst store = new CounterPairStore()\nexport const CounterPair = () =\u003e {\n  const state = useStore(store, x =\u003e x)\n  const sum = CounterPairStore.sum(state)\n\n  return (\n    \u003cdiv className=\"pair\"\u003e\n      \u003cdiv\u003e\n        \u003cbutton onClick={() =\u003e store.swap()}\u003eswap\u003c/button\u003e\n        \u003cspan\u003eSum {sum}\u003c/span\u003e\n      \u003c/div\u003e\n      \u003cCounter store={store.leftStore}/\u003e\n      \u003cCounter store={store.rightStore}/\u003e\n    \u003c/div\u003e\n  )\n\n}\n```\n\n\n\n\n\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsimmor-store%2Fsimmor","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsimmor-store%2Fsimmor","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsimmor-store%2Fsimmor/lists"}