Search code examples
signalspreact

How to listen to changes of an signal that contains a set in preact (using signal.value.has...)


In the follwoing minimal example I would like to change the buttons' text, after the click event. The button-ids that have been clicked, are stored in a set within a signal. When the set changes, no dom-update is triggered.

What am I doing wrong?

import { render } from 'preact';
import { signal, effect } from "@preact/signals-core";


export function App() {

    const selected = signal(new Set<number>);

    const onClick = (index) =>{
        console.log("click" + index +" current state:"+ selected.value.has(index))
        const s = new Set(selected.value)
        if(selected.value.has(index))
            s.delete(index)
        else
            s.add(index)

        selected.value = s
    }


    return (
        <div>
            <div>
            {new Array(10).fill(0).map((_, index) => (
                <button type="button" onClick={() => onClick(index)}>
                    {selected.value.has(index)+""}
                </button>
            ))}
            </div>
        </div>
    );

}

render(<App />, document.getElementById('app'));



Solution

  • Firstly, you should be using @preact/signals, not @preact/signals-core, in a Preact app. The former adds bindings that you need.

    Secondly, you cannot use signal inside of a component. Use useSignal() if you cannot extract out the signal creation.

    When creating signals within a component, use the hook variant: useSignal(initialValue).

    https://preactjs.com/guide/v10/signals#signalinitialvalue

    After that, it should work:

    import { render } from 'preact';
    import { useSignal } from '@preact/signals';
    
    export function App() {
      const selected = useSignal(new Set<number>);
    
      const onClick = (index) => {
        console.log(
          'click' + index + ' current state:' + selected.value.has(index)
        );
        const s = new Set(selected.value);
        if (selected.value.has(index)) s.delete(index);
        else s.add(index);
    
        selected.value = s;
      };
    
      return (
        <div>
          <div>
            {new Array(10).fill(0).map((_, index) => (
              <button type="button" onClick={() => onClick(index)}>
                {selected.value.has(index) + ''}
              </button>
            ))}
          </div>
        </div>
      );
    }
    
    render(<App />, document.getElementById('app'));