8 ms·
Cycle.js – A functional and reactive JavaScript framework for predictable code
- jakelazaroff 8y agoCycle is really cool, and I'd love to see reactive programming become more popular, but after trying it out for a project it has a few ergonomics issues that keep me from using it as my primary web framework. - Dynamic lists of components are really annoying to create. In Cycle, the happy path is calling every component function in your app at startup to set up the data flow graph, and then your reactive operators handle the actual logic. With dynamically added or removed components, you get a stream of "sinks" and then have to splice them into your data flow somehow. - Circular dependencies are cumbersome. If a parent passes a value to a child which can change that value in the parent, you essentially have to create a stub stream in the parent, use it to derive the parent's state and then "imitate" the output of the child as well. That said, Cycle has a lot of great ideas that I want to become more mainstream. If you have a free weekend, try it! The creator André Staltz's blog is also a good read: https://staltz.com/ https://staltz.com/
- staltz 8y agoI totally agree with the two issues you listed! I think both of them are well enough addressed with Cycle State, a new state management library. It has an API makeCollection() specifically meant for dynamic lists, with good DX and great performance. Also I regret imitate(), and gladly using Cycle State I haven't found a single need anymore for imitate(). Not mentioned in the official docs (yet), but Cycle.js has nowadays full support for React DOM and React Native.
- staltz 8y agoSorry, forgot to link: https://github.com/cyclejs/react https://github.com/cyclejs/react https://github.com/cyclejs/react-dom https://github.com/cyclejs/react-dom https://github.com/cyclejs/react-native https://github.com/cyclejs/react-native
- aarpmcgee 8y agoIs there JSX support?
- jakelazaroff 8y agoYup! https://cycle.js.org/getting-started.html#getting-started-coding-consider-jsx https://cycle.js.org/getting-started.html#getting-started-co... If you're just starting out though (or even if you're not) I'd recommend checking out create-cycle-app, the Cycle analogue to create-cycle-app that will set up things like that for you: https://github.com/cyclejs-community/create-cycle-app https://github.com/cyclejs-community/create-cycle-app
- aarpmcgee 8y agoRad! Thanks!
- jakelazaroff 8y agoCycle State looks great! I had checked out onionify, but it didn't have anything like the makeCollection() API at that point. As an aside, thank you for all your work on Cycle! The project I was working on was a visual reactive programming and design tool, inspired by Bret Victor's on direct manipulation and abstraction and by your work on reactive programming and Cycle itself! I remember seeing something like that on the long-term Cycle roadmap as well, but I couldn't find it when writing that comment.
- lgessler 8y agoCan anyone comment on how Cycle.js compares to other frontend FRP frameworks like ClojureScript's re-frame[1]? [1]: https://github.com/Day8/re-frame https://github.com/Day8/re-frame
- lilactown 8y ago[NOTE: I have much more experience using re-frame than Cycle.js, but have used both previously] Cycle.js and re-frame, in some ways, are on opposite sides of various spectrums. Cycle.js favors local reasoning and composability through modeling your application as a pure function that takes in streams (sources) and returns streams (sinks). All of your event handling, external data, DOM changes, etc. are explicitly tied together in your "main" function. Cycle.js requires you to reason about, write and read your application code in terms of reactive "streams" of values. This means that a big part of your app's development is expressing it in terms of operations on reactive streams and using the language of whichever stream library you choose. Re-frame favors separating your UI code (which you can compose) from state and other side effects (which are global) through it's use of named reactive subscriptions and dispatching of named events. You register your app's events as functions that take in "coeffects" (e.g. the current state of your app) and returns "effects" as data, e.g. the new state of your application. You can also return other "effects" like the parameters for doing an HTTP request and what event to dispatch when it completes/fails. Your UI code is then written to dispatch the names of these events (e.g. on click) that will trigger the registered function and cause re-frame to run the returned effects. You register your app's subscriptions as functions that take in the application state and return a materialized view of it. Your UI code is then written as functions that subscribe to the names of these subscriptions, take the materialized view of your app state and return the UI tree that should be shown. The majority of your re-frame app code is written as regular functions which return data. This means that a big part of your app's development is expressing it in the terms of functions that return data so that the UI framework (React/reagent) can render it and re-frame can do the appropriate side effects. Cycle.js is a very "pure" framework, in that it's approach is to start from first principles - pure functions, application-as-a-stream - and to layer on tools and abstractions to make it effective. The work that the core of Cycle.js actually does for you is quite minimal, and the skeleton and muscles of your application is not at all hidden from you. Re-frame is much less "pure"; it has a guiding concept of separating your UI code from the side effects it produces. For instance, many UI functions are not pure because they subscribe to the global app state. And in fact you don't have to use re-frame for everything at all; you can opt to use local component state and direct mutation instead of that fits your use case better.
- XCSme 8y agoThe code looks a bit messy to be honest, maybe it's just the variable naming. `const input$ = sources.DOM.select('.field').events('input')` Why not name it the same as the DOM function `querySelector` ? `const name$ = input$.map(ev => ev.target.value).startWith('')` Wouldn't in this case `name$` be a boolean, but then `map` is called on a boolean. I just find the syntax very confusing.
- typon 8y agoWhat makes you guess that `name$` is a boolean?
- projectramo 8y agoPerhaps you were thinking of “name?” Because there is nothing about “$name” that implies it’s a Boolean
- jakelazaroff 8y agoBy convention, anything suffixed with $ is a stream. input$ is a stream of input events on all elements matching the selector ".field". The declaration for name$ says to take that stream of events and turn it into a stream of the value of those events, and to seed it with an empty string as the initial value. Reactive programming is a paradigm shift and definitely tough to wrap your head around at first. André Staltz (the creator of Cycle) has a good intro here: https://gist.github.com/staltz/868e7e9bc2a7b8c1f754 https://gist.github.com/staltz/868e7e9bc2a7b8c1f754
- 8y ago
- awaisraad 8y agoMan, I should start pasting my bookmarks on HN - that will give major boost to my HN score.
- todd3834 8y agoPlease do, why not?
- wild_preference 8y agoThey were being dismissive, suggesting that this is old news and something they already had on vinyl. As if all we care about is the new shiny and can't possibly be interested in nor discuss existing things.
- k__ 8y agoCycle.js is what Angular2 should have been.
- deleted 8y ago[deleted]
- apolymath 8y agoI am definitely an advocate for reactive programming, as I have used React Native to develop a cross-platform mobile application (https://dedicate.datasilk.io https://dedicate.datasilk.io), but when it comes to web development, I am very much an advocate for using the basic tools that were given to us web developers to build web applications. These tools include mainly HTML & CSS, and when needed, "vanilla" JavaScript (and maybe even a compact jQuery alternative like zepto), but in no way do I advocate for using client-side frameworks for developing web applications. It is because they are difficult to debug, sometimes near impossible, and also because it leads the developer astray from practicing basic concepts such as writing beautifully formatted HTML & CSS or utilizing small bits of JavaScript for event handling & AJAX calls. Instead, they are forced to focus nearly all of their attention to writing JavaScript in what I call a "black box" environment where they send input into the black box and the magical sorcery of said black box somehow outputs a graphical user interface. A web application should harness the power of the web browser's native capabilities instead of harnessing the powers of a JavaScript framework, because the web is a beautiful place made of beautiful technologies that were built in the golden era of technological creativity. Because of such creativity, many great people came together and invented HTML, CSS, and JavaScript, the near-perfect solution to a very complex, graphically interactive problem. Sure, you may think that you want to build a single-page application, and that's fine. Why not test your skills and write a micro-library that harnesses the HTML5 History API along with a simple AJAX method to transform your website into that single-page app you fantasize about daily. Sure, you may think using the JavaScript "import" keyword in all of your many JavaScript files is okay because it's a single-page app that you're building and all those thirty-some-odd JS files will only be loaded one time for each user session when the app initializes, but what you don't understand is that you are lazy and careless and on a daily basis, you make excuses for all of your sins. You and I both know that your application only needs ONE small JS file on initialization and ONE dynamically loaded JS file for each area within the app that your user may or may not navigate to in the first place. So, for the love of all humanity, please, please, write HTML & CSS, and pepper it ever-so-lightly with bits of JavaScript (but only when it is absolutely necessary). If you need a more convincing argument to steer away from client-side JavaScript frameworks, simply read this small comic strip: https://xkcd.com/927/ https://xkcd.com/927/
- deleted 8y ago
- ww520 8y agoThis looks not much different than the usual way of doing event binding and rendering in the event callback.
- xrd 8y agoThe thing that got me excited about React was (and please correct me if my vocabulary is incorrect) the virtual Dom and the algorithm to detect changes and only update the dom that changes. Does Cycle.js save that same work and reduce bugs, or is there a hidden cost without that kind of feature? To be specific, this reference criticises setState because it does a bunch of implicit updates to subcomponents. I've seen how in practice this does make things hard, but I've also seen the power of it when it works well. Cycle.js looks like it requires me to do a lot more to manage that re-rendering with the benefit of knowing exactly how it is happening. It is a myth that what I've described above actually saves time and reduces bugs? I've loved reading Andre Staltz's posts on reactive programming, they are so good. This is an amazing project. Edit: clarified my comment to add example.
- jakelazaroff 8y agoCycle uses a virtual DOM as well. The difference is that React will call your component functions every time it needs to render, while Cycle will (mostly) only call them once, when the app starts up. A Cycle app is a data flow graph set up by your component functions, with a "driver" at the root observing that data flow and causing side effects, like rendering your virtual DOM or making HTTP requests. In short: instead of a component being called with props and state and returning VDOM, in Cycle it "maps" a stream of state into a stream of VDOM.
- kylecordes 8y agoCycle is very interesting, and Andre is a genius. Even if you will never use Cycle in "real work", learning it enough to build anything at all will enhance your understanding of many things. Particularly around reactivity and state. A while back I dug in enough to make a short series of videos explaining how some simple Cycle apps work. They are not up to date with the Cycle progress since then, but nonetheless are probably still a decent way to get a sense of what Cycle is like. https://www.youtube.com/watch?v=DEHgcT_RmJc&list=PLP4qxCldB2jRJmszTeuS1-zghi60g1vzJ https://www.youtube.com/watch?v=DEHgcT_RmJc&list=PLP4qxCldB2...
- inglor 8y agoAnyone considering cycle should also consider MobX which is effectively "automatic cycle". It's as functional and as Reactive but uses vue-like observables rather than rx-like ones.
- pickle-ts 8y agoI've used cycle.js on a small commercial project. I'm drawn to novel non-mainstream approaches, and am happy to put up with teething pains with new technology. However, my experience with cycle.js was such that I couldn't recommend the framework to others. Central to the understanding of cycle.js is the concept of observables. An observable is powerful abstraction, as it allows event streams to be programmable. However, an overreached abstraction can be even worse than a lack of abstraction. Cycle.js gratuitously applies the observable abstraction to its component model. The cycle.js notion of a component is a object that takes multiple observable sources, and emits multiple observable sinks. I have to admit, at first I didn't see this as problematic: to the contrary - it actually struck me as elegant. What I didn't realize, before building a real-world project with cycle, was that this meant all interactions between components were asynchronous. So each component cannot even access the state of another component without communication via observables. This introduces enormous needless complexity into an application. Not only do you have to pay an up front mandatory boilerplate tax to minimally wire together your components, you'll tie yourself in knots readjusting that wiring to get those components to communicate with each other in the way you need. We actually ended up re-writing the project with our own framework, pickle: https://github.com/pickle-ts/pickle https://github.com/pickle-ts/pickle For us, this substantially increased maintainability / reduced the code size of our application compared with cycle.js, or the prototype we wrote with react/redux. The code reduction came from realizing the appropriate abstraction: the entire application should be composed of an object-oriented hierarchy of stateful components, where on each update, these mutable components render themselves as immutable v-dom trees. Anyway, to counteract any pluggishness (our framework is obviously super new so is only of interest to ultra early-adopters), the conservative sensible choice for a web framework right now is to use react or vue /w a state manager. Not cycle.js.
- Tade0 8y agoWhat I didn't realize, before building a real-world project with cycle, was that this meant all interactions between components were asynchronous. [...] This introduces enormous needless complexity into an application. This hits the nail on the head. I spent a year using the author's other project - Rx.js - and while I understand the benefits of using it I don't think they outweigh the costs. And asynchronicity is especially expensive.
- JSDave 8y ago"const input$ = sources.DOM.select('.field').events('input')" Is there a switchMap in here? What happens if the input element is removed/inserted back into the DOM? "input('.field', {attrs: {type: 'text'}})," It's possible right now for the value in this input to differ from the value in input$. Maybe use a combineLatest and then this? "input('.field', {attrs: {type: 'text', value: inputVal}}),"
- jvanbruegge 8y ago`select().event()` does not use `querySelector` directly under the hood, it filtering and routing all incoming events accordingly. So even if you change the elements, as long as they match the selector, they will be routed correctly. Source: I recently rewrote the DOM driver :)
- bsimpson 8y agoThe core idea of Cycle is that the user is a function that reads views and emits user input events; while the application is a function that reads user input events and emits views. (Hence, the name "cycle.") I thought it was written up in their docs, but this video is what comes up when I search for "user is a function": https://www.youtube.com/watch?v=1zj7M1LnJV4 https://www.youtube.com/watch?v=1zj7M1LnJV4 This framing, that user input and interfaces react to changes in each other, is a nice way to think about interaction design. Ergonomically, I find virtual DOM composition in Cycle a bit too cumbersome. Stateless components are just functions that return virtual DOM, but stateful ones need the input/output streams woven in. In practice, this means you end up doing a lot of const statefulComponent1$ = // compose input streams here return { DOM: combineLatest([ statefulComponent1$, statefulComponent2$ ]).map( ([ partial1, partial2 ]) => ( <StatelessComponent> { partial1 } // ... </StatelessComponent> ) ) } To add state to a stateless component, you've got to do a bunch of refactoring to both the component and its callsite. Instead of passing props/function arguments inline to a stateless component, now you've got to make a variable to hold your stream, pass it into combineLatest, and put a slot in your returned virtual DOM to hold its latest emission. I understand why it's this way, but I also don't like having to think about if a component has state when I'm just trying to use it. Cycle is a nice architecture for Chrome extensions though. They do a lot of message passing between JS contexts. Because everything in Cycle is a stream, you can wrap the extension messaging API in a stream, and your extension architecture looks like any other Cycle project: https://github.com/appsforartists/midicast/blob/develop/packages/cycle-extensions/src/index.ts https://github.com/appsforartists/midicast/blob/develop/pack...
- GirlsCanCode 8y agoWe use Elm to address most of the same issues.
- deleted 8y ago[deleted]
- brucou 8y agoThis is interesting feedback. There are obviously subjective parts in it, but that's valuable all the same. Focusing on the objective parts : An observable is powerful abstraction, as it allows event streams to be programmable. However, an overreached abstraction can be even worse than a lack of abstraction. Cycle.js gratuitously applies the observable abstraction to its component model. Some caveats aside, cyclejs allows you : - to express your app as a series of equations - separate side-effects from pure dataflow processing Whether that is gratuitous or overreached is a question of taste, familiarity and other things, I won't discuss with you on that. What I will say is that under some conditions, the cyclejs approach actually can make your application much easier to understand that standard imperative workflow processing. For instance, I could write for a super simplified reservation system : - screen `=def=` booking_details_screen + booking_feedback_screen - booking_feedback_screen `=def=` is_ok(booking_response) ? nothing : display_errors(booking_feedback_screen) - booking_details_screen `=def=` whatever interface you want - book_button_click `<-read_dom_effect-` = <provided by the DOM> - flight `=def=` read_flight (booking_details_screen) - booking_request `=def=` makeBookingRequest(flight) sampled by book_button_click - booking_response `<-booking_effect-` booking_request The `=def=` relations are equations which are always true. the `<-effect-` relations express the causal relation between asking for a request to be performed and the response resulting from that request. On another note, as you can see from the equations, there is no notions of component. Or if you prefer a component is a variable on the leftside of the equations. So for instance screen is a component with two children components. That is the underlying theory. In practice: - code does not get written as a list of equations, but through doing some juggling with streams operators. Nothing of the other world really, but there is certainly a ceremony to it, and tips, tricks, gotchas to be aware of. - Cycle actually does not have a component model stricto sensu. A component model should at least provide a systematic way to combine components into a hierarchy, i.e. a `combine` function by which `parentComponent` = combine(childrenComponents)`. For instance in React, `reactElement = React.createElement(parentComponent, props, childrenComponents)`. With `cyclejs` you must explicitly write your `combine` function yourself, everytime. That represent probably a portion of the pain you felt. But conversely look at the other approach. Let's imagine a `ParentComponent` with two child components. To reason about your application, you need to know about the end-to-end sequence of operations involved in all the framework's API. So for instance: `render = render Parent -then-> call onRender of Parent -then-> render Child1 -then-> fetch data -then-> render Child2 -then-> etc.`. This is obviously possible, that is how React works and your own proposed framework too - by the way you do not offer a component model either, stricto sensu). Nothing of the other world really, but there is certainly a ceremony to it, and tips, tricks, gotchas to be aware of (yes I copy pasted from before). So in short, your evaluation of how bad the ceremony is, the effort it involves etc. will obviously include factors which are specific to you. So I am not doubting your feedback. My whole point here is to say that your mileage may vary and that there is no a priori superiority of one approach over another (stuff like `realizing the appropriate abstraction` as you mention in your post). There are real pain points both ways and what is appropriate to you might be in part the result of a higher familiarity with one approach over another. If you want to understand what I mean by component model, or want to see what a component model for cyclejs look like, I created my cyclejs component model to address some of the pain points you probably encountered : cf. https://brucou.github.io/posts/a-componentization-framework-for-cyclejs/ https://brucou.github.io/posts/a-componentization-framework-...