Reactivity and Reactive Programming

| 4 min read

Introduction

Reactive programming has become increasingly popular in the frontend programming world, with the advent of modern browser UI frameworks such as React and Vue, nearly all of which boast "Reactivity" as a core feature.

While it is popular in the web browser world, it is not inherent to it. Reactive programming is a general programming paradigm that can be applied anywhere, without a UI involved. So if you are not a frontend developer, do not be discouraged!

In my opinion, reactive programming is what throws off many backend developers when they attempt frontend programming. Reactive programming can be confusing to the newcomer, as it completely changes the flow of a program. But once you get familiar with it, it can unlock powerful programming patterns and change the way you write your program.

What is reactivity?

Reactivity is a declarative programming paradigm in which changes propagate across layers of data automatically and implicitly. It is declarative because the programmer does not have to specify how to propagate data changes and keep them in sync.

Does not make sense yet? Let us demonstrate with examples!

Example: Spreadsheets

If you are not familiar with spreadsheet software such as Microsoft Excel, skip this section.

Spreadsheet software such as Microsoft Excel have a feature similar to reactivity, so let us check it out. Suppose you have a list of expenses that you are splitting up with some friends. You want to know how much you have spent on each expense. Your spreadsheet could look like this:

Expense NameCostMy ShareNumber of Friends
Restaurant70145
Transportation306
Tickets40080
Total500100

In spreadsheet software, you can often have the ability to insert formulas that make calculations for you. For example, we can tell it that the "My share" column equals corresponding (on same row) "cost" divided by "Number of friends" (regardless of row). We can also tell it that "Total" is the sum of column above it.

Although this feature saves us some calculations that would otherwise be performed manually, it also has another important benefit: Data binding. If I change one of the values in "Cost", the spreadsheet recognizes there is a data dependency and that it must update other data fields. This is because we previously gave it a formula that told it that these data fields are calculated using "Cost". Suppose we found additional restaurant costs and the total is now 120 instead of 70. We only need to update the "cost" column, and if we did things right, the spreadsheet will automatically update "my share":

Expense NameCostMy ShareNumber of Friends
Restaurant120245
Transportation306
Tickets40080
Total550110

We did not have to explicitly tell the spreadsheet to update these columns. This occurs automatically.

Example: Code

Suppose I have 3 pieces of data: A, B, C. Think of them as variables holding the data. Now imagine there is a relationship between them. For example: A = B + C. Here is some sample code:

let b = 1
let c = 2
let a = b + c // a = 3

Now suppose the value of C changes:

if (some_condition) {
c = 4
}

Now, C is 4, B remains at 1, but A also remains at 3. Interesting to note is that the statement a = b + c is no longer true, because if we were to calculate this with the new value of C, we would get 5.

Well that's not a problem, we can just recalculate A again right?

# --- old code ---
let b = 1
let c = 2
let a = b + c // a = 3
if (some_condition) {
c = 4
}
# --- new code ---
a = b + c

But what if C changes again? we would have to add a = b + c everywhere it happens. Surely this is bad programming. What if we can bind the changes to C to A, such that, if it ever changes, A is recalculated and remains in sync with changes in C? That is essentially reactive programming. Let us demonstrate:

// library code
class ReactiveValue {
constructor(val, updater = null, callback = () => {}) {
if (val == null) {
if (updater == null) {
throw Error('Cannot have both value and updater as null');
} else {
this.val = updater()
}
} else {
this.val = val
this.callback = callback
this.updater = updater
}
}

getValue() {
return this.val
}

update() {
if (this.updater != null) {
this.updater()
}
}

setValue(newVal) {
this.val = newVal
if (this.callback != null) {
this.callback()
}
}

setCallback(callback) {
this.callback = callback
}
}

// application code

let b = new ReactiveValue(1)
let c = new ReactiveValue(2)
let a = new ReactiveValue(null, null, function() {
return b.getValue() + c.getValue()
})

c.setCallback(a.update)

b.setCallback(a.update)

console.log(a.getValue()) // 3
c.setValue(4)
console.log(a.getValue()) // 5

Now, anytime C and B are updated, A will update, without explicitly specifying how and when to do so every time we needed. Cool, right?

Different Implementations

To achieve reactivity, our library (the ReactiveValue class) needed a way to figure out the dependencies between data. In our implementation, we had to explicitly tell the program which values are reactive and what the dependencies are. Many implementations will attempt to figure out the dependencies automatically. Some may treat all values as reactive values, and others will attempt to automatically figure out which variables are reactive. The point is, do not get stuck on specific implementations, but take it as a way to demonstrate the concept of Reactivity.

Conclusion

So let us summarize: What is reactivity?

It is a programming paradigm in which the program automatically and implicitly updates data; in other words, without explicit direction from the programmer on how and when to update the data. Implementations may look different from library to other, and even application code that uses these libraries will look very different. The point is that, from the user's perspective, there is some form of automatic propagation of data changes.

In typical imperative programming style, the program flows step by step, line by line. When we assign a value to a variable, it is said and done. We have the confidence that it will never change unless explicitly changed. In reactive programming, the values may change, but the data relations are preserved. This may be unintuitive to a newcomer, and it may not make sense for certain programs. Use it in the right situations, and you will find it to be powerful and useful.