{"id":13495363,"url":"https://github.com/bcherny/undux","last_synced_at":"2025-05-15T03:08:01.903Z","repository":{"id":26354147,"uuid":"108475177","full_name":"bcherny/undux","owner":"bcherny","description":"⚡️ Dead simple state for React. Now with Hooks support.","archived":false,"fork":false,"pushed_at":"2025-05-04T23:07:31.000Z","size":1584,"stargazers_count":1496,"open_issues_count":19,"forks_count":30,"subscribers_count":27,"default_branch":"master","last_synced_at":"2025-05-05T23:05:30.997Z","etag":null,"topics":["flux","react","redux","typesafe","typescript"],"latest_commit_sha":null,"homepage":"https://undux.org","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/bcherny.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.md","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":"2017-10-26T23:11:37.000Z","updated_at":"2025-04-21T07:55:29.000Z","dependencies_parsed_at":"2023-12-13T15:04:09.717Z","dependency_job_id":"198ce0b7-e801-4d4f-b503-f24dec25da27","html_url":"https://github.com/bcherny/undux","commit_stats":{"total_commits":261,"total_committers":8,"mean_commits":32.625,"dds":"0.13793103448275867","last_synced_commit":"687a4f0f0d3326356460285e29d6b706e95b571d"},"previous_names":["bcherny/babydux"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bcherny%2Fundux","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bcherny%2Fundux/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bcherny%2Fundux/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bcherny%2Fundux/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bcherny","download_url":"https://codeload.github.com/bcherny/undux/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254264769,"owners_count":22041794,"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":["flux","react","redux","typesafe","typescript"],"created_at":"2024-07-31T19:01:33.946Z","updated_at":"2025-05-15T03:07:56.876Z","avatar_url":"https://github.com/bcherny.png","language":"TypeScript","funding_links":[],"categories":["react","TypeScript","List"],"sub_categories":[],"readme":"\u003cimg src=\"logo.png\" width=\"362\" alt=\"undux\" /\u003e\n\n[![Build Status][build]](https://github.com/bcherny/undux/actions?query=branch%3Amaster+workflow%3ACI) [![npm]](https://www.npmjs.com/package/undux) [![mit]](https://opensource.org/licenses/MIT) [![ts]](https://www.typescriptlang.org/) [![flow]](https://flow.org/)\n\n[build]: https://img.shields.io/github/actions/workflow/status/bcherny/undux/ci.yml?style=flat-square\n[npm]: https://img.shields.io/npm/v/undux.svg?style=flat-square\n[mit]: https://img.shields.io/npm/l/undux.svg?style=flat-square\n[ts]: https://img.shields.io/badge/TypeScript-%E2%9C%93-007ACC.svg?style=flat-square\n[flow]: https://img.shields.io/badge/Flow-%E2%9C%93-007ACC.svg?style=flat-square\n\n\u003e Dead simple state management for React\n\n---\n\n## 📖 Official docs: https://undux.org\n\n---\n\n## Install\n\n```sh\n# Using Yarn:\nyarn add undux\n\n# Or, using NPM:\nnpm install undux --save\n```\n\n## Install (with RxJS v4-)\n\n```sh\n# Using Yarn:\nyarn add undux@^3\n\n# Or, using NPM:\nnpm install undux@^3 --save\n```\n\n## Design Goals\n\n1. Complete type-safety, no exceptions\n2. Super easy to use: forget actions, reducers, dispatchers, containers, etc.\n3. Familiar abstractions: just `get` and `set`\n\n[Read more here](https://github.com/bcherny/undux#design-philosophy)\n\n## Use\n\n### 1. Create a store\n\n```jsx\nimport { createConnectedStore } from 'undux'\n\n// Create a store with an initial value.\nexport default createConnectedStore({\n  one: 0,\n  two: 0,\n})\n```\n\n_Be sure to define a key for each value in your model, even if the value is initially `undefined`._\n\n### 2. Connect your React components\n\n#### With [React Hooks](https://reactjs.org/docs/hooks-intro.html): `useStore`\n\n```jsx\nimport { useStore } from './MyStore'\n\n// Re-render the component when the store updates.\nfunction MyComponent() {\n  const store = useStore()\n  return (\n    \u003c\u003e\n      \u003cNumberInput onChange={store.set('one')} value={store.get('one')} /\u003e\n      \u003cNumberInput onChange={store.set('two')} value={store.get('two')} /\u003e\n      Sum: {store.get('one') + store.get('two')}\n    \u003c/\u003e\n  )\n}\n\nfunction NumberInput({ onChange, value }) {\n  return (\n    \u003cinput\n      onChange={(e) =\u003e onChange(parseInt(e.target.value, 10))}\n      type=\"number\"\n      value={value}\n    /\u003e\n  )\n}\n\nexport default MyComponent\n```\n\n#### Without React Hooks: `withStore`\n\n```jsx\nimport { withStore } from './MyStore'\n\n// Re-render the component when the store updates.\nfunction MyComponent({ store }) {\n  return (\n    \u003c\u003e\n      \u003cNumberInput onChange={store.set('one')} value={store.get('one')} /\u003e\n      \u003cNumberInput onChange={store.set('two')} value={store.get('two')} /\u003e\n      Sum: {store.get('one') + store.get('two')}\n    \u003c/\u003e\n  )\n}\n\nfunction NumberInput({ onChange, value }) {\n  return (\n    \u003cinput\n      onChange={(e) =\u003e onChange(parseInt(e.target.value, 10))}\n      type=\"number\"\n      value={value}\n    /\u003e\n  )\n}\n\nexport default withStore(MyComponent)\n```\n\n### 3. Put your app in an Undux Container\n\n```jsx\nimport MyComponent from './MyComponent'\nimport { Container } from './MyStore'\n\nfunction MyApp() {\n  return (\n    \u003cContainer\u003e\n      \u003cMyComponent /\u003e\n    \u003c/Container\u003e\n  )\n}\n\nexport default MyApp\n```\n\n**That's all there is to it.**\n\n[Open this code in playground](https://stackblitz.com/edit/undux-readme-demo-js).\n\n## Features\n\n### Effects\n\nThough Undux automatically re-renders your connected React components for you when the store updates, it also lets you subscribe to changes to specific fields on your store. Undux subscriptions are full [Rx observables](http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html), so you have fine control over how you react to a change:\n\n```js\nimport { debounce, filter } from 'rxjs/operators'\n\nstore\n  .on('today')\n  .pipe(\n    filter((date) =\u003e date.getTime() % 2 === 0), // Only even timestamps.\n    debounce(100), // Fire at most once every 100ms.\n  )\n  .subscribe((date) =\u003e console.log('Date changed to', date))\n```\n\nYou can even use Effects to trigger a change in response to an update:\n\n```js\nstore\n  .on('today')\n  .pipe(debounce(100))\n  .subscribe(async (date) =\u003e {\n    const users = await api.get({ since: date })\n    store.set('users')(users)\n  })\n```\n\nIn order to keep its footprint small, Undux does not come with RxJS out of the box. However, Undux does come with a minimal implementation of parts of RxJS, which interoperates with RxJS operators. To use RxJS operators, you'll need to install RxJS first:\n\n```sh\nnpm install rxjs --save\n```\n\n### Partial application\n\nPartially apply the `set` function to yield a convenient setter:\n\n```tsx\nconst setUsers = store.set('users')\nsetUsers(['amy'])\nsetUsers(['amy', 'bob'])\n```\n\n### Built-in logger\n\nUndux works out of the box with the Redux Devtools browser extension (download: [Chrome](https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd), [Firefox](https://addons.mozilla.org/firefox/addon/remotedev/), [React Native](https://github.com/zalmoxisus/remote-redux-devtools)). To enable it, just wrap your store with the Redux Devtools plugin:\n\n```js\nimport { createConnectedStore, withReduxDevtools } from 'undux'\n\nconst store = createConnectedStore(initialState, withReduxDevtools)\n```\n\nRedux Devtools has an inspector, a time travel debugger, and jump-to-state built in. All of these features are enabled for Undux as well. It looks like this:\n\n\u003cimg src=\"redux-logger.png\" width=\"895\" /\u003e\n\nAlternatively, Undux has a simple, console-based debugger built in. Just create your store with `withLogger` higher order store, and all model updates (which key was updated, previous value, and new value) will be logged to the console.\n\nTo enable the logger, simply import `withLogger` and wrap your store with it:\n\n```ts\nimport { createConnectedStore, withLogger } from 'undux'\n\nlet store = createConnectedStore(initialState, withLogger)\n```\n\nThe logger will produce logs that look like this:\n\n\u003cimg src=\"logger.png\" width=\"895\" /\u003e\n\n### Effects\n\nUndux is easy to modify with effects. Just define a function that takes a store as an argument, adding listeners along the way. For generic plugins that work across different stores, use the `.onAll` method to listen on all changes on a store:\n\n```js\n// MyStore.ts (if using TypeScript)\nimport { Effects } from 'undux'\n\ntype State = {\n  // ...\n}\n\nexport type StoreEffects = Effects\u003cState\u003e\n\n// MyEffects.ts\nimport { StoreEffects } from './MyStore'\n\nconst withLocalStorage: StoreEffects = (store) =\u003e {\n  // Listen on all changes to the store.\n  store\n    .onAll()\n    .subscribe(({ key, value, previousValue }) =\u003e\n      console.log(key, 'changed from', previousValue, 'to', value),\n    )\n}\n```\n\n## Recipes\n\n### Creating a store (TypeScript)\n\n```ts\n// MyStore.ts\nimport { createConnectedStore, type Effects, type Store } from 'undux'\n\ntype State = {\n  foo: number\n  bar: string[]\n}\n\nconst initialState: State = {\n  foo: 12,\n  bar: [],\n}\n\nexport default createConnectedStore(initialState)\n\n// If using effects..\nexport type StoreEffects = Effects\u003cState\u003e\n\n// If using class components..\nexport type StoreProps = {\n  store: Store\u003cState\u003e\n}\n```\n\n[See full example (in JavaScript, TypeScript, or Flow) here](https://undux.org/#examples/basic-usage).\n\n### Function component (TypeScript)\n\n```tsx\n// MyComponent.ts\nimport { useStore, type StoreProps } from './MyStore'\n\ntype Props = {\n  foo: number\n}\n\nfunction MyComponent({ foo }: Props) {\n  const { store } = useStore()\n  return (\n    \u003c\u003e\n      Today is {store.get('today')}\n      Foo is {foo}\n    \u003c/\u003e\n  )\n}\n\nexport default MyComponent\n\n// App.ts\nimport { Container } from './MyStore'\n\nfunction App() {\n  return (\n    \u003cContainer\u003e\n      \u003cMyComponent foo={3} /\u003e\n    \u003c/Container\u003e\n  )\n}\n\nexport default App\n```\n\n[See full example (in JavaScript, TypeScript, or Flow) here](https://undux.org/#examples/stateless-component-with-props).\n\n### Class component (TypeScript)\n\nUndux is as easy to use with class components as with function components.\n\n```tsx\n// MyComponent.ts\nimport { withStore, type StoreProps } from './MyStore'\n\ntype Props = StoreProps \u0026 {\n  foo: number\n}\n\nclass MyComponent extends React.Component\u003cProps\u003e {\n  render() {\n    return (\n      \u003c\u003e\n        Today is {this.props.store.get('today')}\n        Foo is {this.props.foo}\n      \u003c/\u003e\n    )\n  }\n}\n\nexport default withStore(MyComponent)\n\n// App.ts\nimport { Container } from './MyStore'\n\nfunction App() {\n  return (\n    \u003cContainer\u003e\n      \u003cMyComponent foo={3} /\u003e\n    \u003c/Container\u003e\n  )\n}\n\nexport default App\n```\n\n[See full example (in JavaScript, TypeScript, or Flow) here](https://undux.org/#examples/class-component-with-props).\n\n### Undux + Hot module reloading\n\nSee a full example [here](https://github.com/bcherny/undux-hot-module-reloading-demo).\n\n### Undux + TodoMVC\n\nSee the Undux TodoMVC example [here](https://github.com/bcherny/undux-todomvc).\n\n## Design philosophy\n\n**Goal #1 is total type-safety.**\n\nGetting, setting, reading, and listening on model updates is 100% type-safe: use a key that isn't defined in your model or set a key to the wrong type, and you'll get a compile-time error. And connected components and Effects are just as type-safe.\n\n**Goal #2 is letting you write as little boilerplate as possible.**\n\nDefine your model in a single place, and use it anywhere safely. No need to define tedious boilerplate for each field on your model. Container components and action creators are optional - most of the time you don't need them, and can introduce them only where needed as your application grows.\n\n**Goal #3 is familiar abstractions.**\n\nNo need to learn about Actions, Reducers, or any of that. Just call `get` and `set`, and everything works just as you expect.\n\n## Tests\n\n```sh\nnpm test\n```\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbcherny%2Fundux","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbcherny%2Fundux","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbcherny%2Fundux/lists"}