{"id":20806722,"url":"https://github.com/fed/stato","last_synced_at":"2026-04-29T03:02:26.573Z","repository":{"id":43627128,"uuid":"71687781","full_name":"fed/stato","owner":"fed","description":"Functional reactive state management library 🔥","archived":false,"fork":false,"pushed_at":"2017-12-31T20:03:49.000Z","size":77,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2026-03-30T09:11:34.615Z","etag":null,"topics":["architecture","bacon","data-flow","event-stream","frp","frp-library","reducer","state","state-management"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/stato","language":"JavaScript","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/fed.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":"2016-10-23T07:16:13.000Z","updated_at":"2023-12-12T16:56:33.000Z","dependencies_parsed_at":"2022-09-26T16:21:46.386Z","dependency_job_id":null,"html_url":"https://github.com/fed/stato","commit_stats":null,"previous_names":["fknussel/stato","fknussel/baconify"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/fed/stato","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fed%2Fstato","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fed%2Fstato/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fed%2Fstato/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fed%2Fstato/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/fed","download_url":"https://codeload.github.com/fed/stato/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fed%2Fstato/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32408446,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-29T02:37:21.628Z","status":"ssl_error","status_checked_at":"2026-04-29T02:36:50.947Z","response_time":110,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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":["architecture","bacon","data-flow","event-stream","frp","frp-library","reducer","state","state-management"],"created_at":"2024-11-17T19:24:54.064Z","updated_at":"2026-04-29T03:02:26.560Z","avatar_url":"https://github.com/fed.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# stato\n\n[![Travis](https://img.shields.io/travis/fknussel/stato.svg)](https://travis-ci.org/fknussel/stato)\n[![npm](https://img.shields.io/npm/v/stato.svg)](https://www.npmjs.com/package/stato)\n[![npm](https://img.shields.io/npm/l/stato.svg)](https://github.com/fknussel/stato/blob/master/LICENSE.md)\n[![npm](https://img.shields.io/npm/dm/stato.svg)](https://www.npmjs.com/package/stato)\n[![David](https://img.shields.io/david/fknussel/stato.svg)](https://github.com/fknussel/stato)\n\nSuper simple, opinionated functional reactive state management library powered by [Bacon.js](http://baconjs.github.io/) 🔥\n\n## Installation\n\n```\n# npm\nnpm install stato\n\n# yarn\nyarn add stato\n```\n\nThen just import the main `Store` class (aka: function constructor) which is the controlling bus you need to instantiate.\n\n```\nimport Store from 'stato';\n```\n\n## Peer dependencies\n\nThis library depends on `baconjs@^1.0.0`, so make sure you have Bacon.js installed and listed as a dependency on your project.\n\n## Development Tasks\n\n| Command | Description |\n|---------|-------------|\n| `yarn install` | Fetch dependencies and build binaries for any of the modules |\n| `yarn clean` | Remove `lib` directory |\n| `yarn build` | Build `lib/stato.js` file |\n| `yarn test` | Run test suite |\n\n## Quick Start Guide\n\n### 1) Define your **action types**\n\n```js\nexport const SHOW_SPINNER = 'SPINNER/SHOW';\n```\n\nI usually define all actions within a single file for convenience.\n\n### 2) Create your **reducers**\n\n**Reducers are pure functions** that derive the next application state for a particular action, based on the current state and the payload the action provides. The first parameter reducers take is always the current state for the app, whereas the rest of the arguments are whatever data your reducer needs and you pass on to them.\n\n**Reducers and action types have a 1:1 relationship.** You need to name your reducers after the action type they are bound to ⚠️ -- this is the only style convention this library has.\n\n```js\nexport default {\n  [SHOW_SPINNER]: state =\u003e (\n    ...state,\n    loading: true\n  ),\n\n  [HIDE_SPINNER]: state =\u003e (\n    ...state,\n    loading: false\n  )\n}\n```\n\nOf course reducers don't need to be inline functions, you can define them elsewhere and then bind them together in the format stato needs them to be in, something along the lines of this chunk of code... But this is totally up to you and depends on your preferred code style.\n\n```js\nexport default {\n  [SHOW_SPINNER]: showSpinnerReducer,\n  [HIDE_SPINNER]: hideSpinnerReducer\n}\n```\n\n### 3) Define your **initial state**\n\n```js\nconst initialState = {\n  loading: false\n};\n```\n\n### 4) Instantiate your store\n\nMake sure to pass in your reducer object plus the initial state for the application.\n\n```js\nconst store = new Store(reducers, initialState);\n```\n\n### 5) Initialise your application state\n\n```js\nstore.subscribe(props =\u003e {\n  ReactDOM.render(\n    \u003cApp {...props} /\u003e,\n    document.getElementById('root')\n  );\n});\n```\n\n## TL;DR: Usage Example\n\nHave a look at this [example](https://github.com/fknussel/stato-example).\n\n## Motivation and Proposed Architecture\n\nJust a lil bit of context first *re: functional reactive programming*. The most fundamental concept of [Functional Reactive Programming (FRP)](http://en.wikipedia.org/wiki/Functional_reactive_programming) is the **event stream**. Streams are like (immutable) arrays of events: they can be mapped, filtered, merged and combined. The difference between arrays and event streams is that values (events) of the event stream occur asynchronously. Every time an event occurs, it gets propagated through the stream and finally gets consumed by the subscriber.\n\nWe have [Flux](https://facebook.github.io/flux/) and other implementations such as [Redux](http://redux.js.org/) and [MobX](https://mobxjs.github.io/) to handle our app state, and in fact they do a great job abstracting our views from the *business logic* and keeping our **data flow unidirectional**. However, Reactive programming is what React was made for. So, what if we delegate the app state handling to FRP libraries like [Bacon.js](http://baconjs.github.io/) or [RxJS](http://reactivex.io/rxjs/)? Well, that actually makes a lot of sense:\n\n1. Actions happen eventually and they propagate through event streams.\n2. The combination of these event streams result in the app's state.\n3. After an event has propagated through the system, the new state is consumed by the subscriber and rendered by the **root level** React component.\n\nThis makes the data flow super simple:\n\n![Application Architecture](http://i.imgur.com/ButOsvf.png)\n\nThe fundamental idea behind this approach is that every user-triggered action gets pushed to the appropriate event stream, which is then merged in to the **application state** stream which is our single source of truth. Events take place at different points in time, and they cause the application state to change. Finally the updated state triggers a re-render of the root component, and React's virtual DOM takes care of the rest :tada: This results in **dead simple, dumb** React views, mostly powered by **[stateless functional components](https://facebook.github.io/react/docs/components-and-props.html#functional-and-class-components)** in favour of stateful class components (wherever possible).\n\n## Triggering actions and the role of the view layer\n\nState changes are only dispatched by actions. It's only throughout actions that we can update our application state.\n\nActions can be triggered:\n\n* as a response to some user interaction (click on a button, form submission, scroll past the end of the page, etc.)\n* as part of a lifecycle method (request data on componentWillMount). Note that I said \"request data\" and not \"fetch data\", that's because we cannot directly fetch data on a lifecycle hook as that's a side effect (talking to a server). We rather call a \"request\" data action that triggers the network call.\n* as a cron job: as apart of our bootstrapping action we could set a bunch of timers (intervals) to periodically sync us with the server or update the internal state.\n\nThe bottom line here is that the View Layer ONLY and EXCLUSIVELY interacts with actions by importing them:\n\n```\nimport {logout} from './state/actions';\n```\n\nAnd this is the only use case for importing files from the `state` directory.\n\nNote that some actions are never used from the UI, for instance `RECEIVE_DATA` gets only triggered by the side effect triggered by `REQUEST_DATA`. The view layer doesn't necessarily need to be concerned with the entirety of the actions.\n\n## About Side Effects\n\nSide effects allow your application to interact with the outside world, i.e.: fetching data from an API, getting/setting data from/to `localStorage`/`sessionStorage`, talking to a database, etc.\n\nUnlike reducers, effects are **not** pure functions.\n\nNaturally effects may (and most usually do) trigger actions to update the application state once they are done making asynchronous operations.\n\nFor instance, consider this effect called `getUserDetails` that fetches a list of users from an API. Provided the Ajax request completes successfully, the effect will trigger the `RECEIVE_USER_DETAILS` action which simply updates the application state with those user details. This allows for a separation of concerns between hitting an API and updating the app state.\n\n```js\nexport function getUserDetails() {\n  store.push(SHOW_SPINNER); // triggers an action\n\n  const ajaxCall = fetch('//api.github.com/users/fknussel')\n    .then((response) =\u003e response.json());\n\n  const userDetailsStream = Bacon\n    .fromPromise(ajaxCall)\n    .onValue((user) =\u003e {\n      store.push(GET_USER_DETAILS, user); // triggers an action\n      store.push(HIDE_SPINNER); // triggers an action\n    });\n}\n```\n\nNote that the user cannot directly trigger a side effect. Actions are the only ones allowed to do this.\n\nSide effects include:\n* API calls\n* Local storage caching\n* Logging, etc.\n\n```\nVIEW (React component) \u003c--------------------|\n  |                                         |\n  | triggers                                |\n  |                                         |\nACTION ---\u003e--causes---\u003e-- SIDE EFFECTS      |\n  |                                         |\n  | invokes, passing data forward           ^\n  |                                         |\nREDUCER                                     |\n  |                                         |\n  | computes next state and updates store   |\n  |                                         |\nSTORE ----\u003e----notifies root component --\u003e--|\n```\n\nBased on this, actions are not ALWAYS pure functions as they can trigger side effects. Reducers are, though.\n\n## Using stato with other view libraries\n\nstato is actually view-layer agnostic, so it can easily be used with any other UI library such as [Preact](https://preactjs.com/) or [Vue.js](https://vuejs.org/).\n\n## TO DO\n\n- [ ] Agree on how to set/define initial state: should it be passed in when creating the store? or should it be set on each particular reducer like Redux does with `combineReducers`? Is it the store the one that should be concerned with the initial state, or reducers ought to?\n- [ ] Find a way to \"connect\" children components to avoid cascading props all the way down. Probably follow the Redux way via `Provider` and `connect`.\n\n## Complementary Readings, Inspiration and Credits\n\nI've first used a somewhat similar architecture while at [Fox Sports Australia](https://github.com/FoxSportsAustralia/) and it made perfect sense. This was probably before or at the same time [Redux](http://redux.js.org/) and [MobX](https://mobxjs.github.io/) became popular.\n\n[Matti Lankinen](https://github.com/milankinen) proposes the same idea on his [article on Medium](https://medium.com/@milankinen/good-bye-flux-welcome-bacon-rx-23c71abfb1a7). I've made tweaks and enhancements to this library after some of his comments and ideas.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffed%2Fstato","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffed%2Fstato","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffed%2Fstato/lists"}