4

I have a Component in SolidJS that looks something like this:

const MyComponent: Component = (params) => {
  // ...
  const [votes, setVotes] = createSignal(new Set());
  const toggle = (val: string) => {
    if (votes().has(val)) {
      let _votes = votes();
      _votes.delete(val);
      setVotes(_votes);
    } else {
      let _votes = votes();
      _votes.add(val);
      setVotes(_votes);
    }
  }

  return <>
    <For each={someArray()}>{(opt, i) =>
      <button 
        style={{
          color: `${ votes().has(opt) ? "blue" : "black"}`
        }} 
        onClick={() => { toggle(opt) } }
      >
        {opt()}
      </button>
    }</For>
  </>
}

What I want to happen, is that the styling of the button will change, based on wheter it (opt in the for loop) is present in the votes set.

However, this code does not work. It doesn't update when the votes are updated.

The votes variable does update as expected.

When I give the votes set an initial value, the correct style is displayed (but it isn't updated afterwards).

Is there any solution to make this work?

snnsnn
  • 10,486
  • 4
  • 39
  • 44
Jomy
  • 514
  • 6
  • 22
  • 2
    It looks like you're mutating the same `Set` the entire time. You might want to create a new Set in your `toggle` function, something like `let _votes = new Set(votes());` – Nick May 19 '22 at 00:25

3 Answers3

7

What @Nick said seems to be correct, try changing your toggle method to:

  const toggle = (val: string) => {
    const _votes = votes();
    _votes.has(val) ? _votes.delete(val) : _votes.add(val);
    setVotes(new Set(_votes));
  }

Playground

Owl
  • 6,337
  • 3
  • 16
  • 30
  • 1
    Great solution, works as intended now. @lexLohr's solution seems cleaner as it doesn't require the setVotes. – Jomy May 19 '22 at 09:02
6

There is a reactive Set primitive in @solid-primitives/set that you can directly use as state without createSignal. This might simplify things for you.

artem
  • 46,476
  • 8
  • 74
  • 78
lexLohr
  • 201
  • 1
  • 3
4

The question has an accepted answer but actual question is how to track values stored in a set, not how to style an element. Scroll down to see how to style an element.

Solid does not track Sets out of the box because it relies on the Proxy API for tracking changes on an object. There are several alternatives but problem is mutating a set does not trigger an effect unless we replace it with a new set like setVotes(new Set(votes().values())); because sets are reference values. Since you are passing the same value in your question, signal is not updated.

import { render } from "solid-js/web";
import { createSignal } from "solid-js";

const App = () => {
  const [votes, setVotes] = createSignal(new Set());

  const castVote = () => {
    if (votes().has(4)) {
      votes().delete(4);
    } else {
      votes().add(4);
    }
    setVotes(new Set(votes().values()));
  };

  return (
    <>
      <button
        style={{
          color: votes().has(4) ? "blue" : "red",
        }}
        onClick={castVote}
      >
        Toggle
      </button>
    </>
  );
};
render(App, document.getElementById("app")!);

Now, on updating an element's style.

Currently there are two ways to define an inline style on an element:

  • Using an object with kebab-case keys.
  • Using a plain string as we do with HTML elements.

You can use a ternary operation or other conditional expressions to create a value conditionally.

const App = () => {
  const [cond, setCond] = createSignal(true);

  const toggle = () => setCond(c => !c);

  return (
    <div onclick={toggle}>
      <span>conditon: {cond() ? 'true' : 'false'}</span>
      {` `}
      <span
        style={{ 'background-color': cond() ? 'red' : 'blue', color: 'white' }}
      >
        Using Object
      </span>
      {` `}
      <span
        style={`background-color: ${cond() ? 'red' : 'blue'}; color: white;`}
      >
        Using String
      </span>
    </div>
  );
};

render(App, document.querySelector('#app'));
snnsnn
  • 10,486
  • 4
  • 39
  • 44