justin-schroeder / arrow-js

Reactivity without the framework
https://arrow-js.com
MIT License
2.32k stars 50 forks source link

fails to render with multiple expressions in an attribute #64

Closed jukart closed 1 year ago

jukart commented 1 year ago

Here is a test which shows the problem:

import { html } from '..'

describe('extressions in attribute', () => {
  it('should render correctly with a single expression in an attribute', () => {
    const parent = document.createElement('div')
    html`<div class="${() => 'class'}" @click="${() => 'click'}">
      ${() => 'body'}
    </div>`(parent)
    expect(parent.innerHTML).toStrictEqual(`<div class="class">
      body
    </div>`)
  })

  it('should render correctly with multiple expressions in an attribute', () => {
    const parent = document.createElement('div')
    html`<div class="${() => 'class1'} ${() => 'class2'}">
      ${() => 'body'}
    </div>`(parent)
    expect(parent.innerHTML).toStrictEqual(
      `<div class="<!--➳❍--> <!--➳❍-->">
      class1
    </div>`
    )
  })
})
justin-schroeder commented 1 year ago

This is intentional. You can only provide 1 expression for your attribute. This allows arrow to be small, fast and efficient, and still gives you plenty of ability to do what you’re asking.

html`<div class="${() => 'class1'} ${() => 'class2'}"></div>

should be written:

html`<div class="${() => `${d.class1} ${d.class2}`"></div>
jukart commented 1 year ago

Ok thanks for the quick reply. I was bringing this up because it worked before. It broke some things in my app :) But that`s why it's alpha :)

jukart commented 1 year ago

@justin-schroeder Another note: This also fails: width="${width}px" It has to be this: width="${`${width}px`}"

I'd say this should be mentioned somewhere in the documentation because strange things are happening if you do it wrong.

justin-schroeder commented 1 year ago

Sure, we can mention this in the docs! FWIW, this is how most many frameworks work. If you bind to an attribute you need to provide the full value of that attribute:

React:

<div data-foo={width + 'px}></div> 

Vue:

<div :data-foo="`${width}px`"></div>
jukart commented 1 year ago

Fair enough, it just feels a bit different here because it looks like a "normal" template literal.

One more thing I am just struggling with: I have a checkbox input with checked="${false}" which is not removing the attribute. With checked="${() => false}" it works.

Btw. I love this library. It shows how bloated the existing Libraries are especially React (I don't know much about Vue).

Is there another place to discuss such things or is this Ok?

justin-schroeder commented 1 year ago

I think making it more clear in the docs, and eventually via more dev toolings will be helpful.

As for the ${false} vs ${() = >false} — in the first example you are directly interpolating "false" into your template just like you would with a normal template literal, there is no attribute binding at all there. Only expressions with functions are actually "bound" to the attribute 👍