{"id":28098054,"url":"https://github.com/seracio/xstream-connect","last_synced_at":"2025-05-13T17:49:05.636Z","repository":{"id":57139994,"uuid":"80356610","full_name":"seracio/xstream-connect","owner":"seracio","description":"higher order component to plug xstream as a store with React components","archived":false,"fork":false,"pushed_at":"2018-03-29T11:57:12.000Z","size":196,"stargazers_count":4,"open_issues_count":6,"forks_count":2,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-05-09T10:39:25.940Z","etag":null,"topics":["react","streams","xstream"],"latest_commit_sha":null,"homepage":"","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/seracio.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":"2017-01-29T16:22:47.000Z","updated_at":"2020-08-05T16:56:09.000Z","dependencies_parsed_at":"2022-08-23T01:50:09.276Z","dependency_job_id":null,"html_url":"https://github.com/seracio/xstream-connect","commit_stats":null,"previous_names":[],"tags_count":17,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/seracio%2Fxstream-connect","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/seracio%2Fxstream-connect/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/seracio%2Fxstream-connect/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/seracio%2Fxstream-connect/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/seracio","download_url":"https://codeload.github.com/seracio/xstream-connect/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253997251,"owners_count":21996828,"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":["react","streams","xstream"],"created_at":"2025-05-13T17:49:05.124Z","updated_at":"2025-05-13T17:49:05.630Z","avatar_url":"https://github.com/seracio.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# xstream-connect\n\nhigher order component to plug [xstream](https://github.com/staltz/xstream) as a store with React components\n\n[![Build Status](https://travis-ci.org/seracio/xstream-connect.svg?branch=master)](https://travis-ci.org/seracio/xstream-connect)\n\n## Install\n\n```bash\nyarn add react react-dom prop-types xstream @seracio/xstream-connect\n```\n\n## Disclaimer\n\nThe purpose here is not to provide an async middleware to a redux store with Streams, \nas [redux-cycle-middleware](https://github.com/cyclejs-community/redux-cycle-middleware) \nand [redux-observable](https://github.com/redux-observable/redux-observable) do \nbut to replace *redux* and its different slices (*async middlewares*, *reducers* and *derived data*) with *MemoryStreams*.\nAs this, we can express each variable of the store as a function of other variables, in a clean and async way.\n\n[xstream](https://github.com/staltz/xstream)'s Streams are the perfect tool to achieve this goal, as they are hot\nand can easily be transformed into *MemoryStreams* (via the *remember* method).\n[xstream](https://github.com/staltz/xstream) is also light and fast, with a simple and comprehensive API compared to other FRP libs.\n\nThis library only exposes a component *Provider* and an higher order function *connect* to connect your store to \nthe React layer in a *react-redux* fashion.  \n\n## Basic example\n\n```javascript\nimport React from 'react';\nimport ReactDOM from 'react-dom';\nimport xs from 'xstream';\nimport {connect, Provider} from '@seracio/xstream-connect';\n\n// A store is just a dictionary of exposed Streams\nconst store = {\n  count$: xs.periodic(1000).startWith(0)\n};\n\nconst App = ({count}) =\u003e { \n  return \u003cdiv\u003e{this.props.count}\u003c/div\u003e;\n};\n\n// the combinator defines which part of your store \n// will be exposed and realised the mapping from Streams to props\nconst combinator = state =\u003e {\n  const {count$, hello$} = state;\n  return xs.combine(count$).map(count =\u003e ({count}));\n};\n\n// We use a Higher order function connect to wrap our component and plug its props to the store values\nconst ConnectedApp = connect(combinator)(App);\n\nReactDOM.render(\n  \u003cProvider store={store}\u003e\n    \u003cConnectedApp /\u003e\n  \u003c/Provider\u003e,\n  document.querySelector('#root')\n);\n```\n\n## Principles\n\n### Store and the Provider component\n\nWithin this architecture, all the logic resides in *Streams*.  \nThe store props of the Provide component is just a hash/dictionary of *Streams* (or static values) you want to expose to the React layer.  \n   \n```javascript\n// main.js\nimport React from 'react';\nimport ReactDOM from 'react-dom';\nimport {Provider} from '@seracio/xstream-connect';\nimport * as store from './store'; // \u003c---- your store is just a dictionnary of exposed Streams\n\nReactDOM.render(\n  \u003cProvider store={store}\u003e {/* \u003c-- plugged your store to the React layer */}\n    \u003cConnected /\u003e\n  \u003c/Provider\u003e,\n  document.querySelector('#root')\n);\n\n```\n\n### The connect function\n\nThe `connect` function takes a single function as param.\nThis function, called the `combinator`, expressed two things: \n\n* what parts of the store our component will subscribe to\n* and when will it receive props updates \n\nTo put it another way, the `combinator` receives the Provider's store as param and will return **a unique Stream** that combine the parts of the store we want our component to be aware of.\nThe value of this Stream will be **a plain object** {key =\u003e value} (as React's components are expected props).\n\nFor instance: \n\n```javascript\nconst combinator = state =\u003e {\n  const {list$, selected$} = state;\n  return xs\n    .combine(list$, selected$)\n    .map([list, selected] =\u003e ({list, selected}));\n}\n```\n\nor if you only want props to update when selected$ changes, you can use a [`sampleCombine`](https://github.com/staltz/xstream/blob/master/EXTRA_DOCS.md#-samplecombinestreams)\n\n```javascript\nconst combinator = state =\u003e {\n  const {list$, selected$} = state;\n  return selected$\n    .compose(sampleCombine(list$))\n    .map([selected, list] =\u003e ({selected, list}));\n}\n```\n\nor if you only want props to update when selected$'s id changes\n\n```javascript\nconst combinator = state =\u003e {\n  const {list$, selected$} = state;\n  return selected$\n    .compose(dropRepeats(isIdEqual))\n    .compose(sampleCombine(list$))\n    .map([selected, list] =\u003e ({selected, list}));\n}\n```\n\n### Waiting component\n\nBe aware that the HOC will wait for the first value of the combinator's Stream before rendering its component. \nIts render method will return `null` before or a default component if defined.\n\nYou can specify a Waiting component as this:\n\n```javascript\nconnect(combinator)(MyComponent, WaitingComponent);\n```\n\nThe waiting component will receive all props of MyComponent that are not provided by the combinator.\n\nOf course, you can also compose the combinator's Stream with a `startWith` operator:\n\n```javascript\nconst combinatr = state =\u003e {\n  const {myStream$} = state;\n  return myStream$\n    .startWith(null)\n    .map(data =\u003e ({data}));\n}\n```\n\n## Common patterns and caveats\n\n### How to dispatch actions from the React layer to the store?\n\nThere is no canonical way to achieve this. \nFor instance, you can do as this:\n* in your store, expose a *dispatcher$* Stream of Stream, as this the React layer will have a Stream to send actions through.\n* in your store, create an *actions$* Stream that flatten *dispatcher$*    \n\n```javascript\n// store index.js\nimport _ from 'lodash/fp';\nimport xs from 'xstream';\n\n// Our dispatcher is exposed \nexport const dispatcher$ = xs.of(xs.create());\n// actions are not\nconst actions$ = dispatcher$.flatten();\n\n// little helper\n// to check if an action is of a certain type\nconst isType = type =\u003e _.flow(_.get('type'), _.isEqual(type));\n\n// an exposed Stream that depends on an action\nexport const counter$ = actions$\n  .filter(isType('increment'))\n  .fold(acc =\u003e acc + 1, 0)\n  .startWith(0)\n  .remember(); \n// note that startWith() already returns a MemoryStream, remember() is not needed here \n```\n\n* expose your store into the React layer context \n\n```javascript\n// main.js\nimport {Provider} from '@seracio/xstream-connect';\nimport * as store from './store'; // dispatcher$ is exposed \n\n// ...\n```\n\n* via the *connect* method to *actionsProvider$*, retrieve a Stream into your component's props:\n\n```javascript\n// components/MyComponent.js\nimport xs from 'xstream';\nimport sampleCombine from 'xstream/extra/sampleCombine';\nimport {connect} from '@seracio/xstream-connect';\n\nconst MyComponent = ({counter, dispatcher}) =\u003e {\n  // here, the dispatcher's value is a Stream, so we can, shamefully, send actions through it\n  const increment = () =\u003e dispatcher.shamefullySendNext({type: 'increment'}); \n  return (\n    \u003cdiv onClick={increment}\u003e{counter}\u003c/div\u003e;\n  );\n};\n\n// create a combinator function\nconst combinator = (state) =\u003e state.counter$\n  .compose(sampleCombine(state.dispatcher$))\n  .map(([counter, dispatcher]) =\u003e ({counter, dispatcher}));\n\n// plug the component with the store\nexport default connect(combinator)(MyComponent);\n```\n\n### How to manage Promises Streams and other Streams that complete?\n\n`fromPromise` Streams can be tricky as they will complete as soon as their inner Promise is resolved:\n\n```javascript\nimport {getAsyncData} from '../services';\n\nconst data$ = xs.fromPromise(getAsyncData()).remember();\n```\n\nThis is problematic with complex async flow, especially if you use `sampleCombine` or `combine` operators.\nI advise you to transform `fromPromise` Streams as this:\n\n```javascript\nconst data$ = xs\n  .merge(\n    xs.create(), \n    xs.fromPromise(getAsyncData())\n  ).remember();\n```\n\nWe can generalize this rule with all `completable` Streams.\n\n## Why don't you use [cycle.js](https://cycle.js.org)?\n\n[cycle.js](https://cycle.js.org) is neat, but we kinda like the React ecosystem... \nand are also tired of the growing complexity of the redux layer when you want to manage asynchronicity.\n\n## License\n\nMIT License\n\nCopyright (c) 2017 serac.io \n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fseracio%2Fxstream-connect","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fseracio%2Fxstream-connect","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fseracio%2Fxstream-connect/lists"}