Recommendation for updating an item in-place
I would like to update a signal in an array in-place at an index, analogous to array[index] = value with a standard array. I haven't found a way to do so with a built-in SArray method.
I discovered that .find() returns a signal rather than the raw value, and I hoped this would be a means of updating an item in the array. However, that does not appear to be the case. It seems that (like the other methods SArray provides) this returns a result of the original SArray but doesn't update the original SArray.
For example, I would expect this code:
const testArray = SArray([1, 2, 3]);
S.root(() => {
S(function logFromComputation () {
console.log("Logging from computation");
console.log(testArray().join(', '));
});
const testItem = testArray.find(item => item === 3);
testItem(5);
console.log("Logging explicitly");
console.log(testArray().join(', '));
})
To result in:
Logging from computation
1, 2, 3
Logging from computation
1, 2, 5
Logging explicitly
1, 2, 5
But instead it results in:
Logging from computation
1, 2, 3
Logging explicitly
1, 2, 3
A couple of workarounds I've thought of:
- Using
.splice()to replace the original value with a new one (kind of ugly, and requires evaluating the signal to get the index since.indexOf()isn't implemented) - Wrapping each value in a signal (requires additional
()everywhere the value is used, and I don't think updates to those signals propagate back to the parent SArray)
Do you have another recommendation for this use case, or should I go with one of those workarounds?
For what it's worth, it works perfectly with the second workaround, wrapping each value in a signal, although I had to evaluate the SArray and call the result's Array.find(). It's a little ugly but I think I can live with it:
const testArray = SArray([1, 2, 3].map(item => S.data(item)));
S.root(() => {
S(function logFromComputation () {
console.log("Logging from computation");
console.log(testArray.map(signal => signal())().join(', '));
});
const testItem = testArray().find(item => item() === 3);
testItem(5);
console.log("Logging explicitly");
console.log(testArray.map(signal => signal())().join(', '));
})
Surprisingly, the changes propagate back to the parent SArray which is exactly what I want!
So is this potentially a bug with SArray.find()? Or am I misunderstanding how to use it?
Sorry for the slow reply, had a deadline and then some travel.
Yeah, I was going to suggest (with a caveat which I'll get to) your second solution: an array of data signals. Essentially, data signals define the granularity of changes we care about. By default, SArray defines that as the whole array: you supply, listen to, and modify the whole array. If you want the capability to have slot-level changes, then an array of data signals is probably the way to go. In case you didn't happen to see it, there's a utility, .combine(), which can be useful to convert that back into an array of values.
The caveat is that SArray is in general under-designed. I spent a while playing around with different approaches to a collection library, all of which seemed to have shortcomings. So in the end I went with the "caveman" approach of paralleling native JS Array methods.
On that line, missing indexOf() is a goof. I'll fix that. Thanks for pointing it out!
The surprise, for me, has been how long this "caveman" approach has worked :/.
No worries on reply time, especially since it was a holiday weekend.
It makes sense now that SArray defines the granularity of relevant changes as the whole array, and this seems to provide more of that control without cluttering the API, which is great. Come to think of it, creating an SArray of computations as well as data signals could be quite powerful. It makes sense to control it at that level and isn't that much overhead. Some explanation in the documentation would help clarify this, though.
I wasn't aware of .combine() - it looks like .map() uses that under the hood, so in a sense I've already been using it :) I'll play with that on its own and try to get a feel for it.
You certainly know much better than I do what appropriate API choices would look like, but I don't think it was a bad one to go with paralleling the native Array methods. From a consumer perspective, you get the API "for free" because you don't have to learn any new methods.
Thanks for your reply and for S/SArray/Surplus! I'm really enjoying working with them.
Also, FYI, in addition to .indexOf(), these are also missing:
- findIndex
- join
- lastIndexOf
Methods that take a predicate (like findIndex) seem most useful for this use case, since you can evaluate the contained signals/computations within the predicate.
Adam, could a possibility to resolve issues like this be to expose the mutation() function from SArray? E.g. you could then just pass in your mutator directly to the underlying values, something like this:
// In SArray
function mutate(fn : (values : T[]) => void) {
mutation(function() {
fn(values);
});
}
// Usage
var a = SArray([1,2,3,4]);
a.mutate(function(values) {
for (var i = 0; i < values.length; i++) {
if (i % 2 === 0)
values.splice(i, 1);
}
});