S
SolidJS2mo ago
J

Question Relating to Arrays

Hey, I have been working with Svelte for the past couple months and I am thinking about rewriting one of my projects in Solid. One of the biggest things that plighted me with Svelte was the lack of support for nested reactivity. I understand that Solid supports nested reactivity via stores, but in my project I am working with arrays all the time, especially reordering elements in arrays. Now my question, how, for example, would I reorder elements in an array based off a user provided index? After looking through the docs I have only seen examples of prepending and appending of arrays, and even those examples are slightly confusing. A simple and idiomatic code example with an explanation would be extremely helpful. Thanks in advance and I apologize if this question has already been answered somewhere.
13 Replies
Maciek50322
Maciek503222mo ago
Not sure If that's what you mean:
const [array, setArray] = createStore([
{ id: 1, x: 5 },
{ id: 2, x: 4 },
{ id: 3, x: 6 },
]);


// way 1 - copy and change array
const [userProvidedIndex, setUserProvidedIndex] = createSignal("id");
const onInput = (idx) => {
// update index signal by user input
setUserProvidedIndex(idx);
};
const sortedArray = createMemo((prev) => {
const idx = userProvidedIndex();
// if index doesn't fit, skip sorting, keep old sortedArray or original array if it's first calculation
if (!(idx in array[0])) return prev ?? array;
// sort by new index and set new array value
return [...array].sort((a, b) => a[idx] - b[idx]);
});


// way 2 - change original array
const onInput = (idx) => {
// if index doesn't fit, skip sorting
if (!(idx in array[0])) return;
// sort by new index and set new array value
setArray((prev) => [...prev].sort((a, b) => a[idx] - b[idx]));
};
const [array, setArray] = createStore([
{ id: 1, x: 5 },
{ id: 2, x: 4 },
{ id: 3, x: 6 },
]);


// way 1 - copy and change array
const [userProvidedIndex, setUserProvidedIndex] = createSignal("id");
const onInput = (idx) => {
// update index signal by user input
setUserProvidedIndex(idx);
};
const sortedArray = createMemo((prev) => {
const idx = userProvidedIndex();
// if index doesn't fit, skip sorting, keep old sortedArray or original array if it's first calculation
if (!(idx in array[0])) return prev ?? array;
// sort by new index and set new array value
return [...array].sort((a, b) => a[idx] - b[idx]);
});


// way 2 - change original array
const onInput = (idx) => {
// if index doesn't fit, skip sorting
if (!(idx in array[0])) return;
// sort by new index and set new array value
setArray((prev) => [...prev].sort((a, b) => a[idx] - b[idx]));
};
Stores are bit more complex signals, you can always replace them with whole new value, you can create new derived value and you can replace each element, so there are several ways to change / sort / append it.
J
J2mo ago
Thank you for such a thorough response but I really should have phrased my question better. My brain is not functioning at full speed right now since it's 3am, apologies. Here's a better question: What's the idiomatic SolidJS way of doing a reactive splice? Sorting the entire array seems like a rather inefficient way of accomplishing a move
Maciek50322
Maciek503222mo ago
I think you can simply use the splice the array, and set array's signal new value
const [array, setArray] = createStore([
{ id: 1, x: 5 },
{ id: 2, x: 4 },
{ id: 3, x: 6 },
])
const onIndexClick = (idx) => {
const copy = [...array];
copy.splice(idx, 1);
setArray(copy);
}
const [array, setArray] = createStore([
{ id: 1, x: 5 },
{ id: 2, x: 4 },
{ id: 3, x: 6 },
])
const onIndexClick = (idx) => {
const copy = [...array];
copy.splice(idx, 1);
setArray(copy);
}
TaQuanMinhLong
TaQuanMinhLong2mo ago
I think using a store would be much cleaner, you can just batch update one to the other for swapping operation
Maciek50322
Maciek503222mo ago
You can, so with swapping values above example would be something like that
const [array, setArray] = createStore([
{ id: 1, x: 5 },
{ id: 2, x: 4 },
{ id: 3, x: 6 },
])
const onIndexClick = (idx) => {
const length = array.length;
batch(() => {
for (let i = idx; i < length - 1; i++) {
setArray(i, array[i + 1]);
}
setArray("length", length - 1);
})
}
const [array, setArray] = createStore([
{ id: 1, x: 5 },
{ id: 2, x: 4 },
{ id: 3, x: 6 },
])
const onIndexClick = (idx) => {
const length = array.length;
batch(() => {
for (let i = idx; i < length - 1; i++) {
setArray(i, array[i + 1]);
}
setArray("length", length - 1);
})
}
Both ways are valid, one might argue about performance but it's really dependent on use case & if you track each element separately or if you track whole array
J
J2mo ago
Well, unfortunately if these are the best ways to perform a reactive move, Solid's DX is really not looking so good. This is worse than Svelte unless there's some kind of performance advantage.
Maciek50322
Maciek503222mo ago
This seems to be the simplest way, not sure for which "best" you look for
J
J2mo ago
Well ideally you would be able to avoid copying the array at all, just performing a splice somehow
Maciek50322
Maciek503222mo ago
I think you are looking for createMutable
const array = createMutable([
{ id: 1, x: 5 },
{ id: 2, x: 4 },
{ id: 3, x: 6 },
])

createEffect(() => {
console.log(array)
});

// triggers effect
array.splice(1, 1);
const array = createMutable([
{ id: 1, x: 5 },
{ id: 2, x: 4 },
{ id: 3, x: 6 },
])

createEffect(() => {
console.log(array)
});

// triggers effect
array.splice(1, 1);
It uses proxies to perform any operation on object / it's properties, but also has it's own pitfalls Previously I created copies of array to let setters know that the value actually changed (because solid performs equality check on each set by default - this can be avoided with signal's options by doing)
const [value, setValue] = createSignal(123, { equals: false })
const [value, setValue] = createSignal(123, { equals: false })
With this { equals: false }, everywhere value() is used, it will be triggered on setValue even if the previous value is the same Note that with createMutable lots of setting and getting signals is implicit, and you can create infinite loop with
createEffect(() => {
array[0].x += 1;
});
createEffect(() => {
array[0].x += 1;
});
Infinite loop happens, because array[0].x += 1; is shorthand for array[0].x = array[0].x + 1; and so on the left side we have implicit setter, and on right side we have implicit getter, that means the effect is subscribed to array[0].x getter, but also it triggers change with setter array[0].x = on the same entry, and because setter triggers getter, effect runs again after it sets new value
mdynnl
mdynnl2mo ago
we also have produce if you love the usual mutative array methods .toSpliced is also an option if you can polyfill in case it wasn't obvious to anyone
setStore(orig => {
const copy = [...orig] // we need a copy(new reference) to trigger reactivity
// whatever you want with the copy
return copy

// as far as the setStore is concerned doing this also works
return [...orig]
})
setStore(orig => {
const copy = [...orig] // we need a copy(new reference) to trigger reactivity
// whatever you want with the copy
return copy

// as far as the setStore is concerned doing this also works
return [...orig]
})
but if you need anything more complex than what's possible with the available api, use produce
J
J2mo ago
Yeah, after looking through the docs and playing around with some stuff myself, produce seems to be what I am looking for! Thank you guys for the help I really appreciated it
TaQuanMinhLong
TaQuanMinhLong2mo ago
is anything inside produce consider as batch update or line by line update?
mdynnl
mdynnl2mo ago
setStore is batched
Want results from more Discord servers?
Add your server