{"id":13802458,"url":"https://github.com/recyclejs/recycle","last_synced_at":"2025-05-13T13:31:17.074Z","repository":{"id":57349468,"uuid":"70593004","full_name":"recyclejs/recycle","owner":"recyclejs","description":"Convert functional/reactive object description using RxJS into React component","archived":false,"fork":false,"pushed_at":"2020-01-10T10:55:33.000Z","size":3352,"stargazers_count":366,"open_issues_count":2,"forks_count":17,"subscribers_count":11,"default_branch":"master","last_synced_at":"2025-04-22T19:11:31.736Z","etag":null,"topics":["observable","react","reactive-programming","redux","rxjs"],"latest_commit_sha":null,"homepage":"","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/recyclejs.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":"2016-10-11T12:53:36.000Z","updated_at":"2025-01-15T09:05:20.000Z","dependencies_parsed_at":"2022-09-09T13:21:14.015Z","dependency_job_id":null,"html_url":"https://github.com/recyclejs/recycle","commit_stats":null,"previous_names":[],"tags_count":10,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/recyclejs%2Frecycle","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/recyclejs%2Frecycle/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/recyclejs%2Frecycle/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/recyclejs%2Frecycle/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/recyclejs","download_url":"https://codeload.github.com/recyclejs/recycle/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253950036,"owners_count":21989295,"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":["observable","react","reactive-programming","redux","rxjs"],"created_at":"2024-08-04T00:01:44.931Z","updated_at":"2025-05-13T13:31:16.791Z","avatar_url":"https://github.com/recyclejs.png","language":"JavaScript","readme":"[![Join the chat at https://gitter.im/recyclejs](https://img.shields.io/gitter/room/nwjs/nw.js.svg?style=flat-square)](https://gitter.im/recyclejs?utm_source=badge\u0026utm_medium=badge\u0026utm_campaign=pr-badge\u0026utm_content=badge)\n[![npm version](https://img.shields.io/npm/v/recycle.svg?style=flat-square)](https://www.npmjs.com/package/recycle)\n[![npm downloads](https://img.shields.io/npm/dm/recycle.svg?style=flat-square)](https://www.npmjs.com/package/recycle)\n\n# DEPRECATED\nPlease note that this library hasn't been updated for more than two years. It's very rarely used and I consider it deprecated.\n\n# Recycle\nConvert functional/reactive object description into React component.\n\nYou don't need another UI framework if you want to use [RxJS](https://github.com/ReactiveX/rxjs).\n\n## Installation\n```bash\nnpm install --save recycle\n```\n\n## Example\n[**Webpackbin example**](https://www.webpackbin.com/bins/-KiHSPOMjmY9tz4qYnbv)\n\n```javascript\nconst Timer = recycle({\n  initialState: {\n    secondsElapsed: 0,\n    counter: 0\n  },\n \n  update (sources) {\n    return [\n      sources.select('button')\n        .addListener('onClick')\n        .reducer(state =\u003e {\n          ...state,\n          counter: state.counter + 1\n        }),\n      \n      Rx.Observable.interval(1000)\n        .reducer(state =\u003e {\n          ...state,\n          secondsElapsed: state.secondsElapsed + 1\n        })\n    ]\n  },\n \n  view (props, state) {\n    return (\n      \u003cdiv\u003e\n        \u003cdiv\u003eSeconds Elapsed: {state.secondsElapsed}\u003c/div\u003e\n        \u003cdiv\u003eTimes Clicked: {state.counter}\u003c/div\u003e\n        \u003cbutton\u003eClick Me\u003c/button\u003e\n      \u003c/div\u003e\n    )\n  }\n})\n```\n\nYou can also listen on child component events and define custom event handlers.\nJust make sure you specify what should be returned:\n\n```javascript\nimport CustomButton from './CustomButton'\n\nconst Timer = recycle({\n  initialState: {\n    counter: 0\n  },\n \n  update (sources) {\n    return [\n      sources.select(CustomButton)\n        .addListener('customOnClick')\n        .reducer((state, returnedValue) =\u003e {\n          counter: state.counter + returnedValue\n        })\n    ]\n  },\n \n  view (props, state) {\n    return (\n      \u003cdiv\u003e\n        \u003cdiv\u003eTimes Clicked: {state.counter}\u003c/div\u003e\n        \u003cCustomButton customOnClick={e =\u003e e.something}\u003eClick Me\u003c/CustomButton\u003e\n      \u003c/div\u003e\n    )\n  }\n})\n```\n\n## Replacing Redux Connect\nIf you are using Redux,\nRecycle component can also be used as a container (an alternative to Redux `connect`).\n\nThe advantage of this approach is that you have full control over component rerendering (components will not be \"forceUpdated\" magically).\n\nAlso, you can listen to a specific part of the state and update your component only if that property is changed.\n\n```javascript\nexport default recycle({\n  dispatch (sources) {\n    return [\n      sources.select('div')\n        .addListener('onClick')\n        .mapTo({ type: 'REDUX_ACTION_TYPE', text: 'hello from recycle' })\n    ]\n  },\n\n  update (sources) {\n    return [\n      sources.store\n        .reducer(function (state, store) {\n          return store\n        })\n\n      /** \n      * Example of a subscription on a specific store property\n      * with distinctUntilChanged() component will be updated only when that property is changed\n      *\n      * sources.store\n      *   .map(s =\u003e s.specificProperty)\n      *   .distinctUntilChanged()\n      *   .reducer(function (state, specificProperty) {\n      *     state.something = specificProperty\n      *     return state\n      *   })\n      */\n    ]\n  },\n\n  view (props, state) {\n    return \u003cdiv\u003eNumber of todos: {store.todos.length}\u003c/div\u003e\n  }\n})\n```\n\n## Effects\nIf you don't need to update a component local state or dispatch Redux action,\nbut you still need to react to some kind of async operation, you can use `effects`.\n\nRecycle will subscribe to this stream but it will not use it.\nIt is intended for making side effects (like calling callback functions passed from a parent component)\n\n```javascript\nconst Timer = recycle({\n \n  effects (sources) {\n    return [\n      sources.select('input')\n        .addListener('onKeyPress')\n        .withLatestFrom(sources.props)\n        .map(([e, props]) =\u003e {\n          props.callParentFunction(e.target.value)\n        })\n    ]\n  },\n \n  view (props) {\n    return (\n      \u003cinput placeholder={props.defaultValue}\u003e\u003c/input\u003e\n    )\n  }\n})\n```\n\n## API\nComponent description object accepts following properties:\n\n```javascript\n{\n  propTypes: { name: PropTypes.string },\n  displayName: 'ComponentName',\n  initialState: {},\n  dispatch: function(sources) { return Observable },\n  update: function(sources) { return Observable },\n  effects: function(sources) { return Observable },\n  view: function(props, state) { return JSX }\n}\n```\n\nIn `update`, `dispatch` and `effects` functions, you can use the following sources:\n\n```javascript\n/**\n*   sources.select\n*\n*   select node by tag name or child component\n*/\nsources.select('tag')\n  .addListener('event')\n\nsources.select(ChildComponent)\n  .addListener('event')\n\n/**\n*   sources.selectClass\n*\n*   select node by class name\n*/\nsources.selectClass('classname')\n  .addListener('event')\n\n/**\n*   sources.selectId\n*\n*   select node by its id\n*/\nsources.selectId('node-id')\n  .addListener('event')\n\n/**\n*   sources.store\n*\n*   If you are using redux (component is inside Provider)\n*   sources.store will emit its state changes\n*/\n  sources.store\n    .reducer(...)\n\n/**\n*   sources.state\n*\n*   Stream of current local component state\n*/\n  sources.select('input')\n    .addListener('onKeyPress')\n    .filter(e =\u003e e.key === 'Enter')\n    .withLatestFrom(sources.state)\n    .map(([e, state]) =\u003e state.someStateValue)\n    .map(someStateValue =\u003e using(someStateValue))\n\n/**\n*   sources.props\n*\n*   Stream of current local component props\n*/\n  sources.select('input')\n    .addListener('onKeyPress')\n    .filter(e =\u003e e.key === 'Enter')\n    .withLatestFrom(sources.props)\n    .map(([e, props]) =\u003e props.somePropsValue)\n    .map(somePropsValue =\u003e using(somePropsValue))\n\n/**\n*   sources.lifecycle\n*\n*   Stream of component lifecycle events\n*/\n  sources.lifecycle\n    .filter(e =\u003e e === 'componentDidMount')\n    .do(something)\n```\n\n## FAQ\n\n### Why would I use it?\n- Greater separation of concerns between component presentation and component logic\n- You don't need classes so each part of a component can be defined and tested separately.\n- Component description is more consistent.\n  There is no custom `handleClick` events or `this.setState` statements that you need to worry about.\n- The State is calculated the same way as for redux store: `state = reducer(state, action)`.\n- Redux container looks like a normal component and it's more clear what it does.\n- Easy to use in an existing React application (choose components which you wish to convert).\n\n### Why would I NOT use it?\n- Observables are not your thing.\n- You need more control over component lifecycle (like `shouldComponentUpdate`)\n\n### What is this? jQuery?\nNo.\n\nAlthough it resembles [query selectors](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector), Recycle uses React’s inline event handlers and doesn’t rely on the DOM. Since selection is isolated per component, no child nodes can ever be accessed.\n\n### Can I use CSS selectors?\nNo.\n\nSince Recycle doesn't query over your nodes, selectors like `div .class` will not work.\n\n### How does it then find selected nodes?\nIt works by monkeypatching `React.createElement`.\nBefore a component is rendered, for each element,\nif a select query is matched, recycle sets inline event listener.\n\nEach time event handler dispatches an event,\nit calls `selectedNode.rxSubject.next(e)`\n\n### Can I use it with React Native?\nYes.\n\nRecycle creates classical React component which can be safely used in React Native.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frecyclejs%2Frecycle","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frecyclejs%2Frecycle","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frecyclejs%2Frecycle/lists"}