Hacker News new | ask | show | jobs
by atirip 1802 days ago
Oh 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>
3 comments

Codepen for this:

https://codepen.io/uwwgo/pen/GRmEKJz

(nothing revolutionary, does the exact same thing.)

What does storing the state as a WeakMap do here?
I initially just mimicked the React version into Vanilla, so this was a show-off how to do that somewhat exactly the same way. But in this particular case it serves no meaning, we can always read 'checked' from DOM.
If the custom DOM element is deleted, then its entry in the map will also be deleted. Otherwise it would hang around in the Map forever (unless manually deleted in a lifecycle callback).
Why would component's state sit outside the component anyway? Why isn't it a field on the extended class?
True, it would make more sense to do that.
Well, technically it's not an equivalent as this would recreate dom nodes on every render
This 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>
> - with such a tiny DOM to rerender, it is equivalent, probably even faster

I hope you’re comparing this to React? Because if you’re comparing it to vanilla JS that reuses the DOM nodes, absolutely not.