DEV Community

Discussion on: What happened to Components being just a visual thing?

Collapse
 
peerreynders profile image
peerreynders • Edited

Don't Make Me Think applies for developers just as well as end-users. That's why technologies that feel familiar, like JSX, have generally won.

I argue that this type of mindset is part of the problem.
"Don't make me think" is about site/app design - ensuring that users will arrive at their ultimate objective with a minimal amount of friction. "Don't make me think" in the developer world isn't a is a drive towards nirvana but towards assembly line work (which is easy and mindless and ultimately automated into oblivion).

Being a developer is about solving problems - primarily to solve a domain problem while secondarily resolving any technological challenges that may be relevant in that particular problem environment. So being a developer is very much about thinking. Some tools support that by reducing the tedium that distracts from focusing on the important issues. Other tools restrict thinking because they force decisions that really need to be made explicitly for that particular problem context (which is why some tools are not a good fit for some problems, irrespective of your level of expertise with those tools).

Familiarity may drive popularity but it often does not lead to technical excellence (i.e. popularity is rarely a reflection of quality or relevance). Technical excellence is achieved by focusing on solving the actual underlying problem rather than inventing new ways to more quickly sweep tough decisions under the rug.

But could we at least: name same things the same, and different things different?

The fallacy here is that component as a context free concept actually ever manifested any unambiguous properties. According to the glossary of The Unified Modelling Language User Guide (1999) a component is:

A physical and replaceable part of a system that conforms to and provides the realization of a set of interfaces.

Note there isn't even a reference to the granularity of a "component".
What people want from components is the "ease" of replacing an analog-era speedometer in an automotive dashboard (which was installed on an assembly line in the first place) - and perhaps the aftermarket modifiability/customizability that is inherent to that design. Meanwhile a "React Component" is nothing like a "Web Component" - and therefore not interchangeable.

We've moved away from separating the view based on structure (HTML), styling (CSS) and behaviour (JS) - which was a good move.

There are tools that make that choice for you but web browsers still make that separation because it is a foundational aspect of the web's approach to fault tolerance and resilience in the face of The Fallacies of Distributed Computing. Conceptually the component model is attractive because it can limit the the amount of information that you have to "keep in your head" at any one time - but that is simply not the way a browser deals with the assets it processes. The component model is for the benefit of the human designer/developer - not the technology (browser) that consumes it.

The Real Cost of UI Components:

Rich Harris, the author of Svelte, made the claim that “Frameworks aren’t there to organize your code, but to organize your mind”. I feel this way about Components. There are always going to be boundaries and modules to package up to keep code encapsulated and re-usable. Those boundaries are always going to have a cost, so I believe we are best served to optimize for those boundaries rather than introducing our own.
...
I think Components should vanish in the same way as Frameworks.

Going back to the analog speedometer analogy - even if all the parts were made by the same manufacturer some very different technologies were used to manufacture that component. Meanwhile a web browser doesn't deal in "components" as "raw materials" - in order of importance it deals with:

  • HTML (What to put on the screen)
  • CSS (How it appears on the screen)
  • JS (How it reacts in response to interaction)

i.e. there are no runtime benefits to component boundaries in the browser. Ideally the browser should receive in order:

  1. All the HTML necessary for the first client render (perhaps asynchronously in parts - Island Architecture).
  2. At minimum the CSS to support that first render (but perhaps more).
  3. And last the JS necessary to make the render interactive - by hooking into the DOM as it was rendered by the HTML rather than creating it from scratch.

Ultimately this points to tooling where "components" only exist at design time (if at all), not runtime. This requires some kind of design time compiler.

To my knowledge Marko.js is the only stack that is currently heading in that direction. Given their limited resources it makes sense that they stick to an isomorphic solution (JS & node JS). In order to support greater (and more performant) choices on the server side in the long term I would like to see the development of a programming language agnostic design-time templating solution that can transform page (or island) templates to host language functions that given the necessary data will render the resulting HTML near-instantaneously.

In conclusion: "visual components" may not be all they're cracked up to be even if they seem to be the popular choice in web development right now.

Collapse
 
redbar0n profile image
Magne • Edited

So being a developer is very much about thinking. Some tools support that by reducing the tedium that distracts from focusing on the important issues.

Sure. And with "Don't make me think", I'm arguing for reducing that tedium, to improve the DX. The point is to rely on a developer's intuition, in cases where the problem cannot be automated away. If it can be automated away, then sure, all the better.

The fallacy here is that component as a context free concept actually ever manifested any unambiguous properties. According to the glossary of The Unified Modelling Language User Guide (1999) a component is:
"A physical and replaceable part of a system that conforms to and provides the realization of a set of interfaces."

Sure. In general parlance a Component can mean just about anything. I am addressing the use in a web development context, specifically with the original intention behind "Component" as introduced by React (as I've tried to evidence in my article). "React Components" and "Components" that are derived from, or attempts to bear similarity to, React Components, is what I'm talking about.

The component model is for the benefit of the human designer/developer - not the technology (browser) that consumes it.

Completely agree. I am simply arguing for the consistency in usage of the first. As I see it, diluting the concept of a (React-style) "Component" is reducing the benefit to you as a human designer/developer to "limit the the amount of information that you have to 'keep in your head'", as you said. I am seeing "Component" as regressing from a strong (and potentially even stronger) abstraction, to a leaky one.

tooling where "components" only exist at design time (if at all), not runtime.

Completely on board with that, if it makes runtime faster and better.

Collapse
 
peerreynders profile image
peerreynders • Edited

I am addressing the use in a web development context, specifically with the original intention behind "Component" as introduced by React (as I've tried to evidence in my article).

Yes - but your article is also based on the premise that the concept of a "React Component" is desirable in the web development context - likely due to its perceived popularity - and the second part of my comment was starting to challenge that. I think once you consider the nature of the web as a distributed system and the way browsers actually work (and the full range of capabilities they offer) the notion of a "React Component" (as it is typically implemented) is not ideal for operation on the web. Just because it's popular doesn't mean it's optimal or perhaps even appropriate.

 // Using JSX to express UI components.
var dropdown =
  <Dropdown>
    A dropdown list
    <Menu>
      <MenuItem>Do Something</MenuItem>
      <MenuItem>Do Something Fun!</MenuItem>
      <MenuItem>Do Something Else</MenuItem>
    </Menu>
  </Dropdown>;

render(dropdown);
Enter fullscreen mode Exit fullscreen mode

Over four years ago I looked for the first time more seriously at React. What immediately struck me as odd:

Why are the components organized as a tree?

Of course, an implementation detail was leaking into the design - as a component will ultimately render to a DOM fragment (well, fragment of React Elements), each component is bound to its rendering position within the page (or app).

Now when assembling micro-components from nano-components the owner-ownee communication is often sufficient to make things work but the higher you go up in component granularity the more limiting communications within the component tree becomes - as evidenced by the groaning about "prop-drilling", Redux being included by default, React Context becoming the app's information bus, etc.

However because JSX "looks just like markup" people quickly accepted the "JSX component tree" as an intentional, "by design" feature - rather than realizing that it is a terrible way to organize components that may need to interact with one another in very different ways.

Then there is the practice of building React components that "do all the work" - i.e. rather than focusing on all matters DOM (i.e. presentation and interaction events) they take care of actual app logic and even fetch their own data from the server - ignoring all sorts of system design wisdom that lead to approaches like the Hexagonal Architecture.

The root of this particular problem is that despite its claims to the contrary React is a framework - once you call ReactDOM.render() React is in full control of the UI thread (the only thread in JavaScript unless you use web workers) and it's not going to give it back - so the only way to get any work done is inside a React component (or something a React component calls). So the same forces that lead to the creation of God Classes on the back end lead to "God Components" in React applications - only there it seems to be accepted practice rather than being identified as an anti-pattern. Now hooks may make some components seem less "god-like" simply because some functionality has now been repackaged as a hook - but this isn't a standard refactoring into a function, closure, class or module but a hook which is a React specific (and supported) code/logic organization mechanism.

Ultimately these "God Components" ignore GUI architecture insights that lead to the Humble View (2006) which later gave rise to the Segregated DOM (2014: Keeping jQuery in Check).


The actual "components" in the web development context are rather unexciting.

The HTML fragment (partial)

Just a chunk of markup containing some contextually related data structured for presentation to the viewer possibly exposing some event sites. In terms of code there is the distinction between the actual markup and the data that is placed within it - ultimately leading to the notion of a template. While it is necessary to allow for conditional and repeated data, in general "logic" should be avoided (Rule of Least Power vs. The Case Against Logic-less Templates).

The CSS Block

While a block can align with an HTML fragment( component) it doesn't have to; it could just as easily target some aspect inside multiple distinct HTML fragment( component)s or style a particular aggregate of various HTML fragment( component)s. Also blocks come in two flavors:

  • structure
  • skin

Structural blocks tend to align with HTML fragment( component) boundaries as they give them structure (spacing etc.). Without a skin the "look" of the block will be based on the current global styles (which may be the exact "look" that is needed). Applying a "skin" is akin to snapping a face plate onto a gaming console.

The JS function, class, module etc.

Whatever it takes to add the required interactivity to the relevant section of the DOM created from the server rendered HTML - perhaps with the intent to later replace it with client rendered DOM - but only when and if required. One interesting way of JS taking control of server rendered DOM is Andrea Giammarchi's regularElements. Again the JS component boundary may align with a HTML fragment( component) boundary but something rendered on the server side from a single data aggregate could on the client side easily be controlled by various client-side JS components.

The important thing to notice is that component boundaries of the various aspects don't always align.

Now something like this can already be managed manually today - though it would be tedious as hell. Ultimately design time tooling could help to weave all these elements together in a coherent fashion - but it is conceivable that Content/Markup, Visual Design and Interaction will be managed as separate aspects (i.e. components) rather than one unified UI component in order to optimize the boundaries of each aspect.

The other issue with contemporary runtime components is the conflation of server and client logic to support SSR inside the same component leading to accidental complexity.

Post-compile time there should be two separate artifacts:

  • server artifact - simple from the perspective that it only needs to render HTML and it should be free to acquire the required data any way the server deems fit (for performance reasons) - i.e. it is not coupled to the client's method of acquiring data.
  • client artifact - only deals with client side concerns - no "mixed-in" logic to support SSR.

How that separation is handled at design time depends on what (and how) "components" are being managed at design time.


When you express the sentiment that:

I feel that putting all sorts of logical components into the rendering is abusing the original idea of components.

you may simply be finally realizing the real limitations behind the React component model.

Thread Thread
 
redbar0n profile image
Magne • Edited

Great insights, thanks for sharing!

It took me a while to process it all, read up on the references you shared, and reflect on it.

The references to 'The Rule of Least Power' and 'The Case Against Logic-Less Templates' were particularly elucidating.

You mention, very interestingly:

Why are the components organized as a tree?
Of course, an implementation detail was leaking into the design

What do you think would be the solution to this? Is there a non-tree structure that works, and doesn't become the wild west?

Thread Thread
 
peerreynders profile image
peerreynders • Edited

Is there a non-tree structure that works, and doesn't become the wild west?

Segregated DOM/Keeping jQuery in Check (which allude to the Humble Dialog) essentially boil down to keeping your "view components" as thin possible.

Just enough logic to "hook into" the DOM from the browser-parsed, server-sent HTML, to modify that area of the DOM and some means to "rendezvous" with the application component that sources the data to display and sinks the interaction commands. This way the "application components" (including state management) are free to organize around the necessary communication patterns - not their visual organization on the page.

I argue that the whole Presentational and Container Components discussion in React is related to this - however the concept wasn't taken to its logical conclusion: only the (dumb) presentation components belong in the React component tree - the "application" belongs somewhere else entirely.

This glitch attempts to leverage the organizing principles of the "actual components in web development":

  • disclosure-toggle.js - JS code that takes control of the disclosure-toggle HTML fragment with the help of Andrea Giammarchi's regular-elements (in main.js). Hypothetically this could manipulate the DOM within the fragment especially via template cloning or using something like Nunjucks or lit-html (similarly hyperHTML) but it's just not necessary in this case. Similarly it could "connect" to an "application component" to act as a data source and command sink - but this is just a simple example. This "component" is organized as an ES module (rather than a class) which works fairly well with today's bundlers.
  • disclosure-toggle HTML fragment - server-rendered (or generated), itself composed of the link-button and panel HTML fragments which are styled by link-button.css and panel.css (note that the selectors have a c- prefix identifying them as belonging to the CSS component namespace, o- identifies "objects", js- javascript hooks (don't use data attributes) which are not to be used for styling) respectively.

As I said before, tooling could alleviate some of the tedium of weaving all these elements together.

Thread Thread
 
redbar0n profile image
Magne

I agree that the view components should be as thin as possible. From what I can see, the hooks scenario is exactly what React hooks attempts to solve. The freeing of the communication between components seems what React Context, Redux and Recoil are solving. Yes, the former is still in the render hierarchy, and the two latter are third party libraries, so it would be nice to not need them.

But isn't it good that React at least gives some organisational structure to the code. Otherwise I imagine, when jumping into a new codebase, there would be components all over the place, communicating in any kind of way. That would make debugging much harder, no?

I'm not sure what the disclosure-toggle example is supposed to show. What is the glitch? Is it that it can set the disclosure/ToS as initially hidden, whereas it otherwise would be open by default?

Thread Thread
 
peerreynders profile image
peerreynders • Edited

From what I can see, the hooks scenario is exactly what React hooks attempts to solve.

Ultimately it doesn't free you from React though does it?

Why should the application portion of your client side logic care about React?

React's job is supposed to be confined to managing the VDOM - not to provide or run the infrastructure for your client side application.

But isn't it good that React at least gives some organisational structure to the code.

Structure is good but the right structure is better. Ultimately this is Architecture the Lost Years all over again. In the same manner as all Rails apps looked the same, all React apps tend to look the same but that gives no hint whether or not the final app is actually fit for its intended purpose or maintainable in the long run. Eventually some organizations maintaining Rails sites realized that the default structure was sub-optimal and started to challenge the status quo: Bring clarity to your monolith with Bounded Contexts.

Similarly people are bound to discover sooner or later that having React dictate the structure of their client side application may not always be in their best interest.

What is the glitch?

"Glitch" simply refers to the hosting service "glitch.com".
Clicking on the "two fish" (the Glitch logo) to the right takes you to the project view to view the files.

whereas it otherwise would be open by default?

That is the point of the "progressive disclosure component" as designed by Andy Bell; even when JavaScript fails - for whatever reason - no information or access is lost to the user.

The point of the glitch was to present an extremely simple example of "the actual components on the web".

In React disclosure-toggle.js and the disclosure-toggle HTML fragment would be fused into the same component - because they are part of one and the same "component" right? But this ignores the strengths of the browser which is much better at parsing HTML and generating a DOM from it rather than parsing and executing JS to create the DOM out of thin air.

So the JS and HTML fragment are actually separate "components" which at some point have to come together to form the disclosure-toggle "system" that exhibits the desired interaction behaviour. Similarly link-button and panel are "components" with an HTML and CSS aspect but no JS aspect because that has to be supplied by the containing "component" - in this case disclosure-toggle.js.

A lot of tools that have gained popularity recently have prioritized author-time convenience over end-use fitness which is aggravated by the widespread desire to shift web development more towards the experience of back end, desktop or native development. This ignores the fact that the browser-based environment doesn't have the same tolerance for bloat and needs to be treated as a constrained environment in terms of bandwidth, memory and CPU cycles - especially as mainstream personal computing has shifted from fat core desktop PCs to small core hand held devices connected via increasingly oversubscribed cellular networks.

Obligatory links to:

Thread Thread
 
redbar0n profile image
Magne • Edited

Re: Limitations of the React component model, and I'd thought you'd like this post from 2015 I came across:

"But one problem I’ve run into with this principle is that the React render tree is bound to the same structure as the DOM tree. The flow of data in React is thus bound to the way CSS and HTML allow you to lay out your application. This basically means that information can only flow from larger rectangles to smaller inner rectangles."

medium.com/@chetcorcos/shindig-rea...

Thread Thread
 
peerreynders profile image
peerreynders

Thanks.

I also just came across UI as an Afterthought (2019)

Setting aside that it centers around Mobx the core idea is:

Initially, design your state, stores, processes as if you were building a CLI, not a web app.

Not that the idea is new but it's interesting to see it applied to client side application logic.

The result:

Most components will be dumb

... at which point something leaner to render the UI might be appropriate.

Also Complexity: Divide and Conquer! (2017).

Thread Thread
 
redbar0n profile image
Magne

Why are the components organized as a tree?

Of course, an implementation detail was leaking into the design - as a component will ultimately render to a DOM fragment (well, fragment of React Elements), each component is bound to its rendering position within the page (or app).

@peerreynders , I read Christopher Alexander's A City is not a tree, and was reminded of our conversation here, and this quote of yours.

I tweeted some sections of the article, of how it may relate to UI:
twitter.com/magnemg/status/1444350...

I presume you are already familiar with the article. How do you think the concept of a UI as a semi-lattice instead of a tree could be applied to UI? Would it be beneficial, at all?

Thread Thread
 
peerreynders profile image
peerreynders

I presume you are already familiar with the article.

No I wasn't before now.

I'm of course aware of Christopher Alexander — ever since the release of "Design Patterns Elements of Reusable Object-Oriented Software" in late 1994, which referenced A Pattern Language. Now one thing to keep in mind is that the majority of Alexander's work has to deal with physical constraints — something software doesn't have to deal with unless we're talking about the fallacies of distributed computing. That said in this particular instance he is zeroing in on the constraints imposed by the unavoidable mental limitations of humans in their role as designers.

because they are trapped by a mental habit, perhaps even trapped by the way the mind works

To some degree this aligns with The Real Cost of UI Components:

Rich Harris, the author of Svelte, made the claim that “Frameworks aren’t there to organize your code, but to organize your mind”. I feel this way about Components.

but Alexander's point could be interpreted laterally as: "just because components help to organize your mind doesn't imply they are the correct means of structuring your solution."

In my quote

Why are the components organized as a tree?

I was alluding to the conflation of UI and application responsibilities within the same component instance. The tree organization of markup and by extension DOM is simply a constraint of the medium that semi-structures data for the purpose of visual representation. My criticism of React was that it doesn't necessarily follow that the communication pathways and interrelations for the various parts of the application will be accommodated by that same tree.

So when Alexander asserts that a tree is constraining to the point of constriction (compared to a semi-lattice), I see that as a truism with reference to React. Many applications cannot and will not constrain themselves to lifting state up and prop drilling but instead will use context to break out of React's component tree for out-of-band-communication (just like the additional edges of a semi-lattice). So there are really two things going on here:

  • The visual representation is a tree by virtue of the medium (HTML/DOM) that is being used to represent the data to the user.
  • That tree is not necessarily how the application logic needs/wants to be organized.

In the early 2000's there was a lot of discussion around the impedance mismatch between data models and application logic. I think a similar "impedance mismatch" exist between visual design and client side application logic. Trygve Reenskaug arrived at MVC for a very specific context, subsequently MVC was taken increasingly out of context, inevitably leading to problems. By declaring itself as the "V in MVC" React didn't actually address any of the problems but instead positioned itself to absorb functionality from the other two aspects — for better or worse.


Earlier in this discussion I stated:

Now something like this can already be managed manually today - though it would be tedious as hell.

Here is another attempt at describing what I mean: rendezvous (live on glitch).

Thread Thread
 
redbar0n profile image
Magne • Edited

I agree.

My criticism of React was that it doesn't necessarily follow that the communication pathways and interrelations for the various parts of the application will be accommodated by that same tree.

Indeed. A UI does not behave as a tree. This is vaguely related, and desparingly funny: xkcd.com/2044/

That tree is not necessarily how the application logic needs/wants to be organized.

I think XState is an interesting alternative to organizing application logic (making behavior declarative). Especially on the front end (though potentially also on the back-end) With React: Possibly to the point of avoiding the props interface altogether for updating state (in favor of only using XState's hook) ...

I even think strictly updating CSS shouldn't be done through props, but rather through the CSSOM (like the CSS-in-JS library Stitches does), to avoid triggering prop changes in (and re-rendering of) the entire React tree.

Since the DOM (and VDOM) should be for markup:

HTML markup is transformed into a Document Object Model (DOM); CSS markup is transformed into a CSS Object Model (CSSOM). DOM and CSSOM are independent data structures. -- constructing-the-object-model

But I digress.

By declaring itself as the "V in MVC" React didn't actually address any of the problems but instead positioned itself to absorb functionality from the other two aspects — for better or worse.

I definitely agree with this! Components have become ViewControllers, like in Swift. When Components even contain application state, they become ModelViewControllers...

Which, ironically, isn't so far from what Reenskaug envisioned for MVC (as opposed to the layered architecture it became):

"Every little widget on the screen had its own Model, Controller and View." at MVC is not an Architecture.

Although this approach has some drawbacks, especially related to syncing state changes, as noted at the section starting with "Sometimes MVC is applied at the individual widget level ..." in this excellent and visual MVC explainer article.

rendezvous looks interesting. Although I'm not sure I fully grok it...

This allows client side rendering to be split into two distinct and decoupled phases:

  1. render template (UI structure)
  2. bind behaviour (UI behaviour)

The separation between the "render template" and the "bind behavior" reminds me of StimulusJS. But maybe the thing with rendezvous is that it renders HTML in the same way on the server as on the client? Instead of just manipulating HTML on the client, like Stimulus does.

It also starts with the server and then moves to the client
...
On the client side the state initialization data (to support context) and the transfer messages are extracted from the markup and prepared for use as part of the page configuration. Then the binder definitions are loaded to bind application behaviour to the existing (and future) DOM tree.

I am reminded of Qwik with its resumability vs. replayability. How would they compare?

Thread Thread
 
peerreynders profile image
peerreynders

FYI:

The liabilities of MVC are as follow:

Intimate connection between view and controller. Controller and view are separate but closely-related components, which hinders their individual reuse. It is unlikely that a view would be used without its controller, or vice-versa, with exception of read-only views that share a controller that ignores all input.

Buschmann, Frank et al. "Pattern-Oriented Software Architecture: A System of Patterns Volume 1". Model-View-Controller, p.142, 1996.

The second division, the separation of view and controller, is less important. Indeed the irony is that almost every version of Smalltalk didn't actually make a view/controller separation. The classic example of why you'd want to separate them is to support editable and noneditable behavior, which you can do with one view and two controllers for the two cases, where controllers are strategies [Gang of Four] for the view. In practice most systems have only one controller per view, however, so this separation is usually not done. It has come back into vogue with Web interfaces where it becomes useful for separating the controller and view again.

The fact that most GUI frameworks combine view and controller has led to many misquotations of MVC. The model and the view are obvious, but where's the controller? The common idea is that it sits between the model and view, as in the Application Controller (379) — it doesn't help that the word "controller" is used in both contexts. Whatever the merits of the Application Controller (379), it's a very different beast from an MVC controller.

Fowler, Martin. "Patterns of Enterprise Application Architecture", Model View Controller, pp 331-332, 2003.

Thread Thread
 
peerreynders profile image
peerreynders • Edited

I think XState is an interesting alternative to organizing application logic (making behavior declarative).

XState is a tool — or as I like to say product — the general skill behind the product is statecharts.

My personal attitude

  • General skills tend to depreciate slowly so they are typically a valuable investment.
  • Product skills depreciate quickly so they are the "cost of doing business" and should be minimized.

XState is valuable because it enables the application of statecharts. But as wide as an applicability statecharts may have, they don't solve every problem and for many problems they are overkill especially when yet-another-library has to be pulled in.

Also don't pursue declarative for it's own sake. When it comes to the web HTML and CSS are declarative by default but JavaScript is not. Creating declarative abstractions in JavaScript always comes at a cost — sometimes it's worth it, sometimes it's not — as always it depends.

I even think strictly updating CSS shouldn't be done through props

Why change CSS at runtime at all? CSS rules are designed to remain static for the lifetime of the page but they only become active when the specified conditions are met. You wanted "declarative" but now when you have it you want to imperatively change it at run time?

Perhaps what is needed is tooling that can weave together the required CSS style sheet at design time — prior to deployment.

Components have become ViewControllers

As I sourced in my other reply, View and Controller have traditionally always been heavily coupled in practice so the V-C separation really only existed in idealized descriptions. As you remark, the real problem starts when application state gets pulled into a UI component.

Which, ironically, isn't so far from what Reenskaug envisioned for MVC

Robert C. Martin's account may not perfectly align with Trygve Reenskaug.
(trygve also happens to be a DCI programming language)


In Smalltalk-76, the forerunner to Smalltalk-80, the idea was to let objects represent some information of interest to the user and also to know how to present this information on the screen and let the user edit it. This very powerful paradigm is the basis of the intuitively pleasing object-oriented user interfaces so popular today.

This concept proved inadequate when I wanted to use Smalltalk-76 to create a system for production control in shipbuilding. The information represented in the system was the production schedule with its activities and resources, and the user would want to see and manipulate it in many different forms: as a network of activities, as a chart showing each activity as a bar along the time axis, and as a business form presenting activity attributes as texts that could be edited.

A natural consequence of this was to tear the original object apart, so that one object represents the information, one is responsible for the presentation and one for capturing input from the user. The first was called the model object, the second was called the view object and the third was called the controller object. This gave the freedom to have many different presentations and input facilities for the same object, and even to have several views of a given model on the screen simultaneously.

The object-oriented, direct manipulation user interface gives the user an illusion of working directly with the apparently concrete information objects. The Model-View-Controller breaks this illusion when the user has several views on the same information object simultaneously. This is fortunately of no concern to the professional planner who is manipulating different views of the same plan even in the manual systems.

Reenskaug, Trygve et al. "Working with objects The OOram Software Engineering Method", 9.3.2 Model-View-Controller, pp.333-334, 1995.


So given that models were shared among several views it makes no sense for the model to be inside a single UI component (view + controller). MVVM introduced the idea of a View Model which could be co-located with the View.

But maybe the thing with rendezvous is that it renders HTML in the same way on the server as on the client?

I've only looked at Stimulus JS briefly. In terms of my write up I would say it focuses only on the variation (client side functionality) because in Rails-land there is no commonality to exploit given that none of the server side code — not even any language-independent templates — can be reused on the client side. So when it comes to markup you have to write the template logic twice (I'm not aware of any tooling that translates a common template spec to server and client code).

The other thing is whenever I look at it, it's so infuriatingly Rails. It makes sense because that is the ecosystem it was created for but at same time it seemed to assert its Rails-ness even at the cost of being less web-ish. In my opinion Andrea Giammarchi's libraries are "of the web" — his work tends to be in alignment with "the platform" even when he doesn't agree with it and does he ever know how to get the most out of it. Meanwhile Rails just wants to be Rails regardless of what the web is doing.

Some of my pet peeves with Stimulus JS:

  • Any instance specific information has to stuffed into the markup. And as Harry Roberts points out:

A common practice is to use data-* attributes as JS hooks, but this is incorrect. data-* attributes, as per the spec, are used to store custom data private to the page or application (emphasis mine). data-* attributes are designed to store data, not be bound to.

data-controller identifies the controller category (class) but not the controller instance, while other data-* conventions litter the markup with "quasi-bindings" (that have nothing to do with the markup's responsibility of structuring content) for the sake of being "declarative". The cost of being declarative is having to inherit from the Controller class which presumably scans the markup at runtime for the data-* binding sites which uncomfortably reminds me of Vue's DOM templates. The Controller is heavily coupled to the markup — how much work is it really to correctly bind with the DOM with a few imperative steps right in the connect instead of the Controller base class doing who knows what.

  • All the controller files are stored under one single controllers folder. Really? I don't put all my cutlery, paring, chef's knives and box cutters in the same drawer just because they are all "knives". Why should I organize my code that way?

Rendevous in essence hijacks custom element-like ideas and mechanisms to late-bind client side behaviour to the DOM — regardless of whether that DOM is the result of parsed server HTML or client side rendering (and either are created with the same template or template function).

How would they compare?

Don't know, haven't looked at it in detail. For the time being from what I have heard Astro sounds worth investigating — the said, the current astro/qwik/remix hype is giving me visions of this - making me wonder if they've gone a bridge too far.

Thread Thread
 
redbar0n profile image
Magne

I agree with your attitude and advice, re: XState and statecharts.

Why change CSS at runtime at all?

To change the theme of the page. Dark mode, for instance.

CSS rules are designed to remain static for the lifetime of the page but they only become active when the specified conditions are met. You wanted "declarative" but now when you have it you want to imperatively change it at run time?

Perhaps what is needed is tooling that can weave together the required CSS style sheet at design time — prior to deployment.

Yes. Stitches.dev is that tool. It allows a declarative way of writing CSS-in-JS at design time, while also weaving it together and changing it for you at run time.

Some of my pet peeves with Stimulus JS:

Thanks for sharing! It's very interesting to read your insights. I wouldn't have thought of that myself. Personally, my issue with Stimulus is just that I fear I'll incorrectly wire things up due to typos. Plus that with all the data-* prefixes, it's visually confusing (I feel that data-hello-target should have been a simple id). So I can't really quickly scan it to get the info I need. The latter is an issue I have with Ionic Framework too, with it's insistence on prefixing all their components with ion-* (when it ought to have been a suffix, imho).

the current astro/qwik/remix hype is giving me visions of this - making me wonder if they've gone a bridge too far.

I agree. I am overwhelmed by just trying to understand and differentiate all the different approaches...

I've suggested to Ryan Carniato that he - or someone equally knowledgeable - should make a standardised comparison diagram of the different approaches we have today:
twitter.com/magnemg/status/1435235...
twitter.com/magnemg/status/1435280...
twitter.com/magnemg/status/1438271...

Thread Thread
 
peerreynders profile image
peerreynders

To change the theme of the page. Dark mode, for instance.

Perhaps I'm naive but I would have thought that's a job for CSS custom properties i.e. an imperative swap of the design tokens that relate to light/dark.

I wouldn't have thought of that myself.

Don't get me wrong. Rails had a point to employ "convention over configuration" to counter the J2EE XML configuration madness at the time. But if you apply "convention over configuration" dogmatically you end up with an awful lot of convention that you have to keep in your head. It's always a question of balance; hard coding vs configuring vs convention and it's not easy to hit the optimum (which depends on the circumstances).

The biggest problem with the rendezvous example is that static.js — for a full page that would be a monster to work with. So you really would need tooling to map a more developer friendly representation to the machine friendly representation.

I fear I'll incorrectly wire things up due to typos. Plus that with all the data-* prefixes,

Look I completely empathize — I immediately thought: "I wish there was a tool that could verify that all the bindings are set up correctly".

Going back even further, when learning JavaScript I found myself craving the false sense of security that a C#/Java compiler gave me that I at least I got the syntax right. At the time I would have been very receptive to TypeScript (these days — poor ROI — how things change).

Now please indulge a digression here — I'll get to a point soon enough.


Oliver Steele: The IDE Divide

That article differentiates between the

  • Language-Maven
  • Tool-Maven

Given that presumed dichotomy in developer attitudes and my personal reading of the declarations of the vocal minority on social media I'm lead to the conclusion that the currently active generation of developers is immensely skewed towards the "I'll adopt it when the tooling is better" type.

That means that a "good idea" has to survive to the early/late majority stage of the innovation adoption lifecycle.

Innovation Adoption Lifecycle

That makes me wonder: How many "good ideas" die on the "innovators" and "early adopters" hill because the developer population is skewed so much towards the "Tool-Mavens" rather than the "Language-Mavens"?


So if you can make Stimulus JS work then by all means do it!

  • Now in your situation you'll be committed to formulating your templates twice and you'll have to keep them in sync — that's just how it is. However I'd consider adopting something like µhtml for client side templating for the larger partials and I would wrap each template in a (pure) function, passing it all the data it needs to instantiate.

  • Whenever possible use reactive bindings to update values inside the DOM rather than replacing nodes. This is the reason why I wish there was a non-UI core of Solid — for most purposes MobX seems a bit too heavy — I'd like something that is lighter than Redux. I guess one could always roll your own.

  • Keep those (Stimulus JS) controllers as skinny as possible. Their job is fairly narrow:

    • On connect/mount
      • Wire reactive subscriptions for value updates into the DOM and add event listeners for input.
      • If the nested content isn't already present use templates to create it client side (their controller(s) will take care of the rest).
    • On disconnect/unmount
      • Remove event listeners and unsubscribe the reactive bindings.
    • Whatever you do keep actual page application logic out of the (Stimulus JS) controllers.

Remember the aim (of rendezvous) is to have a JavaScript core application that can be effectively micro-tested without:

  • running it in a browser
  • having to spin up a fake DOM.

The templates, controller, event and reactive bindings are tested during integration testing with something like cypress or perhaps Puppeteer. The key is that by this point in time you already have full confidence in your decoupled, micro-tested page application logic.

when it ought to have been a suffix, imho

You have to keep in mind that while most browsers will handle non-standard attributes those attributes will actually invalidate the HTML (which is why those pesky alpine.js x-* attributes are another pet peeve of mine). If you are going to 'invent' attributes on standard HTML elements then they better be data-* attributes. At least Stimulus JS got that right.

Thread Thread
 
redbar0n profile image
Magne

I wish there was a non-UI core of Solid — for most purposes MobX seems a bit too heavy — I'd like something that is lighter than Redux.

Check out Daishi Kato's projects, Jotai, Zustand and Valtio. Zustand is the lighter but direct alternative to Redux (even though Redux-Toolkit, aka. RTK, has come far in approximating that DX). Easy-peasy is another alternative for simple DX, but sits atop of Redux. But Valtio might be what you are looking for, and even Kairo's 'atoms' seem interesting, for fine-grained reactivity. See: Jotai vs. Zustand vs. Valtio example code.

Also have a look at the new way of integrating MobX more cleanly / hassle-free with Solid: twitter.com/RyanCarniato/status/14...

Whatever you do keep actual page application logic out of the (Stimulus JS) controllers.

Where would you put it, then? How would you structure it?

It seems like this is what React achieves with their "hooks".

Thread Thread
 
peerreynders profile image
peerreynders • Edited

Referencing this without comment just for your interest: How To Upgrade Your React UI Architecture

More interestingly Daishi Kato recently published When I Use Valtio and When I Use Jotai:

mirroring Mark Erikson's app-centric vs component-centric classification of React-based client side solution architectures.

(Also a custom hook for a data/app-centric design can just act as an adaptor/translation layer to an (otherwise self-contained) external dependency - simply delegating "business logic" rather than containing it.)

Edit: another one; React Architecture Confessions

Thread Thread
 
peerreynders profile image
peerreynders

Pete Heard's gone reactive now! (solid store anyone?).

Thread Thread
 
redbar0n profile image
Magne • Edited

Thanks for the accurate reference! I’m sympathetic to the principle of separating out the state from the view. But at first glance this architecture seems over-engineered. He also doesn’t address the common concerns about 2-way data-binding (like uncontrolled cascading changes, and debuggability, esp. for large teams). It would be very interesting to hear @ryansolid’s opinion on this kind of reactive architecture, and how it compares with the likes of Recoil and solid-store. In particular, I’m not sure the ViewModel is really needed.

Thread Thread
 
peerreynders profile image
peerreynders

But at first glance this architecture seems over-engineered.

Hence my earlier comment


It would be very interesting to hear ryansolid’s opinion

Given his first comment I think the assessment of MVVM would carry over to this in particular:

"they tended to bloat and it created this second tier of indirection."

"they just recognized the separation of view and view model caused in many cases unnecessary complications."

"This isn't to say you can't have separate model data. "


Basically the components should subscribe to and report the client side domain model directly.

He also doesn’t address the common concerns about 2-way data-binding

I think the core concern is "data", not "2-way".

"2-way" shouldn't be a problem if a component reports user events (perhaps transformed/mapped) to the domain model without changing it's own core state (disregarding throttling etc.) and only changes its core state in response to events from the domain model.

The viewmodel almost seems like a layer to transform (domain model + events from domain model) into a pure view data representation.

To me it makes more sense to have the component poke the domain model directly (not by setting data, but with commands/events), and subscribe to the relevant events from the domain model while the component maintains its own view state.

With XState I came across the notion of extended state i.e. data that provides context for the current "state" but which can change without forcing a "state transition".

i.e. change of data != change in state

Perhaps the issue with the "M" in "MVC", "MVP(VM)", "MVI", "MVVM", etc. is that it is treated as raw data.

But as David Khourshid observes: "When do effects happen—State transitions. Always"

i.e. models can't just be a data dumping ground:

  • they must process inbound events to update internal data
  • they must emit outbound events only when internal data changes in a meaningful way (their state transitions).

In a way view related outbound events bind some of the controller responsibility to the model. To some degree this couples the "model" to the view the same way any service implements a contract to serve the needs of its clients. Then again the view is implementing a visual representation of the domain model on behalf of the domain model.

"Reactive data" is a step in the right direction but in some ways that is too fine grained unless that data only changes "on transition".

This is why I keep coming back to "The big idea is 'messaging'" (events, not components).

state + event = newState + actions [ref]

What are the "events" that will force the change of some part the UI's representation?

Thread Thread
 
peerreynders profile image
peerreynders

Are these components "just visual" enough?

Of course the "actual capabilities" exist here.

And as such the application behaviour can be tested without rendering or interacting with a surrogate DOM.

Thread Thread
 
redbar0n profile image
Magne • Edited

As mentioned in the article, I dislike <Show> and <For> components, since they are basically control-flow logic, and don’t really represent a visual atom (“screen widget”, if you will) on the page. They are more Controllers than ViewControllers, imho. They strike me as more imperative than declarative, although I know Ryan disagrees on the interpretation of those terms, particularly in this context.

I have previously attempted to provide some suggestions to the SolidJS syntax for how I imagine it would look ideally (a “just JS” approach), see alternative 8.1 and compare with the equivalent “alternative 0” in SolidJS. But it wasn’t favored by the community.. There might be reasons unbeknownst to me for why it couldn’t have a syntax like that, given SolidJS internals…

That said, the separation of concerns you demonstrate with your solid-bookstore-a repo looks great. I think we ought to have more experiments of this sort!

It looks as if you effectively created a Model out of the SolidJS stores. The only thing I’m uncertain about is the intuitiveness of having to deal with createEffect and createMemo inside that model. Since those seem like rendering concerns... Ideally, I’d like to have rendering concerns wrapped inside some components (at least in the same file as the component), instead of turning the whole component model inside-out, like you’ve done here.

Would also have been interesting to see how it would look as a small state machine, using xstate/fsm xstate.js.org/docs/packages/xstate..., Robot thisrobot.life/ or as a Zag machine zagjs.com/ (from ChakraUI creator). Since the logic seems amenable to that.

Finally, I’m a bit wary against testing intermediate representations (as opposed to end-to-end, UI snapshots included). Since from experience bugs can always sneak in in the last layer towards the user. But yeah, for testing heavy logic, then isolating it like this presumably vasly improves the runtime. But it doesn’t make the rendering library (View layer) swappable.. (if that is a goal..). It’s probably the closest one can get to a View layer separation with current rendering libraries/trends.

Would it be possible to implement the same pattern with React? Or is it only possible because Solid’s fine grained reactivity?

Thread Thread
 
redbar0n profile image
Magne

But yeah, such terseness of the rendering result (without a bunch of inline ternaries etc.) is definitely something to strive for!

Thread Thread
 
peerreynders profile image
peerreynders • Edited

They strike me as more imperative than declarative,

I think that's a matter of perspective based on your view of "declarative visual atoms".

HTML is semi-structured data. Putting aside that HTML is typically rendered (visual), the whole idea behind semantic HTML is that HTML has meaningful structure. You are declaring a structure that gives the data contained therein context.

Show is simply a meta structure that

  • is present when the data condition is satisfied
  • replaced with a fallback when the data condition is not satisfied or
  • missing altogether in the absence of a fallback

Similarly For/Index are meta structures that represent "repeating" data.

From that perspective Show/For/Index conceptually just extend HTML's capabilities of declaratively structuring data.


There might be reasons unbeknownst to me for why it couldn’t have a syntax like that,

My personal opinion is that my brain hates having to swap parsers mid stream when reading code. I'm either scanning for patterns in a declarative structure or I am reading flow of control (or transformation of values) code. Having to constantly switch mental parsers breaks the flow with repeated stalls.

given SolidJS internals …

The markup executes in a reactive context - it's conceptually a huge createEffect. So any Accessor<T> getters used in there will automatically re-subscribe on access (which is the entire point).

It's just simpler to avoid getting into JSX-style imperative code which can have an undesirable performance impact-just stick strictly to accessing data to be rendered or inspected and everything will be OK.


The only thing I’m uncertain about is the intuitiveness of having to deal with createEffect and createMemo inside that model.

It takes some getting used to. They're essentially read-only (but reactive) view models with command facades to trigger application changes (some facades also have queries for derived data or accessors for derived signals). One-way flow is preserved.


Would also have been interesting to see how it would look as a small state machine, using xstate/fsm

You quickly run into limitations with just finite state machines. In another experiment of mine I was surprised how quickly I needed to use compound state; i.e. upgrade to a state chart.


I’m a bit wary against testing intermediate representations

The intent is to include more logic in the fast feedback loop without slowing the tests down with rendering and comparing rendered output. The rendered output still needs to be tested with less frequently run integration tests.

The integration tests deal with the Horrible Outside World while the microtests exercise as much logic as possible without interacting with the HOW.

But it doesn’t make the rendering library (View layer) swappable.. (if that is a goal..).

It's not. Do you know of any cases with a web UI framework/state management situation where one was swapped while the other was kept? Typically both are generationally linked so when one is replaced the other is replaced with a generational match.


Would it be possible to implement the same pattern with React?

With just React?

I would imagine that would be extremely difficult if not impossible given that the component tree is at React's core. Hooks are specifically designed to operate on component instance specific state that is managed by React.

A component would basically use a single component specific hook to get everything it needs like data for rendering and input handlers. The hook would manage component state in as far it is necessary to re-render the component at appropriate times but would delegate everything else to the core client application that it can subscribe to via context.

But how do you exercise a hook without a component? A component is literally a render function for ReactNodes. You need to get the core client application out from under the render loop.

With Solid there is no render loop and the reactive state primitives are the core rather than some component tree. That makes it possible to build the application around state, independent from rendering. Solid's approach to rendering means that rendering concerns can easily attach to existing reactive state.

In React rendering and state are tightly coupled as component state primarily exists to inform re-renders rather than to enable application state.


A year ago I tinkered with a Preact+RTK version of the Book Store—I figured that the MobX version was perhaps not accessible enough for non-MobX developers (and I personally despise decorators; I find them arcane and unintuitive).

I didn't complete it but the main point was to avoid React Redux because Redux belongs inside the core client side application, not coupled directly to the visual components.

Also the core client side application should only expose methods for subscriptions and "commands" but not "queries". The "query data" would be delivered by the subscriptions.

function makeSubscription(cart, [current, setCurrent]) {
  // (1) Regular render data supplied by subscription here ...
  const listener = (current) => setCurrent(current);
  return cart.subscribe(listener, current);
}

function Cart() {
  const { cart } = useContext(Shop);
  // (2) ... but also accessed via a query method for initialization here
  const pair = useState(() => cart.current());
  useEffect(() => makeSubscription(cart, pair), []);

  const [ current ] = pair;
  if (!cart.hasLoaded(current)) return <Loading />;

  const checkout = () => cart.checkout();
  return renderCart(cart.summary(current), checkout);

}
Enter fullscreen mode Exit fullscreen mode

It's at this point I realized I needed to go back to the drawing board because the direct data accesses (queries) to the cart felt like a hack; all the necessary information for rendering should come in via subscriptions, not need to be accessed directly.

With Solid this was much simpler because components are setup functions; when the function runs we're clearly initializing.

With React function components

  • component initialization
  • initial render
  • subsequent renders

are all crammed into the same place. I needed subscriptions to the application to trigger renders so I was planning to send the necessary rendering data along. But for initial render I needed to get the data directly. So really the application data would be retrieved via direct data access (queries) anyway while the subscriptions only existed to "poke the component in the side" to start a render.

To move forward I would have to implement a separate query method for every type of the subscription the core client application would support. That just seemed all wrong.


"You need to initiate fetches before you render"

Ryan Florence: When To Fetch. Reactathon 2022

Corollary:

"Renders should be a reaction to change; not initiate change".

And for renders to be truly reactive you need either

  • an opportunity to create subscriptions before the first render or
  • have subscriptions that synchronously deliver data immediately upon subscription.

Neither is possible with React function components. Solid's subscriptions will synchronously deliver the (initial) data immediately upon subscription.

Thread Thread
 
peerreynders profile image
peerreynders • Edited

I would have to implement a separate query method

Something along the lines of this:

// src/components/use-app.ts
import { useContext, useEffect, useState, useRef } from 'react';
import { getContext } from './island-context';

import type { App } from '../app/create-app';

type ContextRef = React.MutableRefObject<React.Context<App> | null>;

const getAppContext = (contextKey: string, ref: ContextRef) =>
  ref.current == undefined
    ? (ref.current = getContext(contextKey))
    : ref.current;

const advance = (current: number): number =>
  current < Number.MAX_SAFE_INTEGER ? current + 1 : 0;

function useApp(contextKey: string) {
  const ref: ContextRef = useRef(null);
  const app: App = useContext(getAppContext(contextKey, ref));
  const [, forceRender] = useState(0);

  useEffect(() => {
    return app.listen(() => {
      forceRender(advance);
    });
  }, [app]);

  return app;
}

export { useApp };
Enter fullscreen mode Exit fullscreen mode

i.e. useState() is only used to force a render…

// src/components/CounterUI.ts
import React from 'react';
import { useApp } from './use-app';

type CounterUIProps = {
  appContextKey: string;
};

function CounterUI({ appContextKey }: CounterUIProps): JSX.Element {
  const app = useApp(appContextKey);

  return (
    <div className="counter">
      <button onClick={app.decrement}>-</button>
      <pre>{app.count}</pre>
      <button onClick={app.increment}>+</button>
    </div>
  );
}

export { CounterUI as default };
Enter fullscreen mode Exit fullscreen mode

…while the app properties and methods are used to obtain the render values (ignoring the contents managed by useState() entirely).