☕ 4 min read
Programming with Cycle.js / Rx.js needs you to take another approach to the code. We have to switch your brain to think with a different paradigm − functional and reactive, instead of imperative and passive.
Unfortunately, there is a lack of concrete example, from concrete features, from real-life application.
Since I’m building a tool to analyse Kanban metrics from a Trello board with Cycle.js, I do have concrete examples.
Here’s one use case I want to share with you to illustrate how you deal with the user clicking a button to perform an action when everything is a stream.
However, this metric is contained in the entrails of the web app. This is not convenient if we want to re-use or share these information in any way (e.g., to build a global report for external stakeholders).
What if the data was parsed and we can download a
.csv? That would be awesome.
From a user point of view, we would like to have a “Download” button somewhere close to the graph — probably just below. Clicking on this button will download the .csv of the current graph.
If we change the period/selected lists range and then click on the download button, it should keep matching the displayed graph. We should be able to draw that graph again after we import our data into Excel/GSheet.
In other words: each time a click on the button happens, download in CSV format the latest data.
Let’s interpret this in Cycle.js terms:
downloadClicks$, a stream representing button clicks
csvData$, a stream representing data
There is already a stream containing CFD data since we display them already. We simply need to map them to produce a
csvData$ that matches the input format of
Then, we need to combine our streams to get the latest value from
csvData$whenever — and only when — an item is emitted in
Brace yourself, there is a Rx.js operator for that: withLatestFrom().
This is what we want to achieve:
downloadClicks$: -------x------x----------------x----------> csvData$: ---A---------------B----C--D-------E------> withLatestFrom((clicks, data) => data) downloadData$: -------A------A----------------D---------->
Combining our streams will produce another one (
downloadData$) that will emit an item containing CSV-formatted data whenever a click happens on the button.
Downloading a CSV file from formatted data, this looks like a job for a driver!
Indeed, this kind of operations implies side-effects since the program needs to output something that is visible to the user. This is the part where our application unleash its added value. This is also the part where nasty bugs might lie, regarding the environment, the global state of the machine, etc.
That’s why we prefer to move this part into a dedicated driver, away from our application logic.
Our app is pure dataflow while downloading a CSV file is part of side effects stuff.
Note: I didn’t find an open-sourced “downloadToCsv” driver at the time of implementing the feature, so I build my own for this project. With a very little refactoring, it can easily be open-sourced though.
The driver subscribe to an observable (= stream) input.
Anytime it emits an event containing formatted data, it will perform the side-effect: download the CSV file from these data. Thus, we would simply pass our
downloadData$ stream and we’re done!
And here it is, our “Download .csv” button, waiting for users to click it!
Here is the PR that implemented the feature, in case you want to read the concrete diff.
What are your feelings about this approach? Would you have done it differently? I’d love to discuss about it!