alpinejs / alpine

A rugged, minimal framework for composing JavaScript behavior in your markup.
https://alpinejs.dev
MIT License
28.43k stars 1.23k forks source link

add-use-state: Create useState magic #4417

Open MaquinaTech opened 3 weeks ago

MaquinaTech commented 3 weeks ago

Hello everyone. I have created a new magic property to increase security in CSP-compliant developments. Additionally, you can send variables by parameters to a function, change its value and have it extended. I hope it helps you and I would love it if we could all improve this new implementation if necessary.

SimoTod commented 3 weeks ago

Out of curiosity, what's the difference between a state and the already existing Alpine.stores?

I think the example we'll not run with the CSP build of Alpine, not sure if it makes more sense to show a fully functioning example if the aim is to help CSP complaint sites

MaquinaTech commented 3 weeks ago

The difference between Alpine.store and $useState is that the store defines the data at a global level and $useState does it at a local level respecting the component hierarchy. The communication between Alpine components is quite poor and define global variables degrades it even more.

MaquinaTech commented 3 weeks ago

To respect CSP, direct insertions into variables in the HTML are not allowed, for example @click="count++"

ekwoka commented 3 weeks ago

I don't see why this should be a core plugin.

the naming is also confusing for this use case.

Does the current CSP build allow...function call expressions?

Checked. It does not.

So this still won't evaluate in expressions anyway.

Instead of this, I'd really just prefer a more advanced expression parser build into the CSP evaluator.

SimoTod commented 3 weeks ago

The difference between Alpine.store and $useState is that the store defines the data at a global level and $useState does it at a local level respecting the component hierarchy. The communication between Alpine components is quite poor and define global variables degrades it even more.

Right. In that case, I don't think it solves a big problem in Alpine that would suggest it's needed in the core or even as a first class plugin. x-data has already reactive data scoped locally, the state you define is just a wrapper around a variable.

I had a good look at your PR and I can see why you do it, you are trying to define pure functions that only modify the state and you were not happy because you can't do it for things such as integer literals because they are not references in javascript (like objects are).

<div x-data="{
  count: 0,
  state: {count: 0}
}">
    <button
        @click="increment(count)" // This does not work
        x-text="count"
    ></button>
    <button
        @click="increment2(state)" // This work
        x-text="state.count"
    ></button>
</div>

<script>
    function increment(state) {
        state++;
    }
    function increment2(state) {
        state.count++;
    }
</script>

but in doing so, you are forcing people to use .state and .setState() so it's adds more boilerplate (the majority of the devs will object that it should be transparent like proxy objects to remove DX frictions).

You may not agree with the approach but the Alpine way to do it (or at least one of them) would be to define the logic in your component and not in the global scope so it would look more like

<div x-data="{
  count: 0,
  increment() {this.count++}
}">
    <button
        @click="increment"
        x-text="count"
    ></button>
</div>

or, for those not liking js in the html attributes (CSP build as well), something like

<script>
    document.addEventListener('alpine:init', () => {
        Alpine.data('counter', () => ({
            count: 0,
            increment() {this.count++}
        })
    })
</script>
<div x-data="counter">
    <button
        @click="increment"
        x-text="count"
    ></button>
</div>

That being said, if you like the approach, you can use it in your own project. You can even publish a third party plugin for you to use in all your projects and for other likeminded people who likes the approach but I personally don't feel like it belongs to the core.

MaquinaTech commented 3 weeks ago

I find your answer very interesting @SimoTod . You are right in what you say and I will add this tool as an external plugin. I think the most important use of this is to be able to modify the input parameters just like you do in increment2 in a consistent and simple way.