Remix.run Logo
efortis 3 months ago

I use a helper similar to React.createElement.

  const state = { count: 0 }

  const init = () => document.body.replaceChildren(App())
  init()
  
  function App() {
    return (
      h('div', null,
        h('output', null,  `Counter: ${state.count}`),
        h(IncrementButton, { incrementBy: 2 })))
  }
  
  function IncrementButton({ incrementBy }) {
    return (
      h('button', {
        className: 'IncrementButton',
        onClick() { 
          state.count += incrementBy
          init()
        }
      }, 'Increment'))
  }
  

  function h(elem, props = null, ...children) {
    if (typeof elem === 'function')
      return elem(props)
  
    const node = document.createElement(elem)
    if (props)
      for (const [key, value] of Object.entries(props))
        if (key === 'ref')
          value.current = node
    else if (key.startsWith('on'))
      node.addEventListener(key.replace(/^on/, '').toLowerCase(), value)
    else if (key === 'style')
      Object.assign(node.style, value)
    else if (key in node)
      node[key] = value
    else
      node.setAttribute(key, value)
    node.append(...children.flat().filter(Boolean))
    return node
  }

Working example of a dashboard for a mock server: https://github.com/ericfortis/mockaton/blob/main/src/Dashboa...
simonw 3 months ago | parent [-]

That looks like it replaces the entire document every time state changes. How's the performance of that?

WickyNilliams 3 months ago | parent [-]

Even if performance is fine, the big usability issue is that it will blow away focus, cursor position etc every render. Gets very painful for keyboard use, and of course is a fatal accessibility flaw

efortis 3 months ago | parent [-]

yes, that’s the downside, focus is lost on init()

WickyNilliams 3 months ago | parent [-]

Really wish the browser gave us better APIs for updating the DOM. Creation is extremely easy, but after that you either have to invent some reconciliation system or stick with imperative updates