13 ms·
React's UI State Model vs. Vanilla JavaScript
- megous 5y agoWith react you've just moved your imperative code from REST->DOM manipulation to REST->local state manipulation and local state -> DOM mapping. You have to invent some elaborate local state schema and then map it to DOM, instead of keeping part of the state implicitly stored in DOM, where it makes sense. You can assign your data directly to DOM node objects. Say `el.yourData = todo_item`. And then let the order and list of items be kept implicitly in DOM, so you can do obvious things like remove nodes using `.remove()` reorder them via DnD, and whenever you need the list as data, you just querySelectorAll('.item').map(el => el.yourData) and you have your list. All quite straightforward. You can also do context dependent things by being able to travel up to the ancestors via parentNode. Declarative frameworks like react make simple things like this painful in comparison.
- chmod775 5y agoThis is just an ad. I wish software developers would be more honest about their tools, weighing their pros and cons, instead of thinking up contrived 'examples' that paint them in the best possible light. Every library/tool/framework will make you think "well, this sucks" at some point. So be honest.
- acdha 5y agoI tend to see the problem as the point where people stop thinking of them as tools and more part of their self-identity. If learning how to use React makes you more productive, it’s only reasonable to use it on your next project. The problem starts when you start thinking “using React makes me a Real Programmer™ like the cool kids at Facebook” and that turns every discussion into a tribal loyalty test.
- Spivak 5y agoThe problem with React (or the reason you might want to sometimes choose VanillaJS) is that React's algorithm to apply a DOM diff has to be completely generic. In React your flow will loosely be Something happens The browser triggers and event and sends it to React React calls your code to re-render the world[1] React takes the new world order and does a diff React calculates what to do go from old -> new and applies In VanillaJS world you can do way way way better than this with the benefit of knowing how your application works. Something happens The browser triggers and event and sends it to your handler Your handler updates the DOM It would be nice if in future you could make some promises to React about what happens on an event so React can skip the diffing step and just update the virtual and real DOM directly. And hint hint if you can generate the code to update the DOM directly in the general case too then do you even need the virtual DOM? Svelte is doing really cool work in this space. [1] componentDidUpdate helps but same diff
- bookofsand 5y agoThe beauty of the virtual DOM is that it relies on pure view functions: given this props+state input, this is how the DOM looks like. Generating DOM fragments on update is possible, but naively done it will quickly get out of sync with the DOM view function. Curious about 'really cool work' in this space!
- vosper 5y ago> Generating DOM fragments on update is possible, but naively done it will quickly get out of sync with the DOM view function. Curious about 'really cool work' in this space! This is what Svelte's doing, as I understand it: letting you think like you're writing React ("when in this state, the world should look like this") but avoiding the (greatly overstated, IMO) overhead of the virtual DOM by translating the changes into direct DOM updates in a complilation step.
- bookofsand 5y agoAh, I see. The programming model is similar, but the execution of the virtual DOM shifts from runtime to compile time. Cool!
- jchw 5y agoVirtual DOM overhead is only overstated in that it usually isn’t really compared to any reasonable baseline when discussed. Many will acknowledge that React was faster than many of its contemporaries—and I’m sure it remains competitive—but still express concern over the unnecessary overhead of the virtual DOM. I think I blame part of this on the language we use. These days a huge buzzword of sorts is the “zero-cost abstraction.” It means zero runtime cost, but a lot of us internalize it as completely free, failing to account for the fact that it often entails increased complexity and build times. Granted, often this is a worthwhile tradeoff, but in order to know you need a more nuanced comparison. That virtual DOM is “pure overhead” isn’t objective fact - pure overhead is an error of coding that can be fixed in a pull request. No, this is more like ideology. The problem with this ideology isn’t that it’s bad, it’s just incomplete. It usually explains away why the world hasn’t simply adopted their worldview by appealing to ignorance or even stupidity, while ignoring potentially serious issues. Like yeah, if I want to make a desktop application in 2021, I have to consider Electron because simply put, there’s a lot broken in modern native UI. And sometimes people are still right. There’s always a possibility Svelte will win out, or eventually be proven right even if it doesn’t. Maybe. But it’s 2021, and the year of the Linux desktop is still around the corner. If something hasn’t happened yet, at least humor some reasons why that might be the case. (Personally, in my experience, even update-intense apps wind up having bigger fish to fry than VDOM, so I can’t say I’m holding my breath.)
- username91 5y agoIt seems deceptive to me to compare using createElement etc. to JSX, rather than using innerHTML, or a <template>.
- RodgerTheGreat 5y agolikewise, using an unnecessarily repetitive style in the "vanilla" version and using a few compact ternaries in the "react" version. you can get pretty darn close to the react version of this example using vanilla js.
- reilly3000 5y agoI think that was mostly for illustrative purposes. Most websites aren’t built like that, but it helps show how attributes on DOM objects can be manipulated before throwing the reader into React state concepts.
- username91 5y agoThat's a fair point!
- QuadrupleA 5y agoUgh, this is massively overcomplicating the vanilla js case. Just put the checkbox html & css on the page. Write a function that updates the label based on checked state. Call it on input/change events. It's like 3 lines of js. React.js people always invent this vanilla js / jQuery strawman that's so complicated and spaghetti that no mortal can possibly understand it. It's total BS. You can write succinct maintainable code without a 100 lb framework.
- savanaly 5y agoIt's all about scaling of complexity and number of engineers working on the project. No doubt hand rolled js is better when you only have to do the one thing you mentioned and you're the only person who has to read or write the code. What happens when it's one of hundreds of checkboxes on the site? When they all do slightly different things depending on user sku, product page, file type, file permissions, god knows what else. And when one experiment flag is on or how about this other one? Then i18n? Then what about the fact that after you write this line countless other engineers will see and modify it in the future, it's not just you writing and maintaining it? These are the problems that my company (and, I assume, Publicis Sapient with 20,000 employees according to Google) have to consider when choosing whether to hand roll JS or use a framework.
- simonw 5y agoThen you can switch to something like React. But most projects won't ever get close to the level of complexity you are describing. Meanwhile I have seen SO MANY sites that use full React for a contact form with two fields on it.
- arcturus17 5y agoYou don't need to get close to that level of complexity for React to become useful. > Meanwhile I have seen SO MANY sites that use full React for a contact form with two fields on it. I agree that the contact form you describe would be dead-easy to do vanilla. Maybe throw in Parcel if you want to write ES6. However, while I suspect some people may use React in this case because it's all they know, others may do it because they are literally faster in React - they have a workflow that can see them code and deploy such a website to Netlify or Heroku or whatever in 10 minutes.
- geenat 5y agohttps://htmx.org/ https://htmx.org/
- naasking 5y agoI've been meaning to try this as it looks promising.
- deleted 5y ago[deleted]
- EugeneOZ 5y agoWhy trying to explain “declarative vs imperative” if both examples are totally imperative? At least vanilla js version doesn't re-render the whole component.
- simonw 5y agovar checkbox = document.querySelector( "input[type=checkbox]" ), container = document.querySelector( "#wrapper" ); checkbox.addEventListener("change", () => { if (checkbox.checked) { container.classList.add("checked"); } else { container.classList.remove("checked"); } }); Then use CSS to show or hide the message inside the container based on if it has class "checked" or not. You can get a whole lot done with very little code if you learn to take advantage of the three-legged stool of HTML, CSS and JavaScript.
- SeriousM 5y agoThat's the right way of separation of concerns: handle states with code, look/layout with css.
- nsonha 5y agofunny you should say that, because there is no explicit state here, what happens when there are 10 checkboxes? Just read `.checked` in the dom element?
- simonw 5y agoYes that would work great. Usually for things like that I'll come up with a CSS class that means "set it up so this element has its class toggled based on the state if the checkbox it contains". Sometimes I'll use data- attributes for additional configuration.
- Zababa 5y agoI think there are two ways of looking at things: the DOM is the view layer, created as a function of the state (React and things like that), or the DOM is the state and the view part is handled part by the DOM and part by the CSS. CSS already automatically creates the view as a function of the DOM.
- nsonha 5y ago
- beebeepka 5y agoOver engineering things is truly one of the biggest problems in our industry. It's been years and react still seems like an organized religious community to me. With many, many preachers. Reminds me of all the "yaml is better than XML/JSON" examples where everything is carefully formatted in a way that make no sense unless you are trying to prove a point. Part of me think we should just ignore html and just render everything using webgl/webvulkan/whatever instead of fighting the browser's flow. We're not there yet but things have been moving in that direction because it kind of makes sense
- t0astbread 5y agoWhat about accessibility? As far as I know canvases don't offer ways to expose the same semantic information about their contents as the DOM.
- oblak 5y agoYeah, they don't but perhaps the necessary meta could be generated as things are being rendered. It's certainly possible, though I haven't done any actual work on accessibility tools myself. I guess it's not a trivial problem to solve without breaking things here and there, otherwise someone would've done it already. Not impossible I'd think
- gherkinnn 5y agoReimplementing everything the browser does within canvas. Speaking of over-engineering. Flutter 2, for example, can't even get scrolling right [0]. 0 - https://gallery.flutter.dev/ https://gallery.flutter.dev/
- atirip 5y agoOh please, if you want to compare, use modern vanilla too... <!doctype html> <body> <custom-element></custom-element> <script> const CHECKBOX_ID = "my-checkbox"; const defaultLabelContent = "Toggle me, you newbies"; const beforeDiscountText = "You have not availabled discount"; const afterDiscountText = "Discount Availed!"; const beforeLabelText = "Click on me to remove fake discount" const afterLabelText = "Click me to apply fake discount!" const state = new WeakMap(); class CustomElement extends HTMLElement { connectedCallback() { this.render(); this.shadowRoot.addEventListener('change', (event) => { state.set(this, event.target.checked); this.render(); }) } constructor() { super(); this.attachShadow({ mode: 'open' }); } render() { let isChecked = state.get(this); const discountText = isChecked ? afterDiscountText : beforeDiscountText; const labelText = isChecked ? beforeLabelText : afterLabelText; this.shadowRoot.innerHTML = ` <input ${isChecked ? 'checked ' : ''} type="checkbox" id=${CHECKBOX_ID} > <label for=${CHECKBOX_ID}> ${labelText} </label> <div>${discountText}</div> `; } } customElements.define('custom-element', CustomElement); </script>
- montenegrohugo 5y agoCodepen for this: https://codepen.io/uwwgo/pen/GRmEKJz https://codepen.io/uwwgo/pen/GRmEKJz (nothing revolutionary, does the exact same thing.)
- infensus 5y agoWell, technically it's not an equivalent as this would recreate dom nodes on every render
- atirip 5y agoThis was just taking the final React code in the article and rewriting it in vanilla as close as possible. Your comment is fully correct, but I would like to point out: - with such a tiny DOM to rerender, it is equivalent, probably even faster - custom element encapsulates DOM and it is fairly trivial and extremely fast to pick DOM (like you can even use id's everywhere) in the 'old jquery' way, with simple library functions - updating DOM with simple render() call is way more elegant, but if you keep custom elements DOM small (and one should), this way is not that bad at all Like this: <!doctype html> <body> <custom-element></custom-element> <script> const CHECKBOX_ID = "my-checkbox"; const defaultLabelContent = "Toggle me, you newbies"; const beforeDiscountText = "You have not availabled discount"; const afterDiscountText = "Discount Availed!"; const beforeLabelText = "Click on me to remove fake discount" const afterLabelText = "Click me to apply fake discount!" const state = new WeakMap(); // 'library' code function qs(selector) { return this.shadowRoot.querySelector(selector); } function getElem(nodeOrSelector) { return nodeOrSelector === String(nodeOrSelector) ? qs.call(this, nodeOrSelector) : nodeOrSelector; } function replaceText(nodeOrSelector, text) { let elem = getElem.call(this, nodeOrSelector); if (elem) elem.textContent = text; } function updateAttribute(nodeOrSelector, name, value = '') { let elem = getElem.call(this, nodeOrSelector); if (elem) elem[(value ? 'set' : 'remove') + 'Attribute'](name, value); } // end of 'library' code class CustomElement extends HTMLElement { connectedCallback() { this.attachShadow({ mode: 'open' }); this.shadowRoot.innerHTML = ` <input type="checkbox" id=${CHECKBOX_ID}> <label for=${CHECKBOX_ID}></label> <div></div> `; this.shadowRoot.addEventListener('change', (event) => { state.set(this, event.target.checked); this.update(); }) this.update(); } update() { let isChecked = state.get(this); updateAttribute.call(this, 'input', 'checked', isChecked); replaceText.call(this, 'label', isChecked ? beforeLabelText : afterLabelText); replaceText.call(this, 'div', isChecked ? afterDiscountText : beforeDiscountText); } } customElements.define('custom-element', CustomElement); </script>
- ipnon 5y agoThe paucity of Facebook veterans does more in my mind to explain the popularity of React than its UI state model. React is popular because it's popular because it's popular ...
- simondw 5y agoDo you perhaps mean abundance instead of paucity?
- ipnon 5y agoNo, they get instantly hired, then they say nothing but "React ... React ... React." And the rest of the team goes, "wow they really do things differently in Menlo Park!"
- hajile 5y agoReact wasn't always popular... I remember using jQuery, Backbone/underscore, handlebars, knockout, Angular 1, Enyo, Ember, and probably a couple others. Angular in particular had like 70% market share among major frameworks and almost all the libraries didn't support React. React took over because it was head and shoulders better than everything that went before and taking days to learn instead of weeks or even months. The reason nothing has taken over since is because we've reached the point where they aren't good enough to justify throwing away all the existing library support.
- GrumpyNl 5y agoGreat example how to over complicate something so simple as state of a checkbox.
- Tomuus 5y agoI think this article skips over many nuances of the declarative model. For a real "ground up" approach of explaining those I'd recommend this article series: https://acko.net/blog/climbing-mt-effect/ https://acko.net/blog/climbing-mt-effect/
- gdad-s-river 5y agoThanks for the recommendation! Read through all the three articles. Learnt a lot. Even though some of the things written in a way that were 'handwaving' as the author writes himself, and I couldn't understand everything ( I'll keep revisiting ) I'd never thought about declarative model enabling using O(n) code instead of O(n^2) when state in the application increases, and some of the other things he talks about. I've linked these articles series as a first thing in my blog post.
- jazzyjackson 5y agoSo no one’s going to mention the css sibling selector? You don’t need JavaScript to reveal elements based on the state of a checkbox.
- atum47 5y agoLet's not forget that if your main goal is to have an element that is clickable and update it's label you can write a function for that or even create your own component that does just that. Your heard me correctly, you can create components with JavaScript without the aide of a framework.
- mcintyre1994 5y agoWhy would you use JavaScript to write your HTML elements in the vanilla case? Surely you could just have a plain HTML checkbox and your default text, and then add an event handler to update the text when the checkbox changes? That’s how React with something like NextJS behaves, vanilla JS you get it for free unless you decide to use JS to write your elements like this.
- zurgax 5y agoToggling the checkbox in the completed vanilla example halfway down the page didn't change its subtitle text for me on Safari - instead it updated the subtitle in the example at the very top of the page. Does it find the first matching element on the page and update it? (I don't know JS, DOM)
- lucideer 5y agoI love React. I love the elegance and flexibility of it's architecture, and the power of it's modularity. There are so many really great reasons to use React. This article is absolute nonsense from start to finish. What amounts to basically "lying" about the complexity of vanilla JS actually ends up mainly just doing a massive disservice to React which shouldn't need false comparisons to show it's benefits. Maybe they're just being disengenuous but I got the distinct impression from many of the examples that the author doesn't actually know vanilla JS at all.
- claviska 5y agoThis is the impression I got. The first half of the vanilla example is imperatively creating DOM elements, assigning attributes, and inserting them into an “app” <div>. My guess is the author learned React first and never considered that HTML can be written as (gasp) HTML!
- bob1029 5y agoTo me, the more fundamental problem is the fact that we spread state across multiple computers. If you only mutate state on the server and simply send views back down to the clients, nearly all of "modern" web development practices can be safely ignored. Clearly, there are difficult engineering challenges with the approach of "all state on the server all the time", but anything worthwhile is never easy. Down this path you might not obtain Netflix-tier webscale directly out of the box, but that doesn't mean we throw our hands up and have all our clients drown in multiple megabytes of angular11+ dependency trash either. How many internet users are more than 50ms away from a Cloudflare/Microsoft/Google/Netflix CDN in 2021? How many cores can you get in a 1U server now? Distributed state machines for client UIs have been a massive mistake.
- ngrilly 5y agoI mostly agree, but having used Linear.app which has a crazy good and fast UI based on a local database synced with their cloud database, I'm not sure anymore: https://youtu.be/WxK11RsLqp4?t=2170 https://youtu.be/WxK11RsLqp4?t=2170. I think it's important to define early in a project if the app is "cloud-based" or "local-based". If it's cloud-based, then only the server mutates state and sends views to the clients, as you wrote. Everything is simple. If it's local-based, then all data are available locally and mutated locally. The cloud is only used as mirror/backup and can support for some advanced features like full-text search. That's also a very clear architecture. That's how Linear works. Things become hairy and unnecessarily complex when no clear choice is made between those two options, then it's not clear where the state is.
- dehrmann 5y ago> there are difficult engineering challenges with the approach of "all state on the server all the time" There are real world challenges. There's no internet access on the NYC subway. Latency can spike for no apparent reason. Making a webapp a dumb client really only works for wired connections on an intranet.
- bob1029 5y ago> ... but anything worthwhile is never easy. When I was working in South Korea, their subway system had excellent reception to cellular and even satellite TV networks. They explicitly engineered a solution to this problem in direct, first order terms. This is precisely the type of approach we should be taking in my opinion. Make the networks robust, not the client-facing web apps. We are spending engineering resources in largely the wrong places. This would be like arguing for a car that can hover, but only for an ambiguous/non-specific amount of time, simply because the roads are occasionally total shit.
- game_the0ry 5y agoI get the impression that the author is so into the react way of doing things that they can't seem to comprehend and write JS in a non-react (vanilla) way, thus polluting the author's approach to writing vanilla JS. If anything, this article is an example of how you need to write and truly understand vanilla JS (and learn form your mistakes) before you can appreciate and understand what react had to offer when it first came out. I might have to write a counter blog post just to balance this out...SMH.
- eatonphil 5y agoShowing the difference between markup/scripting a single static element is pretty different from applications where you have to display lists of things with other lists of things inside. If you only have static elements then just writing it in HTML and making it dynamic with JavaScript is fine. But once you have to display lists of stuff with lists of other stuff inside you basically need a template library otherwise you're going to be doing some pretty weird DOM manipulation to copy sections of elements. So a comparison that might resonate better with folks here might be to have a pure JS TODO app with a comment section in each list item. It becomes harder to express in pure HTML. You'll want some template library to keep it maintainable.
- dorfsmay 5y agoTemplate on the server? If your lists are long enough, you probably want to split what you can actually display from the rest of the list, because even today updating the DOM with thousands of nodes is expensive. And doing seems to be easier and less chatty to do on the front-end I think.
- eatonphil 5y agoSure templates on the server are fine too I just mean that the comparison is more compelling when describing the things that prompt templates in the first place (whether on the server or frontend).
- aszen 5y agoWhile this article doesn't do a good job of showing the benefits of state model, I still think it's a great way to build ui. Thinking of ui as derived state from your application model really helps in scaling ui applications reliably. Unfortunately react is not the best example of this approach. I would say try out elm or some of the clojurescript frameworks like reframe to truly grasp ui as application state
- specialist 5y agoHuh. This OC frames the debate as imperative vs declarative. Is that right? It just now occurs to me that React vs vanilla is another iteration of the MVC debate. I lived thru the Java AWT to Swing transition. One of Swing's Big Ideas was for every component (aka view & controller) to be backed by a separate model, finally mainstreaming the MVC design pattern. Then of course all the web frameworks got nutty over MVC. My only takeaway from the many, many MVC debates is that the only way to win is to not play.
- pictur 5y agoSometimes I think people don't know for what purpose projects like React are developed. Or they act like they don't know. Please stop praising your horrible code written in vanilla javascript.
- gherkinnn 5y agoI like most of React and am strongly against writing anything akin to an SPA in vanilla JS. (Whether or not an SPA is the right approach is debatable but besides the point) And yet this article is disingenuous. The OPs target can be achieved using CSS alone. In fact, quite a lot of things can be done quite well in vanilla JS and CSS. This works well to some degree. Then I found it to fall apart. - Those BE devs now forced to write FE in React|Vue|* and hate it so much that they've never learned it, won't learn a vanilla stack either. Especially not CSS. That's just for colours. Or something. - Most vanilla approaches I worked on relied heavily on the correct ordering of HTML elements with the correct classes for something to work. There's little to no encapsulation. Only copy and paste. It's also immensely brittle. - You can add some simple encapsulation, but the easier way is to brute force some imperative mess. Close ticket. Open next. Velocity is high. - The next step is to build your own framework. But chances are it will be worse than any 3rd party solution. - A huge advantage of 3rd party libs is that there's zero chance of some ticket specific code to erroneously make its way in to the framework. Silos aren't always bad. - Onboarding new devs is hard. There's probably no resources at hand. - At this point you've tied yourself in to knots and the actual problem, your product, isn't even close to being solved. It takes less than an hour to have a fast React|Vue|* application off the ground. Everything you need to build the product is in place. And with some care, it will remain fast. Alternatively, Ruby on Rails and friends work just as well. However, wasting endless hours reinventing a worse wheel is unproductive.