{"id":13725983,"url":"https://github.com/johnlindquist/react-streams","last_synced_at":"2025-04-09T07:10:05.654Z","repository":{"id":57345822,"uuid":"128251892","full_name":"johnlindquist/react-streams","owner":"johnlindquist","description":null,"archived":false,"fork":false,"pushed_at":"2018-08-15T19:06:39.000Z","size":1233,"stargazers_count":233,"open_issues_count":5,"forks_count":17,"subscribers_count":12,"default_branch":"master","last_synced_at":"2025-04-02T05:08:02.453Z","etag":null,"topics":["react","rxjs"],"latest_commit_sha":null,"homepage":"http://react-streams.com","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/johnlindquist.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-04-05T19:05:00.000Z","updated_at":"2025-03-23T00:53:14.000Z","dependencies_parsed_at":"2022-09-17T22:21:51.459Z","dependency_job_id":null,"html_url":"https://github.com/johnlindquist/react-streams","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/johnlindquist%2Freact-streams","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/johnlindquist%2Freact-streams/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/johnlindquist%2Freact-streams/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/johnlindquist%2Freact-streams/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/johnlindquist","download_url":"https://codeload.github.com/johnlindquist/react-streams/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247994122,"owners_count":21030050,"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","rxjs"],"created_at":"2024-08-03T01:02:45.602Z","updated_at":"2025-04-09T07:10:05.634Z","avatar_url":"https://github.com/johnlindquist.png","language":"JavaScript","readme":"# react-streams\n\n\u003cp align=\"center\"\u003e\n\n\u003cimg src=\"https://rawgit.com/johnlindquist/react-streams/master/logo.svg\" alt=\"react-streams logo\" width=\"300px;\"/\u003e\n\u003c/p\u003e\n\n## Installation\n\nInstall both `react-streams` and `rxjs`\n\n```bash\nnpm i react-streams rxjs\n```\n\n## Build Status\n\n[![CircleCI](https://circleci.com/gh/johnlindquist/react-streams.svg?style=svg)](https://circleci.com/gh/johnlindquist/react-streams)\n\n[Cypress Dashboard](https://dashboard.cypress.io/#/projects/ar6axg/)\n\n## About\n\n`react-streams` enables you to stream from a source or props. The stream will pass through a `pipe` and new values will often be pushed through by `plans`.\n\n### Stream from sources\n\n**_`\u003cStream source={}/\u003e`_** - A component that subscribes to a `source` and streams values to children. The stream will pass through a `pipe`.\n\n\u003e ```js\n\u003e \u003cStream source={source$}\u003e\n\u003e   {values =\u003e \u003cdiv\u003e{values.message}\u003c/div\u003e}\n\u003e \u003c/Stream\u003e\n\u003e ```\n\n**_`stream(source)`_** - Creates a named component that subscribes to a `source` and streams values to children. The stream will pass through a `pipe`.\n\n\u003e ```js\n\u003e const MyStreamingComponent = stream(source$)\n\u003e\n\u003e \u003cMyStreamingComponent\u003e\n\u003e   {(values)=\u003e \u003cdiv\u003e{values.message}\u003c/div\u003e}\n\u003e \u003c/MyStreamingComponent\u003e\n\u003e ```\n\n### Stream from props\n\n**_`\u003cStreamProps/\u003e`_** - A component that streams props changes to children. Changes to props will pass through the `pipe` and can be updated by `plans`.\n\n\u003e ```js\n\u003e \u003cStreamProps message={message}\u003e\n\u003e   {values =\u003e \u003cdiv\u003e{values.message}\u003c/div\u003e}\n\u003e \u003c/StreamProps\u003e\n\u003e ```\n\n**_`streamProps()`_** - Create a named component that streams props changes to children. Changes to props will pass through the `pipe` and can be updated by `plans`.\n\n\u003e ```js\n\u003e const MyStreamingPropsComponent = streamProps()\n\u003e\n\u003e \u003cMyStreamingComponent message={message}\u003e\n\u003e   {(values)=\u003e \u003cdiv\u003e{values.message}\u003c/div\u003e}\n\u003e \u003c/MyStreamingComponent\u003e\n\u003e ```\n\n### Stream through `pipe`\n\n**_`pipe`_** is any operator (or `piped` combination of operators) that you want to act on your stream. Pipes can be simple mappings or complex ajax requests with timing as long as they return a function that returns an object which matches the `children`'s arguments.\n\n\u003e ```js\n\u003e \u003cStreamProps\n\u003e   message={message}\n\u003e   pipe={map(({ message }) =\u003e message + \"!\")}\n\u003e \u003e\n\u003e   {values =\u003e \u003cdiv\u003e{values.message}\u003c/div\u003e}\n\u003e \u003c/StreamProps\u003e\n\u003e ```\n\n### Make a `plan` to update\n\n**_`plan`_** is a function that can be observed.\n\n\u003e ```js\n\u003e const update = plan()\n\u003e\n\u003e from(update).subscribe(value =\u003e console.log(value))\n\u003e\n\u003e update(\"Hello\") //logs \"Hello\"\n\u003e update(\"Friends\") //logs \"Friends\"\n\u003e ```\n\n## Examples\n\nEnough chit-chat, time for examples!\n\nPlay with Examples at [codesandbox.io](https://codesandbox.io/s/github/johnlindquist/react-streams/tree/master/examples?module=/stream/index.js)\n\n### `\u003cStream/\u003e`\n\n[Demo here](https://codesandbox.io/s/github/johnlindquist/react-streams/tree/master/examples?module=generic/index.js)\n\n```js\nimport React from \"react\"\nimport { Stream } from \"react-streams\"\nimport { of, pipe } from \"rxjs\"\nimport { delay, startWith } from \"rxjs/operators\"\n\nconst startWithAndDelay = (message, time) =\u003e\n  pipe(\n    delay(time),\n    startWith({ message })\n  )\n\nconst message$ = of({ message: \"Hello\" })\n\nexport default () =\u003e (\n  \u003cdiv\u003e\n    \u003ch2\u003eStream as a Component\u003c/h2\u003e\n    \u003cStream\n      source={message$}\n      pipe={startWithAndDelay(\"Wait...\", 500)}\n    \u003e\n      {({ message }) =\u003e \u003cdiv\u003e{message}\u003c/div\u003e}\n    \u003c/Stream\u003e\n    \u003cStream\n      source={message$}\n      pipe={startWithAndDelay(\"Wait longer...\", 3000)}\n    \u003e\n      {({ message }) =\u003e \u003cdiv\u003e{message}\u003c/div\u003e}\n    \u003c/Stream\u003e\n  \u003c/div\u003e\n)\n```\n\n### `stream`\n\n[Demo here](https://codesandbox.io/s/github/johnlindquist/react-streams/tree/master/examples?module=/stream/index.js)\n\n```js\nimport React from \"react\"\nimport { stream } from \"react-streams\"\nimport { interval } from \"rxjs\"\nimport { map } from \"rxjs/operators\"\n\nconst count$ = interval(250).pipe(\n  map(count =\u003e ({ count }))\n)\n\nconst Counter = stream(count$)\n\nexport default () =\u003e (\n  \u003cdiv\u003e\n    \u003ch2\u003eSubscribe to a Stream\u003c/h2\u003e\n    \u003cCounter\u003e\n      {({ count }) =\u003e \u003cdiv\u003e{count}\u003c/div\u003e}\n    \u003c/Counter\u003e\n  \u003c/div\u003e\n)\n```\n\n### `pipe`\n\n[Demo here](https://codesandbox.io/s/github/johnlindquist/react-streams/tree/master/examples?module=/pipe/index.js)\n\n```js\nimport React from \"react\"\nimport { stream } from \"react-streams\"\nimport { of } from \"rxjs\"\nimport { map } from \"rxjs/operators\"\n\nconst stream$ = of({ greeting: \"Hello\", name: \"world\" })\n\nconst mapToMessage = map(({ greeting, name }) =\u003e ({\n  message: `${greeting}, ${name}!`\n}))\n\nconst Greeting = stream(stream$, mapToMessage)\n\nexport default () =\u003e (\n  \u003cdiv\u003e\n    \u003ch2\u003ePipe Stream Values\u003c/h2\u003e\n    \u003cGreeting\u003e\n      {({ message }) =\u003e \u003cdiv\u003e{message}\u003c/div\u003e}\n    \u003c/Greeting\u003e\n  \u003c/div\u003e\n)\n```\n\n### `streamProps`\n\n[Demo here](https://codesandbox.io/s/github/johnlindquist/react-streams/tree/master/examples?module=/streamProps/index.js)\n\n```js\nimport React from \"react\"\nimport { streamProps } from \"react-streams\"\nimport { map } from \"rxjs/operators\"\n\nconst mapGreeting = map(({ greeting, name }) =\u003e ({\n  message: `${greeting}, ${name}!`\n}))\n\nconst HelloWorld = streamProps(mapGreeting)\n\nexport default () =\u003e (\n  \u003cdiv\u003e\n    \u003ch2\u003eStream Props to Children\u003c/h2\u003e\n    \u003cHelloWorld greeting=\"Hello\" name=\"world\"\u003e\n      {({ message }) =\u003e \u003cdiv\u003e{message}\u003c/div\u003e}\n    \u003c/HelloWorld\u003e\n    \u003cHelloWorld greeting=\"Bonjour\" name=\"John\"\u003e\n      {({ message }) =\u003e \u003cdiv\u003e{message}\u003c/div\u003e}\n    \u003c/HelloWorld\u003e\n  \u003c/div\u003e\n)\n```\n\n### Ajax\n\n[Demo here](https://codesandbox.io/s/github/johnlindquist/react-streams/tree/master/examples?module=/ajax/index.js)\n\n```js\nimport React from \"react\"\nimport { streamProps } from \"react-streams\"\nimport { pipe } from \"rxjs\"\nimport { ajax } from \"rxjs/ajax\"\nimport {\n  pluck,\n  switchMap,\n  startWith\n} from \"rxjs/operators\"\n\nconst getTodo = pipe(\n  switchMap(({ url, id }) =\u003e ajax(`${url}/${id}`)),\n  pluck(\"response\")\n)\n\nconst Todo = streamProps(getTodo)\n\nconst url = process.env.DEV\n  ? \"/api/todos\"\n  : \"https://dandelion-bonsai.glitch.me/todos\"\n\nexport default () =\u003e (\n  \u003cdiv\u003e\n    \u003ch2\u003eAjax Demo\u003c/h2\u003e\n    \u003cTodo url={url} id={2}\u003e\n      {({ text, id }) =\u003e (\n        \u003cdiv\u003e\n          {id}. {text}\n        \u003c/div\u003e\n      )}\n    \u003c/Todo\u003e\n    \u003cTodo url={url} id={3}\u003e\n      {({ text, id }) =\u003e (\n        \u003cdiv\u003e\n          {id}. {text}\n        \u003c/div\u003e\n      )}\n    \u003c/Todo\u003e\n  \u003c/div\u003e\n)\n```\n\n### Nested Streams\n\n[Demo here](https://codesandbox.io/s/github/johnlindquist/react-streams/tree/master/examples?module=/nested/index.js)\n\n```js\nimport React from \"react\"\nimport { Stream, StreamProps } from \"react-streams\"\nimport { map, filter } from \"rxjs/operators\"\nimport { interval } from \"rxjs\"\n\nconst count$ = interval(1000).pipe(\n  map(count =\u003e ({ count }))\n)\n\nconst odds = filter(({ count }) =\u003e count % 2)\nconst evens = filter(({ count }) =\u003e !(count % 2))\n\nexport default () =\u003e (\n  \u003cStream source={count$}\u003e\n    {({ count }) =\u003e (\n      \u003cdiv style={{ padding: \"2rem\" }}\u003e\n        \u003ch2\u003e\n          Stream with Nested StreamProps Components\n        \u003c/h2\u003e\n        \u003cStreamProps count={count}\u003e\n          {({ count }) =\u003e \u003cdiv\u003eNo filter: {count}\u003c/div\u003e}\n        \u003c/StreamProps\u003e\n        \u003cStreamProps count={count} pipe={odds}\u003e\n          {({ count }) =\u003e \u003cdiv\u003eOdds: {count}\u003c/div\u003e}\n        \u003c/StreamProps\u003e\n        \u003cStreamProps count={count} pipe={evens}\u003e\n          {({ count }) =\u003e \u003cdiv\u003eEvens: {count}\u003c/div\u003e}\n        \u003c/StreamProps\u003e\n      \u003c/div\u003e\n    )}\n  \u003c/Stream\u003e\n)\n```\n\n### Create a `plan`\n\n[Demo here](https://codesandbox.io/s/github/johnlindquist/react-streams/tree/master/examples?module=/plans/index.js)\n\n```js\nimport React from \"react\"\nimport { StreamProps, plan } from \"react-streams\"\nimport { map, pluck } from \"rxjs/operators\"\n\nconst onChange = plan(\n  pluck(\"target\", \"value\"),\n  map(message =\u003e ({ message }))\n)\n\nexport default () =\u003e (\n  \u003cdiv\u003e\n    \u003ch2\u003eUpdate a Stream with Plans\u003c/h2\u003e\n    \u003cStreamProps message=\"Hello\" plans={{ onChange }}\u003e\n      {({ message, onChange }) =\u003e (\n        \u003cdiv\u003e\n          \u003cinput\n            id=\"input\"\n            type=\"text\"\n            onChange={onChange}\n          /\u003e\n          \u003cdiv id=\"message\"\u003e{message}\u003c/div\u003e\n        \u003c/div\u003e\n      )}\n    \u003c/StreamProps\u003e\n  \u003c/div\u003e\n)\n```\n\n### `scanPlans`\n\n[Demo here](https://codesandbox.io/s/github/johnlindquist/react-streams/tree/master/examples?module=/plans/index.js)\n\n```js\nimport React from \"react\"\nimport {\n  scanPlans,\n  plan,\n  streamProps\n} from \"react-streams\"\nimport { pipe } from \"rxjs\"\nimport { ajax } from \"rxjs/ajax\"\nimport {\n  debounceTime,\n  distinctUntilChanged,\n  map,\n  pluck\n} from \"rxjs/operators\"\n\nconst handleInput = pipe(\n  pluck(\"target\", \"value\"),\n  debounceTime(250),\n  distinctUntilChanged(),\n  /**\n   * map to a fn which returns an object, fn, or Observable (which returns an\n   * object, fn, or Observable)\n   */\n  map(term =\u003e props =\u003e {\n    if (term.length \u003c 2) return { people: [], term: \"\" }\n    return ajax(\n      `${props.url}?username_like=${term}`\n    ).pipe(\n      pluck(\"response\"),\n      map(people =\u003e ({\n        term,\n        people: people.slice(0, 10)\n      }))\n    )\n  })\n)\n\nconst Typeahead = streamProps(\n  scanPlans({ onChange: plan(handleInput) })\n)\n\nconst url = process.env.DEV\n  ? \"/api/people\"\n  : \"https://dandelion-bonsai.glitch.me/people\"\n\nexport default () =\u003e (\n  \u003cTypeahead url={url} people={[]}\u003e\n    {({ term, people, onChange }) =\u003e (\n      \u003cdiv\u003e\n        \u003ch2\u003eSearch a username: {term}\u003c/h2\u003e\n        \u003cinput\n          type=\"text\"\n          onChange={onChange}\n          placeholder=\"Type to seach\"\n          autoFocus\n        /\u003e\n        \u003cul\u003e\n          {people.map(person =\u003e (\n            \u003cli\n              key={person.id}\n              style={{ height: \"25px\" }}\n            \u003e\n              \u003cspan\u003e{person.username}\u003c/span\u003e\n              \u003cimg\n                style={{ height: \"100%\" }}\n                src={person.avatar}\n                alt={person.username}\n              /\u003e\n            \u003c/li\u003e\n          ))}\n        \u003c/ul\u003e\n      \u003c/div\u003e\n    )}\n  \u003c/Typeahead\u003e\n)\n```\n\n### Counter Demo\n\n[Demo here](https://codesandbox.io/s/github/johnlindquist/react-streams/tree/master/examples?module=/counter/index.js)\n\n```js\nimport React from \"react\"\nimport {\n  scanPlans,\n  plan,\n  streamProps\n} from \"react-streams\"\nimport { map } from \"rxjs/operators\"\n\nconst onInc = plan(\n  map(() =\u003e state =\u003e ({ count: state.count + 2 }))\n)\nconst onDec = plan(\n  map(() =\u003e state =\u003e ({ count: state.count - 2 }))\n)\nconst onReset = plan(map(() =\u003e state =\u003e ({ count: 4 })))\n\nconst Counter = streamProps(\n  scanPlans({ onInc, onDec, onReset })\n)\n\nexport default () =\u003e (\n  \u003cCounter count={4}\u003e\n    {({ count, onInc, onDec, onReset }) =\u003e (\n      \u003cdiv\u003e\n        \u003cbutton\n          id=\"dec\"\n          onClick={onDec}\n          aria-label=\"decrement\"\n        \u003e\n          -\n        \u003c/button\u003e\n        \u003cspan id=\"count\" aria-label=\"count\"\u003e\n          {count}\n        \u003c/span\u003e\n        \u003cbutton\n          id=\"inc\"\n          onClick={onInc}\n          aria-label=\"increment\"\n        \u003e\n          +\n        \u003c/button\u003e\n        \u003cbutton onClick={onReset} aria-label=\"reset\"\u003e\n          Reset\n        \u003c/button\u003e\n      \u003c/div\u003e\n    )}\n  \u003c/Counter\u003e\n)\n```\n","funding_links":[],"categories":["JavaScript"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjohnlindquist%2Freact-streams","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjohnlindquist%2Freact-streams","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjohnlindquist%2Freact-streams/lists"}