How do I simply mutate the array inside a signal

5.1k views Asked by At

This is a working example, with a problem. I want to splice an existing array inside a signal, and return it to see it updated. But it doesn't work. How do I simply mutate the array inside a signal? I don't want to create new arrays just a simple splice. There is no example in the docs about mutating an array.

import {  render } from 'solid-js/web';
import { createSignal, createEffect } from 'solid-js'

function HelloWorld() {
  let [a, setA] = createSignal([])

  setTimeout(() => 
  setA(a => {
    a.splice(0, 0, 'hello')
    // this logs as requested if I uncomment this
    //return ['hello']
    return a
  }))


  createEffect(() => {
    console.log(a())
  })
  return <div>Hello World!</div>;
}

render(() => <HelloWorld />, document.getElementById('app'))
2

There are 2 answers

1
Nick On BEST ANSWER

The Solid tutorial strongly recommends immutability:

Solid strongly recommends the use of shallow immutable patterns for updating state. By separating reads and writes we maintain better control over the reactivity of our system without the risk of losing track of changes to our proxy when passed through layers of components.

An immutable way to accomplish what you're going for might look something like this:

setA(a => ['hello', ...a])

If, however, you determine you must mutate the signal, you can specify a how Solid determines if a signal has been updated within createSignal's options object. By default, signal changes are compared by referential equality using the === operator. You can tell Solid to always re-run dependents after the setter is called by setting equals to false:

let [a, setA] = createSignal([], { equals: false });

Or you can pass a function that takes the previous and next values of the signal and returns a boolean:

let [a, setA] = createSignal([], { equals: (prev, next) => {
  if (/* dependents should run */) {
    return false;
  }
  // dependents shouldn't run
  return true;
} });
0
mohamedmoussa On

Warning: I'm new to Solid.js and Javascript/Typescript in general. What I've done below might be a really bad idea.

I came across the same issue and figured out the following. It seems to work. Basically keep the value (e.g. a very large array) in a lightweight wrapped type. Recreate the wrapped type instead of recreating the array. The function createSignalMutable below returns a getter function that returns the unwrapped value, and an updater function that updates the unwrapped value.

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

type ThinWrapper<T> = {
  internal: T;
};

function createSignalMutable<T>(value: T) {
  let [getter, setter] = createSignal<ThinWrapper<T>>({ internal: value });

  let getUnwrapped = () => getter().internal;

  let updateUnwrapped = (mutator: (value: T) => void) =>
    setter((oldWrapped: ThinWrapper<T>) => {
      mutator(oldWrapped.internal);
      let newWrapped: ThinWrapper<T> = { internal: value };
      return newWrapped;
    });

  return [getUnwrapped, updateUnwrapped];
}

let initialValue: number[] = [];
let [numbers, updateNumbers] = createSignalMutable(initialValue);

createEffect(() => {
  console.log("Array length is now ", [...numbers()].length);
});

updateNumbers((val) => val.push(1));
updateNumbers((val) => val.push(2));

This will print

Array length is now  0
Array length is now  1
Array length is now  2

Plyground link here.