DEV Community

loading...
Cover image for Event Streams for Reactive Views

Event Streams for Reactive Views

Neil Syiemlieh
Just starting my tech journey. Engineer at Gojek.
Originally published at mebble.town ・4 min read

I remember hearing about Bacon.js one day and checking out their Getting Started page. In it, they demonstrate a counter using the Bacon event streaming library. This is the demo code:

var up = Bacon.fromEvent($('#up'), 'click');
var down = Bacon.fromEvent($('#down'), 'click');

var counter =
  // map up to 1, down to -1
  up.map(1).merge(down.map(-1))
  // accumulate sum
    .scan(0, (x,y) => x + y);

// assign observable value to jQuery property text
counter.onValue(text => $('#counter').text(text));
Enter fullscreen mode Exit fullscreen mode

Well this was something very new to me. In my early days of programming, when using jQuery or vanilla JS with the DOM API, I would manually update the view whenever my state changed. Something like this:

let count = 0;
updateView(count);

function updateView(count) {
    $('#counter').text = count;
}

$('#up-button').on('click', () => {
    count++;
    updateView(count);
});
$('#down-button').on('click', () => {
    count--;
    updateView(count);
});
Enter fullscreen mode Exit fullscreen mode

Then when I heard that frameworks such as React would update the view for me, I thought "Great! One less thing to think about!". My code became something like this:

const Counter = () => {
    const [count, setCount] = useState(0);
    return (
        <div>
            <p>{count}</p>
            <button onClick={e => setCount(count => count + 1)}>Up</button>
            <button onClick={e => setCount(count => count - 1)}>Down</button>
        </div>
    );
};
Enter fullscreen mode Exit fullscreen mode

Ooooh it's declarative. No more fragile imperative logic, right? One less thing to think about! Now I've got less code, which means less clutter and possibly fewer bugs. And now there's no way I might accidentally forget to update my view! I just need to write to the state, and the state would write to the view for me!

Events, State, View

Writing to the state is triggered by the user clicking a button. A user clicking a button is an event. In UI code, we get a lot of events. From the user, from the network, from some background task. With these events we decide if and how we write to the state. Then the state shows us what's changed by updating the view.

Events triggering writes to the state, and the state automatically updating the view

This is great. Now we can focus on state management. In many apps, state management is simple enough. If it gets complex, you can try out an event sourcing-ish tool like Redux or a state machine tool like XState.

Events, Transform, Accumulate, View

But it never occurred to me that state management isn't a must. State management seemed like such a smarty pants thing to do, I never asked if you could maybe wire up your view to your events directly.

Get an event, write to the view.

Of course this alone is very limiting. We should be able to transform the event. Remember old events. Process two or three different events to get a single result. Merge and accumulate events. Ignore events that don't matter. If we could do all this, we get all the power we had with state management, without actually doing state management.

Two Sides, Same Coin

My mind was blown when I realised that state management is basically the same darn thing. When you write to the state, you are doing all event-related processing in one go. When your #up-button and #down-button are clicked, they're equivalent to two emitted events, emitted on their own event streams. When they write to the state (i.e. count++ and count--), that's equivalent to:

  1. merging their streams
  2. defining how that merge would affect the events that came before them i.e. it's accumulating them

So the state is like a bucket where you dump all your event-processed results. In the event processing world, the ability to accumulate all events that have happened is equivalent to storing state. Because that's what state is: the state of something after everything that's happened to it.

And that's how I understood what Bacon.js meant when it said:

var up = Bacon.fromEvent($('#up'), 'click');
var down = Bacon.fromEvent($('#down'), 'click');

var counter =
  // map up to 1, down to -1
  up.map(1).merge(down.map(-1))
  // accumulate sum
    .scan(0, (x,y) => x + y);

// assign observable value to jQuery property text
counter.onValue(text => $('#counter').text(text));
Enter fullscreen mode Exit fullscreen mode

Events being transformed, event streams merged and events being accumulated, and the result being used to update the view

This is how we manage a count state in the event-streaming world. State management and event stream processing are two sides of the same coin. You can use either, depending on your requirements. These two paradigms are object-oriented programming's approach and functional programming's approach to the same problem, respectively.

A very popular event stream processing library is RxJS. Their site includes a pretty good guide into this style of programming. There's also rxmarbles, which has interactive diagrams of Rx event streams. I think these are good starting points if you're new to this and want to dive deeper.

Have a nice evening.


If you liked this post, consider viewing it on my site! You'll see other posts I've made and you'll find notes where I document my learnings too!

Discussion (0)