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.
@AdamRackis However, some of my favorite conversations have been with developers who say "I don't use XState, but now I think in terms of explicit states/events/transitions all the time"
I count that as a win 🙌
03:26 AM - 19 Oct 2021
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 Astrosounds 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.
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...
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.
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.
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.jsx-* 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.
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.)
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.
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).
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?
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.
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.
functionmakeSubscription(cart,[current,setCurrent]){// (1) Regular render data supplied by subscription here ...constlistener=(current)=>setCurrent(current);returncart.subscribe(listener,current);}functionCart(){const{cart}=useContext(Shop);// (2) ... but also accessed via a query method for initialization hereconstpair=useState(()=>cart.current());useEffect(()=>makeSubscription(cart,pair),[]);const[current]=pair;if(!cart.hasLoaded(current))return<Loading/>;constcheckout=()=>cart.checkout();returnrenderCart(cart.summary(current),checkout);}
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.
FYI:
Buschmann, Frank et al. "Pattern-Oriented Software Architecture: A System of Patterns Volume 1". Model-View-Controller, p.142, 1996.
Fowler, Martin. "Patterns of Enterprise Application Architecture", Model View Controller, pp 331-332, 2003.
XState is a tool — or as I like to say product — the general skill behind the product is statecharts.
My personal attitude
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.
You don't need a library for state machines
David K. 🎹 ・ Jan 20 ・ 10 min read
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.
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.
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.
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.
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:
data-controller
identifies the controller category (class) but not the controller instance, while otherdata-*
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 theController
class which presumably scans the markup at runtime for thedata-*
binding sites which uncomfortably reminds me of Vue's DOM templates. TheController
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 theconnect
instead of theController
base class doing who knows what.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).
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.
I agree with your attitude and advice, re: XState and statecharts.
To change the theme of the page. Dark mode, for instance.
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.
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 thatdata-hello-target
should have been a simpleid
). 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 withion-*
(when it ought to have been a suffix, imho).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...
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.
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.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
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.
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:
Remember the aim (of rendezvous) is to have a JavaScript core application that can be effectively micro-tested without:
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.
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 bedata-*
attributes. At least Stimulus JS got that right.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...
Where would you put it, then? How would you structure it?
It seems like this is what React achieves with their "hooks".
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
Pete Heard's gone reactive now! (solid store anyone?).
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.
Hence my earlier comment
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.
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:
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?
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.
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
andcreateMemo
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?
But yeah, such terseness of the rendering result (without a bunch of inline ternaries etc.) is definitely something to strive for!
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
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.
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.
The markup executes in a reactive context - it's conceptually a huge
createEffect
. So anyAccessor<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.
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.
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.
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.
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.
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.
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
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
Neither is possible with React function components. Solid's subscriptions will synchronously deliver the (initial) data immediately upon subscription.
Something along the lines of this:
i.e.
useState()
is only used to force a render……while the
app
properties and methods are used to obtain the render values (ignoring the contents managed byuseState()
entirely).