DEV Community

loading...
Cover image for How React isn't reactive, and why you shouldn't care
This is Learning

How React isn't reactive, and why you shouldn't care

ryansolid profile image Ryan Carniato Updated on ・6 min read

If the title agrees with you, you can stop reading right now. Move on to the next article. In technology, we tend to grab on to differences to come up with easily identifiable discussion points even when the truth is less clear-cut.

So save yourself some time and move on if you don't want to put some mostly unnecessary information in your head. But if you are interested in this sort of thing let me give this a shot.

What is reactive programming?

This is the heart of it. If there was ever a more overloaded term... Reactive programming refers to a great number of things and most definitions are pretty poor. Either too specific to a mechanism or too academic. So I'm going to take yet another stab.

Reactive Programming is a declarative programming paradigm built on data-centric event emitters.

There are two parts to this. "Declarative programming paradigm" means that the code describes the behavior rather than how to achieve it. Common examples of this are HTML/templates where you describe what you will see rather than how it will be updated. Another is the SQL query language where you describe what data you want rather than how to fetch it.

SELECT name FROM customers
WHERE city = "Dallas"
ORDER BY created_at DESC
Enter fullscreen mode Exit fullscreen mode

This paradigm can apply to data transformation as well and is often associated with functional programming. For example, this map/filter operation describes what your output is rather than how you get there.

const upperCaseOddLengthWords = words
  .filter(word => word.length % 2)
  .map(word => word.toUpperCase());
Enter fullscreen mode Exit fullscreen mode

The second part is "data-centric event emitter". We've all worked in systems with events. DOM has events for when the user interacts with Elements. Operating systems work off event queues. They serve as a way to decouple the handling of changes in our system from the actors that trigger them.

The key to a reactive system is the actors are the data. Each piece of data is responsible for emitting its own events to notify its subscribers when its value has changed. There are many different ways to implement this from streams and operators to signals and computations, but at the core, there is always this data-centric event emitter.

Common types of reactivity

There are 2 distinct common types of reactivity found in JavaScript. They evolved to solve different problems. They share the same core properties but they are modeled slightly differently.

1. Reactive Streams

This is probably the one you hear about the most but isn't necessarily the most used. This one is based around async streams and processing those with operators. This is a system for transformation. It is ideal for modeling the propagation of change over time.

Its most famous incarnation in JavaScript is RxJS and powers things like Angular.

const listener = merge(
  fromEvent(document, 'mousedown').pipe(mapTo(false)),
  fromEvent(document, 'mousemove').pipe(mapTo(true))
)
  .pipe(sample(fromEvent(document, 'mouseup')))
  .subscribe(isDragging => {
    console.log('Were you dragging?', isDragging);
  });
Enter fullscreen mode Exit fullscreen mode

You can see this stream build in front of you. You can describe some incredibly complex behavior with minimal code.

2. Fine-Grained Signals

This is the one often associated with spreadsheets or digital circuits. It was developed to solve synchronization problems. It has little sense of time but ensures glitchless data propagation so that everything is in sync.

It is built on signals and auto-tracking computations instead of streams and operators. Signals represent a single data point whose changes propagate through a web of derivations and ultimately result in side effects.

Often you use these systems without realizing it. It is the core part of Vue, MobX, Alpine, Solid, Riot, Knockout.

import { observable, autorun } from "mobx"

const cityName = observable.box("Vienna")

autorun(() => {
    console.log(cityName.get())
})
// Prints: 'Vienna'

cityName.set("Amsterdam")
// Prints: 'Amsterdam'
Enter fullscreen mode Exit fullscreen mode

If you look, cityName's value looks like it is actually being pulled instead of pushed. And it is on initial execution. These systems use a hybrid push/pull system, but not for the reason you might think. It is to stay in sync.

Regardless of how we attack it, computations need to run in some order, so it is possible to read from a derived value before it has been updated. Given the highly dynamic nature of the expressions in computations topological sort is not always possible when chasing optimal execution. So sometimes we pull instead of push to ensure consistency when we hit a signal read.

Also worth mentioning: Some people confuse the easy proxy setter as being a sure sign something is reactive. This is a mistake. You might see city.name = "Firenze" but what is really happening is city.setName("Firenze"). React could have made their class component state objects proxies and had no impact on behavior.

Which brings us to...

Is React not reactive?

Well, let's see about that. React components are driven off state, and setState calls are sort of like data events. And React's Hooks and JSX are basically declarative. So what's the issue here?

Well actually very little. There is only one key difference, React decouples the data events from component updates. In the middle, it has a scheduler. You may setState a dozen times but React takes notice of which components have been scheduled to update and doesn't bother doing so until it is ready.

But all of this is a type of buffering. Not only is the queue filled by the state update event, but the scheduling of processing that queue is as well. React isn't sitting there with some ever-present polling mechanism to poll for changes. The same events drive the whole system.

So is React not reactive? Only if you view reactivity as a push-only mechanism. Sure React's scheduling generally doesn't play as nice with push-based reactive systems as some would want but that is hardly evidence. It seems to pass the general criteria. But it is definitely not typical reactivity. Know what else isn't? Svelte.

Strawman Argument

When you update a value in Svelte in an event handler and happen to read a derived value on the next line of code it isn't updated. It is definitely not synchronous.

<script>
  let count = 1;
  $: doubleCount = count * 2;
</script>
<button on:click={() => {
  count = count + 1;
  console.log(count, doubleCount);  // 2, 2
}}>Click Me</button>
Enter fullscreen mode Exit fullscreen mode

In fact, updates are scheduled batched and scheduled similarly to React. Maybe not interruptable like time-slicing but still scheduled. In fact, most frameworks do this sort of batching. Vue as well when talking about DOM updates. Set count twice synchronously and sequentially doesn't result in Svelte updating the component more than once.

Taking it a step further, have you seen the compiled output of this? The important parts look like this:

let doubleCount;
let count = 1;

const click_handler = () => {
  $$invalidate(0, count = count + 1);
  console.log(count, doubleCount); // 2, 2
};

$$self.$$.update = () => {
  if ($$self.$$.dirty & /*count*/ 1) {
    $: $$invalidate(1, doubleCount = count * 2);
  }
};
Enter fullscreen mode Exit fullscreen mode

Unsurprisingly $$invalidate is a lot like setState. Guess what it does? Tell the component to call its update function. Basically exactly what React does.

There are differences in execution after this point due to differences in memoization patterns and VDOM vs no VDOM. But for all purposes, Svelte has a setState function that re-evaluates its components. And like React it is component granular, performing a simple flag-based diff instead of one based on referential value check.

So is Svelte not reactive? It has all the characteristics we were willing to disqualify React for.

Summary

This whole line of argument is mostly pointless. Just like the argument of JSX versus custom template DSLs. The difference in the execution model can be notable. But Svelte's difference isn't due to reactivity but because its compiler separates create/update paths allowing skipping on a VDOM.

React team acknowledges that it isn't fully reactive. While that seems like it should be worth something, in practice it isn't that different than many libraries that claim to be reactive. Sure, React Fiber takes scheduling to the extreme, but most UI Frameworks automatically do some amount of this.

Reactivity isn't a specific solution to a problem, but a way to model data change propagation. It's a programming paradigm. You can model almost any problem with reactive approaches. And the sooner we treat it as such the sooner we can focus on the problems that matter.

Discussion (23)

pic
Editor guide
Collapse
peerreynders profile image
peerreynders • Edited

Its most famous incarnation in JavaScript is RxJS ...

From the ReactiveX Introduction

It is sometimes called “functional reactive programming” but this is a misnomer. ReactiveX may be functional, and it may be reactive, but “functional reactive programming” is a different animal. One main point of difference is that functional reactive programming operates on values that change continuously over time, while ReactiveX operates on discrete values that are emitted over time. (See Conal Elliott’s work for more-precise information on functional reactive programming.)

Essentially ReactveX ceded the "FRP" (Functional Reactive Programming) moniker to Conal Elliot's work stating:

(Reactive Programming + Functional Programming) !== Functional Reactive Programming

Back in 2014 - Comment

UPDATE: there's been a lot of confusion around the terms Functional Reactive Programming and Reactive Programming [1] [2].

Sorry, my bad. I guess this sort of confusion happens easily with new paradigms in computing.
Replace all the occurrences of "FRP" with "RP" in the tutorial. Functional Reactive Programming is a variant of Reactive Programming that follows Functional Programming principles such as referential transparency, and seeks to be purely functional. Other people are better at explaining this than I am. [3] [4] [5]

Collapse
ryansolid profile image
Ryan Carniato Author

I see.. I read Staltz' articles but missed his correction. I see I'm a victim of the same confusion. So FRP is yet a different concept. In some ways though this only continues to add to the confusion here. I don't even know what to name it then.. In JS we tend to mainly have the 2 forms Rx and Fine-grained (I've also heard it called SRP). Mind you that description makes FRP sound more like what I've been calling fine-grained.

Someone in academia will probably have to correct us. This has propagated to the point there is even some ambiguity in later white papers. Most of the language I use I reference from this paper on Elm: elm-lang.org/assets/papers/concurr...

But admittedly I'm a little grey where the exact distinction lies. Which is really the heart of the problem here. Calling out reactive or not is more about marketing than anything else.

Collapse
peerreynders profile image
peerreynders • Edited

With xstream Staltz converged on the term "reactive streams". And while I personally like "Rx" it's likely going to be associated with ReactiveX specifically or worse constantly have people quip about "medical prescriptions".
Conal Elliot actually stated this about Elm:

I may have said that Elm was an offshoot of E-FRP iiuc. Sort of inspired-by-inspired-by-FRP.

A Survey of Functional Reactive Programming (2013)

Once signals were dropped in Elm 0.17 (May 2016) that discussion simply faded.


OK that is what this is about!

Truly reactive

No more complex state management libraries — Svelte brings reactivity to JavaScript itself.

Yes, there is a case here that the terminology is imprecise and perhaps even sloppy for the purpose of hype and marketing.

Unfortunately for the target audience "reactive" expresses exactly the message that Svelte wants to convey - "change propagation analogous to that perceived with spreadsheet cell formulas" on the language level - right in your Java(Svelte)Script (which reminds me of Simon Peyton Jones referring to Excel as The world's most popular functional langauge).

But Svelte's difference isn't due to reactivity but because its compiler separates create/update paths allowing skipping on a VDOM.

This description is far more accurate - but it's my sense that in terms of a DX-obsessed audience it's just not as catching to accuse React of doing too much work at run-time when it could be doing it much more effectively at compile time.

So I guess Svelte's claim to reactivity is just as valid as React's claim that it isn't a framework (sorry, it is).

Thread Thread
ryansolid profile image
Ryan Carniato Author

Yeah exactly my point and thanks for digging a bit more. I'm going to edit the terminology in the article. It was my mistake for taking the FRP thing at face value. I will stick with our JavaScript made up terms rather than academic ones to avoid further confusion. I'm having a similar discussion on reddit where someone was really put off by me calling Rx FRP and even calling these different things. In JavaScript there are really 2 different types that have developed out but they are only small part of the spectrum.

I believe it is:
Reactive Streams - Asynchronous Events in continuous time
Fine Grained - Synchronous Behaviors in discrete time

But realistically you can have different combinations of these I suppose.

Thread Thread
trusktr profile image
Joe Pea

Qt's used-to-be-fine-grained reactivity just went from sync to async in a major version bump. Naming aside, I still look at it as the same thing, just everything is batched now.

Thread Thread
ryansolid profile image
Ryan Carniato Author

I've continued this line of thought over to a follow-up article: dev.to/ryansolid/what-the-hell-is-...

Collapse
brucou profile image
brucou • Edited

I like those argumented considerations! I have my own definition of reactive programming and functional reactive programming, but I can't agree more on the fact that we have to prioritize solving actual problems vs. debating the language. At the same time, in places where language do matter, we also have to give precise definitions, else we can talk past another for ages. And it does not matter if my definition is not yours, as long as I can precisely state mine and get understood when it matters.
If I may just add two considerations (that also matter very little), reactive systems change behaviors ONLY as a result of events (sometimes they call that stimuli). That means that if a reactive system does nothing, and receives no events, it continues to do nothing. Also there is an expectation that the reactive system keeps listening on events for processing. That is, there is a notion of liveliness: if something happens, the reactive system will react to it (eventually). I guess that is not really a point that will surprise anybody, but it is seldom mentioned and yet it is at the core of what a reactive system is --- if you remove any of these two things, you can still call your system a reactive one (and it still does not matter) but it becomes confusing.

Collapse
ryansolid profile image
Ryan Carniato Author

Yeah probably a better way of saying what I was trying to about data centric event emitters. And really my point about React not sitting there polling. Even if it is scheduled it only does so because of a data change event. React does nothing once settled if nothing calls setState.

Don't get me wrong. I am not saying definitions around Reactivity don't matter, or that I like misappropriation. More that by almost any criteria you would consider a modern UI framework reactive, React is as well. There is a really narrow slice where the argument could be made but it is splitting hairs.

I think the characteristics around events you describe are essential. The system needs to be driven off events over time. Sidestep events or "over time" and it isn't really reactive. But I'm open to hearing other opinions here. Just the Wikipedia definition is almost useless.

Collapse
brucou profile image
brucou • Edited

Fair enough. The same words may mean different things in different contexts so always good to take a tour of the landscape. IBM calls reactive systems systems that follow the reactive manifesto: reactivemanifesto.org/

Quoting:

We believe that a coherent approach to systems architecture is needed, and we believe that all necessary aspects are already recognised individually: we want systems that are Responsive, Resilient, Elastic and Message Driven. We call these Reactive Systems.

So if you talk about reactive systems to a systems architect, that is likely to be what he will understand.

If you look at academia, it is closer to what I mention, i.e. a system that continously react to external stimuli. quoting ce.pdn.ac.lk/research/complex-reac...

For these complex reactive systems, which continuously react to external stimuli (called events), we need methods and tools that permit specifying them in a precise, easy and safe way,

or here: ercim-news.ercim.eu/en67/special-t...

Many embedded systems belong to the class of reactive systems, which continuously react to inputs from the environment by generating corresponding outputs.

There is like a thousands of the same definition in papers as those folks must agree on the meaning of words before they even start to write research.

Then if you talk to web developers they will understand something else. Then how you define reactive programming is going to depend on how you define reactive systems.

When I said it does not matter, what I really mean is what matters is that you get understood, whatever words you choose to carry your meaning.

Thread Thread
peerreynders profile image
peerreynders • Edited

Then if you talk to web developers they will understand something else.

In my estimation that is what Svelte is talking about when it uses the term "reactive". It takes the perspective of the "developer as a user" differentiating between "explicit change propagation" (e.g. setState()) vs. "implicit change propagation" (i.e. "reactive"). From the developer perspective Svelte has two "reactive" systems:

Example: Reactive declaration

const schedule = (delay, fn) => self.setTimeout(fn, delay);

let a = 5;

// non-reactive declaration
let b = a + 3;

// "reactive" declaration
$: bR = a + 3;

console.log(bR === 8);

schedule(100, () => {
  a += 2;
});

schedule(200, () => {
  console.log(a === 7);
  console.log(b === 8);
  console.log(bR === 10);
}); 
Enter fullscreen mode Exit fullscreen mode

So through a little syntax sugar blocks of code can become "reactive - just like you're used to from spreadsheets".
The primary appeal here is that reactivity is gained with very little adjustment of the imperative mindset of managing the flow of control - except that now reactive code automatically stays in sync with its dependencies without any explicit change propagation.
This seems to be the reactivity that most of Svelte's buzz revolves around while the "developer as a user" persona cares very little how it's implemented under the hood.

Example: Stores

import { tick } from 'svelte';
import { derived, readable, get } from 'svelte/store';

const schedule = (delay, fn) => self.setTimeout(fn, delay);

let a = readable(null, set => {
  let value = 5;
  set(value);

  schedule(100, () => {
    value += 2;
    set(value);
  });
});

// non-reactive use
// Note use of `get()` is discouraged - used for demonstration only
let b = get(a) + 3;

// "reactive" use
let bR = derived(a, $a => {
  const value = $a + 3;
  return value;
});

// use reactive declarations to use auto-subscriptions
$: aLast = $a;
$: bRlast = $bR;

tick().then(() => {
  console.log(bRlast === 8);
});

schedule(200, () => {
  console.log(aLast === 7);
  console.log(b === 8);
  console.log(bRlast === 10);
});
Enter fullscreen mode Exit fullscreen mode

Stores require a bit more explicit setup though auto-subscriptions are supposed to make consuming them more convenient. However propagation of change at the source store has to be explicitly managed by developer code - so this requires a departure from the usual "imperative flow of control" coding.

Example: Stores with explicitly managed subscriptions

import { onDestroy } from 'svelte';
import { readable } from 'svelte/store';

const schedule = (delay, fn) => self.setTimeout(fn, delay);

let a = 5;
const aStore = readable(null, set => {
  set(a);
  schedule(100, () => {
    a += 2;
    set(a);
  });
});

// non-reactive
let b = a + 3;

// "reactive" subscription
let bR;
const unsubscribe = aStore.subscribe(value => {
  bR = value + 3;
});

console.log(bR === 8);

schedule(200, () => {
  console.log(a === 7);
  console.log(b === 8);
  console.log(bR === 10);
});

onDestroy(unsubscribe);
Enter fullscreen mode Exit fullscreen mode

Without auto-subscriptions consumers have to explicitly subscribe (and unsubscribe) to stores so we're even further removed from the "imperative flow of control" model.

So stores are available for more complex use cases but would generally be judged as the least wieldy of the two options.

It's also my opinion that the adoption of RxJS was ultimately limited by the need to shift ones coding mindset away from manipulating the flow of control at runtime to writing code that largely scaffolds the transformation and routing of event streams that - once constructed - just work.

The advantage of Svelte's reactive declarations/statements is that such a shift in mindset simply isn't required - it reminds me of how the majority of developers seem to prefer using async/await (maintaining the illusion of synchronous execution) over raw Promises.

Collapse
artydev profile image
artydev

Thanks Ryan
What do you think of Meiosis pattern?

Collapse
ryansolid profile image
Ryan Carniato Author

I think global stores are important and it has a solid foundation on FRP. It's homogenous all the way down which is important for any sort of scaling. The only real conflict (as always with these sort of things) is how much should be using the frameworks own primitives versus a 3rd party.

There is this thought that we can use this to make our code transferable. And it's sort of true but it is more just acknowledging Meiosis as the framework as you are invested in it. You might be able to move from React to Vue with it, but you are tied to Meiosis. This is true of any invasive state management approach (ie.. uses specialized primitives to propagate change)

Built-in primitives like Hooks, or other reactive APIs give the framework some places to optimize and offer unique features through their ability to manage orchestration. So that would be the only thing I'd caution on buying in too much on state specific solutions. It's probably fine in either case, but understand there are tradeoffs. When say React introduces concurrent rendering in SSR maybe Meiosis doesn't support that (not saying it doesn't, just hypothetical). That's the sort of consideration I think you have to make.

Collapse
artydev profile image
artydev

Thanks you very much for jour detailed answer

Collapse
dodiameer profile image
Mohammed Ali Agha

In my opinion, the difference that sets apart Vue and Svelte from React is that you don't call setState in them, that is what makes them reactive.

React lets you take control and call setState when you feel like it, while Vue and Svelte abstract it away behind a runtime/compiler to give you a reactive feel

Collapse
ryansolid profile image
Ryan Carniato Author • Edited

I know a lot of people think that way. But that's just a syntax consideration. Reactive JavaScript libraries predate getter/setters and proxies. Like Knockout or earlier versions of Svelte, or countless other examples.

Does this hook make React any more or less reactive?

function useAssignableState(init) {
  const [state, setState] = useState(init);
  return {
    get value() { return state; }
    set value(v) { setState(v); }
  } 
}

// use like:
const count = useAssignableState(5);
count.value++:
Enter fullscreen mode Exit fullscreen mode

All of these do scheduling in the background depending on the library they are present in and are effectively identical:

setCount(5);

count(5);

count.set(5);

count = 5;

count.value = 5;
Enter fullscreen mode Exit fullscreen mode

Calling setState gives you no more or less guarantees than assigning a value in Svelte or Vue. It means that at minimum sometime in the future the state of your application will reflect this change. Calling setState 10 times or assigning 10 different values is all the same for the libraries we are talking about.

I respect syntax preferences and there are direct consequences to opting into single primitives versus the clear read/write segregation of React. But none of this has to do with reactivity.

Collapse
goddard profile image
Ryein Goddard

Svelte looks to be a good option.

Collapse
ryansolid profile image
Ryan Carniato Author • Edited

For what? My point is that Svelte claim for Truly Reactivity vs React is basically a wash. Either they are both reactive or neither of them are by all but the narrowest (invented) criterion. So differentiating on that is mostly pointless outside of Svelte pointing out that they have built their primitives into the language instead of using an API.

Collapse
goddard profile image
Ryein Goddard

It just looks good. I like the simplicity. I always thought React was overly complicated and turns out I was right. Some projects start out with the best intentions, but it is easy to recognize a superior solution. I think one sad thing in web development is the stack is constantly changing. It is hard to really get a grasp on everything. That is probably the main reason people don't want to invest in Svelte as they already learned React which was a chore. At least that is how I would feel.

Thread Thread
ryansolid profile image
Ryan Carniato Author • Edited

Right but is the simplicity is any more real? Keep this in mind from someone who understand in depth how both the frameworks work (enough so that I could write an approximation of either from scratch). Once you peel back the layers not actually that different.

This works in Svelte's favor from an adoption side as since people are just starting to understand it is just as capable as React because I think people were hesitant not only because they are done learning, but because there was this preconception that it was somehow limited. Svelte is not limited in any sort of absolute way. Some problems require explicit not automatic solutions (like using Svelte stores). This fills the gap. Once you go there you can view Svelte's language feature more of an easy onboarding tool. It's a progressive learning thing. Once you get deep into it the biggest DX advantage of Svelte I feel is that the update model isn't convoluted, not some sort of perceived syntax benefit.

But obviously I have a bias I wrote Solid on the philosophy that React has it right by showing it's primitives and giving real control to the developer. Acknowledging the limits of compiled frameworks and taking no concessions from either side of the argument only the absolute power and performance. And I work on Marko pushing the limits of what a compiled framework can do. The choices Svelte has made are only too real.

Svelte does do somethings better than React and I actually think that it is really good at a lot of things people don't realize because of the pre-occupation with syntax. But most complexity in React is local scoped, and architecturally Svelte isn't really avoiding the complicated problems in any new way. Which is fine. I think what we are mostly seeing currently is that the size and scale of the average Svelte project is a lot smaller and we aren't hitting the same sort of problems yet. Then the solutions will come and the tradeoffs recognized.

React gets a bit of a bad rap at times, but the problems that come about in large/complicated systems go beyond the ability of any out of the box solution that comes with most minimal frameworks. These always need special consideration, and for now this almost blissfully naive ignorance will help Svelte gain the popularity it needs to get the support to attack these sort of problems. It takes time and the work of tons of people to get a framework ecosystem to where React is and Svelte has shown it is on the fast track. It is only a matter of time.

Collapse
steve8708 profile image
Steve Sewell

Amazing article, really great breakdown and read

Collapse
shriji profile image
Shriji

Svelte's syntax is simple.

Collapse
ryansolid profile image
Ryan Carniato Author

It definitely is.

Collapse
johnkazer profile image
John Kazer

There is a reasonable amount of literature around reactive programming using clojure and clojurescript. Which can use react too.