3 Reasons why I stopped using React.setState

3 Reasons why I stopped using React.setState


Michel Weststrate




Michel Weststrate



Published in





5 min read


Jun 15, 2016







Since a few months I’ve stopped using React’s setState on all my new React components. Don’t get me wrong, I didn’t stop having local component state, I just stopped using React to manage it. And it’s been delightful!

Using setState is tricky for beginners. Even experienced React programmers easily introduce subtle bugs when using React’s own state mechanism, like this:

Bug introduced by forgetting that React state is asynchronous; the log is always one item behind

The excellent Reacts docs sum up everything that could go wrong when using setState:

To summarize, there are 3 issues when using setState:

1. setState is asynchronous

Many devs don’t realize this initially, but setState is asynchronous. If you set some state, than take a look at it, you will still see the old state.This is the most tricky part of setState. setState calls don’t look asynchronous, and naively calling setState introduces very subtle bugs. The next gist demonstrates that nicely:

At first glance this might look fine. Two event handlers and a utility function that fire the onSelect event if provided. However, this Select component has a bug that is nicely demonstrated in the GIF above. onSelect is always fired with the previous value of state.selection, because setState hasn’t done it’s job yet when the fireOnSelect helper is invoked. I think the least React could do here is rename the method to scheduleState or make the callback required.

This bug is easily fixed, the tricky part is realizing it’s there.

Edit 25–jan–2018: Dan Abramov has been so kind to write down an extensive an sound explanation why setState is asynchronous by design.

2. setState causes unnecessary renders

The second issue with setState is that it always triggers a re-render. Often those re-renders are unnecessary. You can use the printWasted method from the React performance tools to find out when this happens. But roughly speaking there are several reasons why a re-render may be unnecessary:

  • The new state is actually the same as the previous one. This can often be addressed by implementing shouldComponentUpdate. You may already be using a (pure render) library to solve this for you.
  • Sometimes the changed state is relevant for the rendering, but not under all circumstances. For example when some data is only conditionally visible.
  • Thirdly, as pointed out in Aria Buckles’ talk at React Europe 2015, sometimes instance state is not relevant for the rendering at all! This is often householding state related to managing event listeners, timer ID’s etc.

3. setState is not sufficient to capture all component state

Following the last point above, not all component state should be stored and updated using setState. More complex components often have administration that is needed by lifecycle hooks to manage timers, network requests, events etc. Managing those with setState not only causes unnecessary renders, but also causes related lifecycle hooks to be triggered again, leading to weird situations.

Managing local component state with MobX

(Surprise, surprise) At Mendix we already rely on MobX to manage all our stores. However, we were still using React’s own state mechanism for local component state. Recently, we switched to managing local component state with MobX as well. That looks like this:

For completeness sake:

No unexpected bugs when using a state mechanism that is synchronous

The above code snippet is not only more concise, MobX also addresses all of the setState related issues:

Changes to the state are immediately reflected in the local component state. This makes our logic simpler and code reuse easier. You don’t have to compensate for the fact that the state might not have been updated yet.

MobX determines at runtime which observables are relevant for rendering. So observables that are temporarily irrelevant for the rendering, won’t cause a re-rendering. Until they are relevant again. For this reason, there are also no rendering penalties (or lifecycle issues) when marking fields as @observable that are not relevant for rendering at all.

So renderable and non-renderable state is treated uniformly. In addition, state stored in our components now works the same as state stored in any of our stores. This makes it trivial to refactor components, and move local component state into a separate store or vice versa. Which is demonstrated in this egghead tutorial.

MobX effectively turns your components into small stores

Furthermore, rookie mistakes like assigning values directly to the state object cannot be made anymore when using observables for state. Oh, and don’t worry about implementing shouldComponentUpdate or PureRenderMixin, MobX already takes care of that as well. Finally, you might be wondering, what if I want to wait until setState has finished? Well, you can still use the _compentDidUpdate l_ifecycle hook for that.

Sounds cool! How do I get started with MobX?

Pretty simple, follow the 10 minute interactive introduction or watch the aforementioned video. You can simply take a single component from your code base, slap @observer on it and introduce some @observable properties. You don’t even have to replace your existing setState calls, they continue to work while using MobX. Although, within a few minutes you might find them so convoluted that you will replace them anyway :). (Oh, and if you don’t like decorators, no worries, it works with good ol’ ES5 as well).


I’ve stopped using React to manage local component state. I use MobX instead. Now React is truly “just the view” :). MobX now manages both local component state and state in stores. It is concise, synchronous, efficient and uniform. From experience, I’ve learned that MobX is even easier to explain to React beginners than React’s own setState. It keeps our components clean and simple.

  • JSBin using setState for state management
  • JSBin using MobX observables for state management

Declarative Shadow DOM

Declarative Shadow DOM

A new way to implement and use Shadow DOM directly in HTML.

Published on Friday, February 17, 2023

Translated to: Español, Português

Jason Miller

Jason Miller

Jason is a Web DevRel focused on speed and the JS ecosystem.

Website Twitter GitHub

Mason Freed

Mason Freed

Developer on the Chrome team

Twitter GitHub

Table of contents

Declarative Shadow DOM is a web platform feature, currently in the standardization process. It is enabled by default in Chrome version 111.

Declarative Shadow DOM has been available since Chrome 90 and Edge 91, but it used an older non-standard attribute called shadowroot instead of the standardized shadowrootmode attribute.

Shadow DOM is one of the three Web Components standards, rounded out by HTML templates and Custom Elements. Shadow DOM provides a way to scope CSS styles to a specific DOM subtree and isolate that subtree from the rest of the document. The <slot> element gives us a way to control where the children of a Custom Element should be inserted within its Shadow Tree. These features combined enable a system for building self-contained, reusable components that integrate seamlessly into existing applications just like a built-in HTML element.

Until now, the only way to use Shadow DOM was to construct a shadow root using JavaScript:

const host = document.getElementById('host');const shadowRoot = host.attachShadow({mode: 'open'});shadowRoot.innerHTML = '<h1>Hello Shadow DOM</h1>';

An imperative API like this works fine for client-side rendering: the same JavaScript modules that define our Custom Elements also create their Shadow Roots and set their content. However, many web applications need to render content server-side or to static HTML at build time. This can be an important part of delivering a reasonable experience to visitors who may not be capable of running JavaScript.

The justifications for Server-Side Rendering (SSR) vary from project to project. Some websites must provide fully functional server-rendered HTML in order to meet accessibility guidelines, others choose to deliver a baseline no-JavaScript experience as a way to guarantee good performance on slow connections or devices.

Historically, it has been difficult to use Shadow DOM in combination with Server-Side Rendering because there was no built-in way to express Shadow Roots in the server-generated HTML. There are also performance implications when attaching Shadow Roots to DOM elements that have already been rendered without them. This can cause layout shifting after the page has loaded, or temporarily show a flash of unstyled content (“FOUC”) while loading the Shadow Root’s stylesheets.

Declarative Shadow DOM (DSD) removes this limitation, bringing Shadow DOM to the server.

# Building a Declarative Shadow Root

A Declarative Shadow Root is a <template> element with a shadowrootmode attribute:

<host-element>  <template shadowrootmode="open">    <slot></slot>  </template>  <h2>Light content</h2></host-element>

A template element with the shadowrootmode attribute is detected by the HTML parser and immediately applied as the shadow root of its parent element. Loading the pure HTML markup from the above sample results in the following DOM tree:

<host-element>  #shadow-root (open)  <slot><h2>Light content</h2>  </slot></host-element>

This code sample is following the Chrome DevTools Elements panel’s conventions for displaying Shadow DOM content. For example, the ↳ character represents slotted Light DOM content.

This gives us the benefits of Shadow DOM’s encapsulation and slot projection in static HTML. No JavaScript is needed to produce the entire tree, including the Shadow Root.

# Component hydration

Declarative Shadow DOM can be used on its own as a way to encapsulate styles or customize child placement, but it’s most powerful when used with Custom Elements. Components built using Custom Elements get automatically upgraded from static HTML. With the introduction of Declarative Shadow DOM, it’s now possible for a Custom Element to have a shadow root before it gets upgraded.

A Custom Element being upgraded from HTML that includes a Declarative Shadow Root will already have that shadow root attached. This means the element will have a shadowRoot property already available when it is instantiated, without your code explicitly creating one. It’s best to check this.shadowRoot for any existing shadow root in your element’s constructor. If there is already a value, the HTML for this component includes a Declarative Shadow Root. If the value is null, there was no Declarative Shadow Root present in the HTML or the browser doesn’t support Declarative Shadow DOM.

<menu-toggle>  <template shadowrootmode="open">    <button>      <slot></slot>    </button>  </template>  Open Menu</menu-toggle><script>  class MenuToggle extends HTMLElement {    constructor() {      super();      // Detect whether we have SSR content already:      if (this.shadowRoot) {        // A Declarative Shadow Root exists!        // wire up event listeners, references, etc.:        const button = this.shadowRoot.firstElementChild;        button.addEventListener('click', toggle);      } else {        // A Declarative Shadow Root doesn't exist.        // Create a new shadow root and populate it:        const shadow = this.attachShadow({mode: 'open'});        shadow.innerHTML = `<button><slot></slot></button>`;        shadow.firstChild.addEventListener('click', toggle);      }    }  }  customElements.define('menu-toggle', MenuToggle);</script>

Custom Elements have been around for a while, and until now there was no reason to check for an existing shadow root before creating one using attachShadow(). Declarative Shadow DOM includes a small change that allows existing components to work despite this: calling the attachShadow() method on an element with an existing Declarative Shadow Root will not throw an error. Instead, the Declarative Shadow Root is emptied and returned. This allows older components not built for Declarative Shadow DOM to continue working, since declarative roots are preserved until an imperative replacement is created.

For newly-created Custom Elements, a new ElementInternals.shadowRoot property provides an explicit way to get a reference to an element’s existing Declarative Shadow Root, both open and closed. This can be used to check for and use any Declarative Shadow Root, while still falling back to attachShadow() in cases where one was not provided.

class MenuToggle extends HTMLElement {  constructor() {    super();    const internals = this.attachInternals();    // check for a Declarative Shadow Root:    let shadow = internals.shadowRoot;    if (!shadow) {      // there wasn't one. create a new Shadow Root:      shadow = this.attachShadow({        mode: 'open'      });      shadow.innerHTML = `<button><slot></slot></button>`;    }    // in either case, wire up our event listener:    shadow.firstChild.addEventListener('click', toggle);  }}customElements.define('menu-toggle', MenuToggle);

# One shadow per root

A Declarative Shadow Root is only associated with its parent element. This means shadow roots are always colocated with their associated element. This design decision ensures shadow roots are streamable like the rest of an HTML document. It’s also convenient for authoring and generation, since adding a shadow root to an element does not require maintaining a registry of existing shadow roots.

The tradeoff of associating shadow roots with their parent element is that it is not possible for multiple elements to be initialized from the same Declarative Shadow Root <template>. However, this is unlikely to matter in most cases where Declarative Shadow DOM is used, since the contents of each shadow root are seldom identical. While server-rendered HTML often contains repeated element structures, their content generally differs–for example, slight variations in text, or attributes. Because the contents of a serialized Declarative Shadow Root are entirely static, upgrading multiple elements from a single Declarative Shadow Root would only work if the elements happened to be identical. Finally, the impact of repeated similar shadow roots on network transfer size is relatively small due to the effects of compression.

In the future, it might be possible to revisit shared shadow roots. If the DOM gains support for built-in templating, Declarative Shadow Roots could be treated as templates that are instantiated in order to construct the shadow root for a given element. The current Declarative Shadow DOM design allows for this possibility to exist in the future by limiting shadow root association to a single element.

# Streaming is cool

Associating Declarative Shadow Roots directly with their parent element simplifies the process of upgrading and attaching them to that element. Declarative Shadow Roots are detected during HTML parsing, and attached immediately when their opening <template> tag is encountered. Parsed HTML within the <template> is parsed directly into the shadow root, so it can be “streamed”: rendered as it is received.

<div id="el">  <script>    el.shadowRoot; // null  </script>  <template shadowrootmode="open">    <!-- shadow realm -->  </template>  <script>    el.shadowRoot; // ShadowRoot  </script></div>

# Parser-only

Declarative Shadow DOM is a feature of the HTML parser. This means that a Declarative Shadow Root will only be parsed and attached for <template> tags with a shadowrootmode attribute that are present during HTML parsing. In other words, Declarative Shadow Roots can be constructed during initial HTML parsing:

<some-element>  <template shadowrootmode="open">    shadow root content for some-element  </template></some-element>

Setting the shadowrootmode attribute of a <template> element does nothing, and the template remains an ordinary template element:

const div = document.createElement('div');const template = document.createElement('template');template.setAttribute('shadowrootmode', 'open'); // this does nothingdiv.appendChild(template);div.shadowRoot; // null

To avoid some important security considerations, Declarative Shadow Roots also can’t be created using fragment parsing APIs like innerHTML or insertAdjacentHTML(). The only way to parse HTML with Declarative Shadow Roots applied is to pass a new includeShadowRoots option to DOMParser:

<script>  const html = `    <div>      <template shadowrootmode="open"></template>    </div>  `;  const div = document.createElement('div');  div.innerHTML = html; // No shadow root here  const fragment = new DOMParser().parseFromString(html, 'text/html', {    includeShadowRoots: true  }); // Shadow root here</script>

# Server-rendering with style

Inline and external stylesheets are fully supported inside Declarative Shadow Roots using the standard <style> and <link> tags:

<nineties-button>  <template shadowrootmode="open">    <style>      button {        color: seagreen;      }    </style>    <link rel="stylesheet" href="/comicsans.css" />    <button>      <slot></slot>    </button>  </template>  I'm Blue</nineties-button>

Styles specified this way are also highly optimized: if the same stylesheet is present in multiple Declarative Shadow Roots, it is only loaded and parsed once. The browser uses a single backing CSSStyleSheet that is shared by all of the shadow roots, eliminating duplicate memory overhead.

Constructable Stylesheets are not supported in Declarative Shadow DOM. This is because there is currently no way to serialize constructable stylesheets in HTML, and no way to refer to them when populating adoptedStyleSheets.

# Avoiding the flash of unstyled content

One potential issue in browsers that do not yet support Declarative Shadow DOM is avoiding “flash of unstyled content” (FOUC), where the raw contents are shown for Custom Elements that have not yet been upgraded. Prior to Declarative Shadow DOM, one common technique for avoiding FOUC was to apply a display:none style rule to Custom Elements that haven’t been loaded yet, since these have not had their shadow root attached and populated. In this way, content is not displayed until it is “ready”:

<style>  x-foo:not(:defined) > * {    display: none;  }</style>

With the introduction of Declarative Shadow DOM, Custom Elements can be rendered or authored in HTML such that their shadow content is in-place and ready before the client-side component implementation is loaded:

<x-foo>  <template shadowrootmode="open">    <style>h2 { color: blue; }</style>    <h2>shadow content</h2>  </template></x-foo>

In this case, the display:none “FOUC” rule would prevent the declarative shadow root’s content from showing. However, removing that rule would cause browsers without Declarative Shadow DOM support to show incorrect or unstyled content until the Declarative Shadow DOM polyfill loads and converts the shadow root template into a real shadow root.

Fortunately, this can be solved in CSS by modifying the FOUC style rule. In browsers that support Declarative Shadow DOM, the <template shadowrootmode> element is immediately converted into a shadow root, leaving no <template> element in the DOM tree. Browsers that don’t support Declarative Shadow DOM preserve the <template> element, which we can use to prevent FOUC:

<style>  x-foo:not(:defined) > template[shadowrootmode] ~ *  {    display: none;  }</style>

Instead of hiding the not-yet-defined Custom Element, the revised “FOUC” rule hides its children when they follow a <template shadowrootmode> element. Once the Custom Element is defined, the rule no longer matches. The rule is ignored in browsers that support Declarative Shadow DOM because the <template shadowrootmode> child is removed during HTML parsing.

# Feature detection and browser support

Declarative Shadow DOM has been available since Chrome 90 and Edge 91, but it used an older non-standard attribute called shadowroot instead of the standardized shadowrootmode attribute. The newer shadowrootmode attribute and streaming behavior is available in Chrome 111 and Edge Xyz.

As a new web platform API, Declarative Shadow DOM does not yet have widespread support across all browsers. Browser support can be detected by checking for the existence of a shadowRootMode property on the prototype of HTMLTemplateElement:

function supportsDeclarativeShadowDOM() {  return HTMLTemplateElement.prototype.hasOwnProperty('shadowRootMode');}

# Polyfill

Building a simplified polyfill for Declarative Shadow DOM is relatively straightforward, since a polyfill doesn’t need to perfectly replicate the timing semantics or parser-only characteristics that a browser implementation concerns itself with. To polyfill Declarative Shadow DOM, we can scan the DOM to find all <template shadowrootmode> elements, then convert them to attached Shadow Roots on their parent element. This process can be done once the document is ready, or triggered by more specific events like Custom Element lifecycles.

(function attachShadowRoots(root) {  root.querySelectorAll("template[shadowrootmode]").forEach(template => {    const mode = template.getAttribute("shadowrootmode");    const shadowRoot = template.parentNode.attachShadow({ mode });    shadowRoot.appendChild(template.content);    template.remove();    attachShadowRoots(shadowRoot);  });})(document);

# Further reading

DOM HTML JavaScript Rendering

Published on Friday, February 17, 2023 • Improve article

【工作坊】Web 自动化测试

2022 新年钟声的敲响有没有让开发小哥哥、测试小姐姐们心头一紧?马上过年了,2021 年的项目还一堆功能和 bug 没搞完,真令人头秃……

小编干了小十年开发,每到项目交付前,再美的测试小姐姐我每天也不想见到她。因为大多数团队每次为了赶进度,在一期工程基本一屁股技术债 ——



码农三大懒 —— 变量命名、注释/文档、测试代码


…… 也不是不可以抢救一下~

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now