{"id":17973970,"url":"https://github.com/cyrenajs/cyrena","last_synced_at":"2026-01-12T02:32:38.630Z","repository":{"id":41638829,"uuid":"173830552","full_name":"cyrenajs/cyrena","owner":"cyrenajs","description":"A composable, functional-reactive UI framework based on Cycle.js and React","archived":false,"fork":false,"pushed_at":"2023-03-04T18:48:32.000Z","size":4448,"stargazers_count":23,"open_issues_count":33,"forks_count":1,"subscribers_count":4,"default_branch":"master","last_synced_at":"2024-10-11T13:37:41.045Z","etag":null,"topics":["composition","cyclejs","fp","react","stream","view"],"latest_commit_sha":null,"homepage":"","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/cyrenajs.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,"governance":null}},"created_at":"2019-03-04T22:19:49.000Z","updated_at":"2023-05-02T21:50:58.000Z","dependencies_parsed_at":"2023-08-19T13:28:12.456Z","dependency_job_id":null,"html_url":"https://github.com/cyrenajs/cyrena","commit_stats":null,"previous_names":["sarimarton/cyrena","sarimarton/powercycle"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cyrenajs%2Fcyrena","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cyrenajs%2Fcyrena/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cyrenajs%2Fcyrena/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cyrenajs%2Fcyrena/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cyrenajs","download_url":"https://codeload.github.com/cyrenajs/cyrena/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":222083690,"owners_count":16928134,"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":["composition","cyclejs","fp","react","stream","view"],"created_at":"2024-10-29T17:05:19.235Z","updated_at":"2026-01-12T02:32:38.625Z","avatar_url":"https://github.com/cyrenajs.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"(Actually the React part is just for easy integration. But it's not essential.)\n\nCyrena is an experimental UI framework. It allows to have streams in any position of a JSX: as child nodes, attributes, or even just parts of (object) attributes. Cyrena currently works on top of Cycle.js, xstream and React. To put it in a more scientific way, it's a transpositional extension, which turns Cycle.js components inside-out: instead of having views defined in streams, we're defining streams in views. It puts the view in the center, to make composition as easy and trivial as it is in React - while keeping all the benefits of a purely functional-reactive environment. Any regular Cycle.js and React component can be included seamlessly in a Cyrena app.\n\n```jsx\nfunction Timer(sources) {\n  return (\n    \u003ch2\u003eTimer: {xs.periodic(1000)}\u003c/h2\u003e\n  )\n}\n```\n\nExamples:\n* [Hello World](https://codesandbox.io/s/94n4j2vxmw)\n* [Timer](https://codesandbox.io/s/1znx4w2xwq)\n* [Counter](https://codesandbox.io/s/5w53z5401p)\n* [Scope](https://codesandbox.io/s/jll2kjolk3)\n* [Todo List - simple](https://codesandbox.io/s/n7m44yjo0j)\n* [Todo List - advanced](https://codesandbox.io/s/2wv3r9ojqp)\n* [React component with Cycle state](https://codesandbox.io/s/74lnv0wy3j)\n* [Tic Tac Toe](https://codesandbox.io/s/github/sarimarton/tic-tac-toe-cyrena)\n* [Testing dashboard](https://codesandbox.io/s/github/sarimarton/cyrena-showcase)\n\n## Motivation\n\u003cdetails\u003e\n    \u003csummary\u003eClick to expand\u003c/summary\u003e\n\nReact and Cycle.js have separate advantages and compromises, and I wanted to bring the good parts together.\n\n\u003ch3\u003eReact\u003c/h3\u003e\n\n\u003cp\u003eReact's benefits are that it's view-based, and imperative. Imperative means that the developer works with first-order values and nudges the application further by calling setState imperatively. This is by far the most accessible way for developers to proceed and get things done.\u003c/p\u003e\n\n\u003cp\u003eBeing view-based means, that when we see a piece of React code, we immediately recognize the structure of the app or component by having a glimpse on the JSX part.\u003c/p\u003e\n\n\u003cp\u003eBut all this comes at the price of an unconventional programming model, where the render function gets called many times by the runtime. And having the ability to return with completely different output VDOMs based on different inputs, defeats the purpose of the JSX as a structural overview. In idiomatic React code, most of the JSX is conceptually a static hierarchy, which contains changing bits and pieces. But in reality, not just the changing parts, but all the conceptually static parts, too, are re-evaluated and matched with the previous output. Sometimes it needs special awareness. And this reasoning goes for the logic as well, with the well-known \u003ca href=\"https://reactjs.org/docs/hooks-rules.html\"\u003eRules of Hooks\u003c/a\u003e. This might not be a big deal, you might think, but what I think is that we can do it better.\u003c/p\u003e\n\n\u003ch3\u003eCycle.js\u003c/h3\u003e\n\n\u003cp\u003eCycle.js is a purely functional-reactive framework, and I won't detail how useful this fact is. It's also quite mature in its current state. Components are static, they're called once, and not by a runtime, but simply by the app author. The downside of it is that it's \u003cem\u003enot view-based\u003c/em\u003e. Sure, we do have the view part, and it can even be JSX, but unfortunately this view part is not in the static scope. It's in the same iteratee-realm in which React is. But here it comes with a serious consequence: there's no easy composition! You can't throw in components in the VDOM tree. You have to do cumbersome boilerplate even for basic composition.\u003c/p\u003e\n\n\u003cp\u003eThis led me to explore the possibilities to make something as simple and composable as React, but as \u003cem\u003eright\u003c/em\u003e in its programming model as Cycle.js. This pursue resulted in Cyrena.\u003c/p\u003e\n\n\u003c/details\u003e\n\n## Guide\n\n- [Motivation](#motivation)\n- [Guide](#guide)\n  - [Installation](#installation)\n  - [Getting Started](#getting-started)\n    - [JSX](#jsx)\n  - [Static VDOM composition](#static-vdom-composition)\n    - [Shorthand return from components](#shorthand-return-from-components)\n    - [withPower](#withpower)\n  - [Streams and components everywhere](#streams-and-components-everywhere)\n  - [Scopes](#scopes)\n    - [Automatic view scoping](#automatic-view-scoping)\n  - [Conditionals](#conditionals)\n    - [`If` component:](#if-component)\n    - [`if` prop (applies to Component and DOM elements):](#if-prop-applies-to-component-and-dom-elements)\n  - [Collection](#collection)\n    - [Reaching out to the outer state from the items](#reaching-out-to-the-outer-state-from-the-items)\n  - [Event props](#event-props)\n  - [React realms](#react-realms)\n  - [Helpers, Shortcuts and Tips](#helpers-shortcuts-and-tips)\n\n### Installation\n\nInstall Cyrena and its peer dependencies:\n\n`npm install cyrena @cycle/react react xstream`\n\nInstall the usual Cycle/react dependencies:\n\n`npm install @cycle/run @cycle/react-dom @cycle/state`\n\n### Getting Started\n\n#### JSX\n\nBesides following the [Installation](#installation) steps, make sure that your setup can handle JSX, because Cyrena was made with JSX in mind. Cyrena has its own JSX pragma:\n\n```jsx\nimport withPower from 'cyrena'\n/** @jsx withPower.pragma */\n/** @jsxFrag withPower.Fragment */\n```\n\nObviously you can skip using JSX, if you really wish, but you'll still need the pragma.\n\n### Static VDOM composition\n\nWe've seen the default import above named `withPower`, but let's forget that for now. Cyrena's core utility is the `cyrena` function, which takes 3 arguments, and returns a regular Cycle.js _sinks_ object. (Don't pick on the name `cyrena`; at the end of the day, you won't even need to use this function.)\n\n1. The first argument is a static VDOM, which can contain streams and other components (even inline components! We'll see them later).\n1. The second argument is the sinks object, which contains all of the sinks for the current component, _except the view_.\n1. The third argument is the sources object which Cyrena will pass to the components during the VDOM traversal.\n\nLet's see a basic example of an atomic component:\n\n```jsx\n// Regular Cycle.js component\nfunction Cmp(sources) {\n  const state$ = sources.state.stream\n\n  return {\n    react: state$.map(state =\u003e \u003cdiv\u003e{state}\u003c/div\u003e)\n  }\n}\n```\n\nIt turns into:\n\n```jsx\nfunction Cmp(sources) {\n  const state$ = sources.state.stream\n\n  return cyrena(\n    \u003cdiv\u003e{state$}\u003c/div\u003e,\n    null,\n    sources\n  )\n}\n```\n\nAt the moment it doesn't seem to be much useful, but what happens when you want to include a child component, for example a Panel, in which you want to wrap the content. It leads to serious boilerplate:\n\n```jsx\nfunction Cmp(sources) {\n  const state$ = sources.state.stream\n\n  const panelSinks = Panel({ ...sources, props: { title: 'State' }})\n\n  return {\n    react: panelSinks.react.map(panelVdom =\u003e\n      \u003cdiv\u003e\n        State in a panel:\n        {panelVdom}\n      \u003c/div\u003e\n    )\n  }\n}\n```\n\nWith Cyrena, it remains an atomic step:\n\n```jsx\nfunction Cmp(sources) {\n  const state$ = sources.state.stream\n\n  return cyrena(\n    \u003cdiv\u003e\n      State in a panel:\n      \u003cPanel title=\"State\"\u003e\n        {state$}\n      \u003c/Panel\u003e\n    \u003c/div\u003e\n    null,\n    sources\n  )\n}\n```\n\nYou can see the limitation in the first version: you can't put content and pass props to the Panel component in the VDOM. Wrapping a section of a content into a container is not a trivial action – you have to declare and manually invoke every related component separately from the VDOM. In the Cyrena example you might now have an idea how easy it can go with composition. The `cyrena` function will invoke the Panel component with passing the sources object to it (given as the 3rd parameter). Whenever the Panel component's view stream updates, the outer component will update as well. We'll see more powerful examples in the next sections, but let's not rush ahead.\n\nHow do we define our other sinks then, which are not the view? This is what the second argument is for:\n\n```jsx\nfunction Cmp(sources) {\n  const state$ = sources.state.stream\n\n  return cyrena(\n    \u003cdiv\u003e\n      State in a panel:\n      \u003cPanel title=\"State\"\u003e\n        {state$}\n      \u003c/Panel\u003e\n    \u003c/div\u003e\n    {\n      state: ...,\n      HTTP: ...\n    },\n    sources\n  )\n}\n```\n\nLet's wrap up what the `cyrena` function does exactly:\n\n1. It traverses the VDOM and searches for streams and components (see section [Streams and components everywhere](#streams-and-components-everywhere) for details).\n1. It creates a view stream for the outer component which combines all the view streams in the given VDOM, and updates with the original VDOM structure.\n1. It collects all the non-view sink channels which were found in the inner components' sinks objects, and merges them all by channel. It also adds the sinks of the second argument to the merges. The result sinks object will be the return value of the `cyrena` function.\n\n#### Shorthand return from components\n\nFrom the last example of the previous section we learned that the VDOM traversal stops at the Panel component. The Panel component can do anything with the state$ stream which it received through `sources.props.children`. It can even dismiss it. This is the same behavior as you can find in React. In order to see the state in the app, the Panel component must include its `sources.props.children` in its VDOM:\n\n```jsx\nfunction Panel(sources) {\n  return cyrena(\n    \u003cdiv className=\"some panel styling\"\u003e\n      \u003ch2 className=\"title\"\u003e{sources.props.title}\u003c/h2\u003e\n      {sources.props.children}\n    \u003c/div\u003e,\n    null,\n    sources\n  )\n}\n```\n\nOne important fact to realize here is that the Panel component is not invoked by the app developer, but by Cyrena. So Cyrena sees its output, and it can automatically call the cyrena function on it, so we have less to type:\n\n```jsx\nfunction Panel(sources) {\n  return [\n    \u003cdiv className=\"some panel styling\"\u003e\n      \u003ch2 className=\"title\"\u003e{sources.props.title}\u003c/h2\u003e\n      {sources.props.children}\n    \u003c/div\u003e,\n    null,\n    sources\n  ]\n}\n```\n\nThis convenience shortcut changes the regular signature of a Cycle.js component's output, but we'll see how it hugely pays off. We can even omit the sources object too, because Cyrena already has it from the first `cyrena` call. If there's no non-view sink channel for the component, we can omit the second parameter too and the array wrapping, so this results in as simple as this:\n\n```jsx\nfunction Panel(sources) {\n  return (\n    \u003cdiv className=\"some panel styling\"\u003e\n      \u003ch2 className=\"title\"\u003e{sources.props.title}\u003c/h2\u003e\n      {sources.props.children}\n    \u003c/div\u003e\n  )\n}\n```\n\n#### withPower\n\nEvery component which returns with the shortcut return format, conveys the same amount of information as a regular Cycle.js component, it's just 'controlled' by Cyrena. The only thing we have to watch out for, is to have a root `cyrena` call to have the VDOM 'controlled'. It turns out, we can wrap our main component in a higher order function to do this:\n\n```jsx\nimport withPower from 'cyrena'\n/** @jsx withPower.pragma */\n/** @jsxFrag withPower.Fragment */\n\n// ...\n\nrun(withPower(main), drivers)\n```\n\nWith the `withPower` function, you don't ever need to use the cyrena function! You can just return with the VDOM, or with an array containing the VDOM and the event sinks object.\n\n### Streams and components everywhere\n\nCyrena collects streams and components from the VDOM according to the following rules:\n\n1. When it finds a stream as a _VDOM child_, it collects the stream:\n\n    ```jsx\n    function main (sources) {\n      // ...\n      return (\n        \u003cdiv\u003e{state$}\u003c/div\u003e\n      )\n    }\n    ```\n\n2. When it finds a stream in a prop of a _plain DOM (e.g. a 'div') element_, it collects the stream:\n\n    ```jsx\n    function main (sources) {\n      // ...\n      return (\n        \u003cdiv style={ { background: color$ } }\u003e...\u003c/div\u003e\n      )\n    }\n    ```\n\n3. When it finds a _component (e.g. Panel) element_, it invokes it with the sources objects, and collects its sinks. It doesn't continue the traversal under the component element. It passes the props object as `sources.props`. The inner component can access the children as `sources.props.children`:\n\n    ```jsx\n    function Panel (sources) {\n      return (\n        \u003c\u003e\n          \u003ch1\u003e{sources.props.title}\u003c/h1\u003e\n          {sources.props.children}\n        \u003c/\u003e\n    }\n\n    function main (sources) {\n      return (\n        \u003cdiv\u003e\n          \u003cPanel title=\"My Panel\"\u003e...\u003c/Panel\u003e\n        \u003c/div\u003e\n      )\n    }\n    ```\n\n4. When it finds a _function as a VDOM child_, it's interpreted as an _inline component_. Cyrena will invoke the component with the sources object and collects its sinks, just like as it were a component element:\n\n    ```jsx\n    function main (sources) {\n      return (\n        \u003cdiv\u003e\n          {sources =\u003e {\n            return [\n              \u003cdiv\u003e...\u003c/div\u003e,\n              { state: ... }\n            ]\n          }}\n        \u003c/div\u003e\n      )\n    }\n    ```\n\n### Scopes\n\nAny VDOM node can have a `scope` prop, which will act as a regular [Cycle.js isolation scope](https://cycle.js.org/api/state.html#cycle-state-source-usage-how-to-share-data-among-components-or-compute-derived-data) for the given element. As components act as boundaries in the Cyrena traversal, a scope will not just affect the component, but the complete sub-VDOM under it as well.\n\n```jsx\nfunction ShowState(sources) {\n  return (\n    \u003cpre\u003e{sources.state.stream.map(JSON.stringify)}\u003c/pre\u003e\n  )\n}\n\nfunction main(sources) {\n  const reducer$ = xs.of(() =\u003e ({\n    foo: { bar: { baz: 5 } }\n  }))\n\n  return [\n    \u003cShowState scope='foo' /\u003e,\n    { state: reducer$ }\n  ]\n  // will show {\"bar\":{\"baz\":5}}\"\n}\n```\n\nUnlike the regular Cycle.js scope parameter, the `scope` prop can be a nested lens:\n\n```jsx\n  return [\n    \u003cShowState scope='foo.bar' /\u003e,\n    { state: reducer$ }\n  ]\n  // will show {\"baz\":5}\"\n```\n\nAnd of course it can be a full scope object:\n\n```jsx\n  return [\n    \u003cShowState scope={ { state: {\n      get: state =\u003e JSON.stringify(state.foo.bar),\n      set: (state, childState) =\u003e ({ ...state, foo: JSON.parse(childState)})\n    } }} /\u003e\n    { state: reducer$ }\n  ]\n  // will show \"{\\\"baz\\\":5}\"\n```\n\nThe `scope` prop can be used on a DOM element as well. In this case, the scope\nwill be applied to all the other props\n(for `get` and `onChange`, see [Helpers, Shortcuts and Tips](#helpers-shortcuts-and-tips)).\nIf there's both an `if` and `scope` prop on the element, their precedence will be\ndefined by their definition order on the node!\n\n```jsx\n  // state: { todos: [{ text: 'todo1' }, { text: 'todo2' }, { text: 'todo3' }]}\n  ...\n  \u003cCollection for='todos'\u003e\n    \u003cdiv\u003e\n      \u003cinput scope='item.text' value={get()} onChange={({ target: { value } }) =\u003e () =\u003e value} /\u003e\n      \u0026nbsp;\n      \u003cbutton onClick={() =\u003e COLLECTION_DELETE}\u003eRemove\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/Collection\u003e\n```\n\n#### Automatic view scoping\n\nBy default, every component in Cyrena is scoped on the view channel. If you need to lift this rule occasionally, you can provide a `noscope` prop on the component. The reason for this rule is to make string VDOM selectors safe by default. String VDOM selectors are useful, because they eliminate the necessity of boilerplate Symbol declarations. Take a look at this inline component, which is inside a `Collection` item:\n\n```jsx\n  {src =\u003e [\n    \u003cbutton sel='remove'\u003eRemove\u003c/button\u003e,\n    { state: src.sel.remove.click.mapTo(COLLECTION_DELETE) }\n  ]}\n```\n\n[See the Todo example](https://codesandbox.io/s/2wv3r9ojqp)\n\n### Conditionals\n\n#### `If` component:\n\nWraps the `then` or `else` value in a Fragment based on the `cond` property. The\n`cond` property can be either a stream or a stream callback. `then` and `else` values\ncan any vdom child.\n\n```jsx\n  \u003cIf cond={condition}\n    then={value}\n    else={otherValue}\n  /\u003e\n```\n\nAs an alternative way of defining the `then` branch, instead of using the `then` prop,\nyou can define the `then` children as the vdom subtree of the If component:\n\n```jsx\n  \u003cIf cond={condition} [else={...}]\u003e\n    {`then` value}\n  /\u003e\n```\n\n#### `if` prop (applies to Component and DOM elements):\n\nControls the existence of the element based on the `if` condition:\n\n```jsx\n  \u003cdiv if={condition}\u003eRemove\u003c/div\u003e\n```\n\nIf there's both an `if` and `scope` prop on the element, their precedence will be\ndefined by their definition order on the node!\n\n\n### Collection\n\nCyrena has a `Collection` component which makes handling dynamic lists easy and trivial. By default, you don't need to provide any props to `Collection`. It uses the state channel as its input, so make sure that you [scope down the state](#scopes) either on the `Collection` component or somewhere above. The `Collection` component will take its VDOM children as a fragment as its item component, so you can put anything between the opening and closing `\u003cCollection\u003e` tags. The Collection package also has a const for item removal reducer:\n\n```jsx\n\u003cCollection\u003e\n  \u003cCombobox /\u003e\n\n  {src =\u003e [\n    \u003cbutton sel='remove'\u003eRemove\u003c/button\u003e,\n    { state: src.sel.remove.click.mapTo(COLLECTION_DELETE) }\n  ]}\n\u003c/Collection\u003e\n```\n\n#### Reaching out to the outer state from the items\n\nThere are cases when you need to interact with the outer state from the items. For example, you need to duplicate an item, or set some state somewhere else, based upon the current item's state. For these cases, `Collection` automatically provides an extra stream in the items' sources object: `outerState` (the name can be specified with `outerstate` prop). Items can also leave reducers in their `outerState` sink. In order to interact with not just the collection array itself, even outer states, use the `for` prop on the `Collection`. The `for` prop works exactly like `scope` in specifying the collection's array, but it doesn't scope down the component, so outerState can see beyond the list.\n\n```jsx\n/*\n  {\n    globalColor: \"blue\",\n    foobar: {\n      list: [{ color: \"white\", id: {} }, { color: \"blue\", id: {} }]\n    }\n  }\n*/\n\n\u003cCollection for='foobar.list'\u003e\n  Set color: \u003cCombobox /\u003e\n\n  {src =\u003e [\n    \u003cbutton sel='set'\u003eSet as global\u003c/button\u003e,\n    {\n      outerState: src.sel.set.click\n        .compose(sampleCombine(src.state.stream))\n        .map(([click, state]) =\u003e outerState =\u003e ({\n          ...outerState,\n          globalColor: state.color\n        }))\n    }\n  ]}\n\u003c/Collection\u003e\n```\n\n[See the Todo app for an example](https://codesandbox.io/s/2wv3r9ojqp)\n\n\n### Event props\n\nCyrena makes use of _onClick_-style even props on elements. Event props are basically [shortcuts](#helpers-shortcuts-and-tips)\nfor inline components. There are 2 types of event props:\n\n* `on\u003cEventname\u003e={ { sink1: \u003cevent$ to sink$ mapper\u003e, sink2: \u003cevent$ to sink$ mapper\u003e, ...} }`\n\nWhen the event prop value is an object, it is treated as a special sinks object, where the values are mappers\nbetween the event _stream_ to the sink _stream_.\n\n* `on\u003cEventname\u003e={\u003cevent to state mapper\u003e}`\n\nWhen the event prop value is a function, it is handled as a mapper between the event object and the state (reducer). The\nstate will be consumed by the state sink.\n\nExamples:\n\n```jsx\n\u003cdiv\u003e\n  {src =\u003e [\n    Last click position: {get()}\u003cbr /\u003e\n    \u003cbutton\u003eMake a request\u003c/button\u003e,\n    {\n      state: src.el.click.map(ev =\u003e () =\u003e `${ev.clientX},${ev.clientY}`),\n      HTTP: src.el.click.mapTo({ url: '?you-clicked' })\n    }\n  ]}\n\u003c/div\u003e\n```\n\nWith using event props, this can be rewritten as below. You can see that there's\nno more need to wrap the fragment in an inline component:\n\n```jsx\n\u003cdiv\u003e\n  Last click position: {get()}\u003cbr /\u003e\n  \u003cbutton\n    onClick={ {\n      state: ev$ =\u003e ev$.map(ev =\u003e `${ev.clientX},${ev.clientY}`),\n      HTTP: ev$ =\u003e ev$.mapTo({ url: '?you-clicked' })\n    }}\n  \u003eMake a request\u003c/button\u003e\n\u003c/div\u003e\n```\n\nMost of the times we're only concerned about the state sink. For this, the\ncallback shortcut is even better:\n\n```jsx\n\u003cdiv\u003e\n  \u003cbutton onClick={ev =\u003e () =\u003e ({ action: 'ADD' })}\u003eAdd\u003c/button\u003e\n\u003c/div\u003e\n```\n\nLet's see another example. Here, we want to use a reducer with a `\u003cselect\u003e` element:\n\n```jsx\nfunction Combobox (sources) {\n  return (\n    \u003c\u003e\n      \u003clabel\u003eColor: \u003c/label\u003e\n      \u003cselect\n        value={get('color')}\n        onChange={ev =\u003e prev =\u003e ({ ...prev, color: ev.target.value })}\n      \u003e\n        \u003coption value='red'\u003eRed\u003c/option\u003e\n        \u003coption value='blue'\u003eBlue\u003c/option\u003e\n      \u003c/select\u003e\n    \u003c/\u003e\n  )\n}\n```\n\nIn this case, by the time the reducer will be called, the event object will be nullyfied\nby React, and React will throw an error related to the synthetic event. Destructuring\nthe arguments helps to overcome this problem:\n\n```jsx\nfunction Combobox (sources) {\n  return (\n    \u003c\u003e\n      \u003clabel\u003eColor: \u003c/label\u003e\n      \u003cselect\n        value={get('color')}\n        onChange={({ target: { value }}) =\u003e prev =\u003e ({ ...prev, color: value })}\n      \u003e\n        \u003coption value='red'\u003eRed\u003c/option\u003e\n        \u003coption value='blue'\u003eBlue\u003c/option\u003e\n      \u003c/select\u003e\n    \u003c/\u003e\n  )\n}\n```\n\n### React realms\n\nReact components can be included in the VDOM by wrapping them in the ReactRealm component. To use the state from the Cycle.js environment, Cyrena offers the `useCycleState` hook. You can put any content inside the opening and closing `\u003cReactRealm\u003e` tags, they won't be traversed by Cyrena. That part of the VDOM will go directly into the React engine:\n\n```jsx\nimport { ReactRealm, useCycleState } from 'cyrena/util/ReactRealm'\n\nfunction ReactCounter(props) {\n  const [count, setCount] = useCycleState(props.sources)\n\n  return (\n    \u003cdiv\u003e\n      \u003cdiv\u003eCounter: {count}\u003c/div\u003e\n      \u003cbutton onClick={() =\u003e setCount(count + 1)}\u003eIncrement\u003c/button\u003e\n    \u003c/div\u003e\n  )\n}\n\nfunction main(sources) {\n  const state$ = sources.state.stream\n\n  const reducer$ = xs.of(() =\u003e ({\n    counter: 5\n  }))\n\n  return [\n    \u003cdiv\u003e\n      \u003cReactRealm scope='counter'\u003e\n        We're under a React realm!\n        \u003cReactCounter /\u003e\n      \u003c/ReactRealm\u003e\n      \u003cpre\u003e{state$.map(JSON.stringify)}\u003c/pre\u003e\n    \u003c/div\u003e,\n    { state: reducer$ }\n  ]\n}\n```\n\n### Helpers, Shortcuts and Tips\n\n* #### [Event props](#event-props)\n\n* #### `map`\n  The `map` utility function is a handy helper to get the state in the VDOM. It has 2 signatures:\n  * `map(mapperFn, \u003csources\u003e)`\n\n  If the sources object is provided as the second parameter, the `map` function returns with a stream which maps `sources.state.stream` over the `mapperFn`, so it's a shortcut for `\u003csources\u003e.state.stream.map(mapperFn)`:\n\n  ```jsx\n  function ShowState(sources) {\n    return (\n      \u003cCode\u003e{map(JSON.stringify, sources)}\u003c/Code\u003e\n      {/* \u003cCode\u003e{sources.state.stream.map(JSON.stringify)}\u003c/Code\u003e */}\n    )\n  }\n  ```\n\n  * `map(mapperFn)`\n\n  If the sources object is omitted, then the `map` function returns with an inline component, which has the content of `sources.state.stream`, mapped over `mapperFn`, so it's a shortcut for `{ sources =\u003e \u003c\u003e{map(mapperFn, sources)}\u003c/\u003e }`:\n\n  ```jsx\n  function ShowState(sources) {\n    return (\n      \u003cCode\u003e{map(JSON.stringify)}\u003c/Code\u003e\n      {/* \u003cCode\u003e{sources =\u003e \u003c\u003e{map(JSON.stringify, sources)}\u003c/\u003e}\u003c/Code\u003e */}\n    )\n  }\n  ```\n\n  Note, that in props, you can only use it with the sources object, as inline components are not applicable as props.\n\n* #### `get`\n\n  The `get` function works exactly like `map` regarding its signature. The only difference is that it uses a Lodash getter as the mapperFn. It's a convenient shortcut for getting a chunk of the state:\n\n  ```jsx\n  function ShowColor(sources) {\n    const reducer$ = xs.of(() =\u003e ({\n      color: 'red'\n    }))\n\n    return (\n      \u003cpre style={ { background: get('color', sources) } }\u003eIt's {get('color')}\u003c/pre\u003e\n    )\n  }\n  ```\n\n  When the `get` function is called with no or empty parameter, it returns with the state object itself:\n\n  ```jsx\n  function ShowColor(sources) {\n    const reducer$ = xs.of(() =\u003e ({\n      color: 'red'\n    }))\n\n    return (\n      \u003cScope scope='color'\u003e\n        \u003cpre style={ { background: get('', sources) } }\u003eIt's {get()}\u003c/Code\u003e\n      \u003c/Scope\u003e\n    )\n  }\n  ```\n\n* #### *sources.sel[\u003cselector\u003e]*\n\n  View selection has a convenience shortcut. Instead of writing\n\n  `sources.react.select('input').events('change').map(ev =\u003e ev.target.value)`\n\n  You can write just:\n\n  `sources.sel.input.change['target.value']`\n\n  Example:\n\n  ```jsx\n  \u003cCollection\u003e\n    \u003cpre\u003e\n      \u003cCombobox /\u003e\n\n      {src =\u003e [\n        \u003cbutton sel='remove'\u003eRemove\u003c/button\u003e,\n        { state: src.sel.remove.click.mapTo(COLLECTION_DELETE) }\n      ]}\n\n      \u003cbr /\u003e\n\n      {src =\u003e\n        \u003cdiv style={ { color: get('color', src) } }\u003e\n          \u003cShowState /\u003e\n        \u003c/div\u003e\n      }\n    \u003c/pre\u003e\n  \u003c/Collection\u003e\n  ```\n\n  * #### *sources.el*\n\n  On a relative root element, you can even leave `sel=` and just write `sources.el`,\n  which will refer to the root element:\n\n  ```jsx\n  {src =\u003e [\n    \u003cbutton\u003eRemove\u003c/button\u003e,\n    { state: src.el.click.mapTo(COLLECTION_DELETE) }\n  ]}\n  ```\n\n\n* #### *How to opt-out from the Cyrena control?*\n\n  You can opt-out from Cyrena at any place in the VDOM by just returning a regular sinks object. The underlying components will not be controlled by Cyrena.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcyrenajs%2Fcyrena","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcyrenajs%2Fcyrena","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcyrenajs%2Fcyrena/lists"}