Skip to content

How nested signals resolution should work? #140

Open

Description

@m-michalis

The bellow is a simple example of how i would expect (?) nested signals to work.

When clicking "to cart" it should change to "loading".

<html>
<body>
<div id="products"></div>
<script type="module">
    import {render, signal, html, effect, computed} from 'https://esm.run/uhtml/signal';

    class Cart {
        add(product) {
            console.log('adding to cart: ', product.name);
            return new Promise((resolve, reject) => {
                setTimeout(() => {
                    console.log('added to cart: ', product.name);
                    resolve();
                }, 2000);
            })
        }
    }

    const cart = new Cart();

    class Item {
        constructor(sData) {
            this.data = sData;
        }

        renderItem() {
            return html`
                <li>${this.data.value.name}
                    <button @click="${e => {
                        this.data.value.loading_cart = true
                        cart.add(this.data.value).then(() => {
                            this.data.value.loading_cart = false
                        });
                    }}">${this.data.value.loading ? 'loading' : 'to cart'}
                    </button>
                </li>`;
        }
    }

    class Catalog {
        #items = signal([]);
        #computed;
        #effect;

        constructor() {

            this.#computed = computed(() => this.#items.value.map(item => (new Item(signal(item))).renderItem()));
            this.#effect = effect(() => {
                render(document.getElementById('products'), () => html`
                    <ul>${this.#computed.value}</ul>`)
            });
        }

        addItem(value) {
            this.#items.value = [...this.#items.value, value];
        }

        setItems(items) {
            this.#items.value = items;
        }
    }

    const catalog = new Catalog();

    setTimeout(() => {
        catalog.setItems([
            {name: 'Shoes'},
            {name: 'T-Shirt'}
        ])
        setTimeout(() => {
            catalog.addItem({name: 'Hat'})
            catalog.addItem({name: 'Boots'})
        }, 1500)
    }, 500)


</script>
</body>
</html>

What would be a working example for the above?

Activity

  1. WebReflection commented on Oct 29, 2024

    @WebReflection
    Owner

    I think you need to re-assign value to trigger a signal, otherwise you are just updating its internal state without communicating any change happened at all?

    this.data.value.loading_cart = false
    this.data.value = this.data.value

    Signals need to notify their value, what's inside their value is never observed ... would that work?

  2. WebReflection commented on Oct 29, 2024

    @WebReflection
    Owner

    P.S. if that doesn't work due meomized value, try:

    this.data.value = {...this.data.value};

    assuming that data.value is just, indeed, data and nothing else ... pass a new thingy otherwise with new values.

  3. m-michalis commented on Oct 29, 2024

    @m-michalis
    Author

    it seems that it switches button text to "loading", but it doesnt trigger back.

    Updated the @click to simulate waiting time.

    (also there was a typo on the example. fixed bellow)

    class Item {
            #data = signal({});
    
            constructor(sData) {
                this.#data.value = sData;
            }
    
            cartLoading(state = false){
                if(this.#data.value.loading_cart !== state) {
                    this.#data.value.loading_cart = state
                    this.#data.value = {...this.#data.value}
                }
                return this;
            }
    
            renderItem() {
                return html`
                    <li>${this.#data.value.name}
                        <button @click="${e => {
                            this.cartLoading(true)
                            setTimeout(()=>{
                                this.cartLoading(false) // <-- this does not trigger the refresh again
                            },2000)
                        }}">${this.#data.value.loading_cart ? 'loading' : 'to cart'}
                        </button>
                    </li>`;
            }
        }
  4. WebReflection commented on Oct 29, 2024

    @WebReflection
    Owner

    this does not trigger the refresh again

    what if you remove if(this.#data.value.loading_cart !== state) ?

    there are various versions of signals in uhtml so if you are anyhow stuck try other variants with preact signals, as example, and let me know if that solves ... if it does, it's my own signal implementation (a different repo) that is doing something weird, if it doesn't, it's your signals based logic that is doing something weird ... I am exploring a rewrite of this library that is currently focused on other topics so my time is limited for this issue but I'd love to understand where, how, this doesn't work, as long as the underlying logic is sound, thanks for your comprehension

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions