S
SolidJS•3mo ago
Andreas Roth

Cycles in dependency graph

Hello fellow Solid devs, we need your help! To make things short: Is there any way to safely recover from a cycle in solid's reactivity dependency graph? I know that the simple answer is: Don't do cycles! But it's not that easy, let me explain: We have been building an Excel-Like application and use Solid's reactivity for dependencies between the cells. So users are in full control what cells they reference within they formulas. One option that we see is to manually inspect the cell references to look for cycles, but something that could again leverage Solid's built-in helpers would maybe be a simpler approach.
9 Replies
deluksic
deluksic•3mo ago
The issue here is not their code, but user-written "code" in the cells I dont think you can get away with running the cell in order to figure out if there was a cycle, you'll need some kind of mechanism that does this without actually running the cell, unfortunately
Andreas Roth
Andreas RothOP•3mo ago
That's what I thought... Thanks for chiming in anyways 🙂
Adam Goldstein
Adam Goldstein•2mo ago
@Andreas Roth You may be long past this now, but this seems doable (at least at first glance) by taking advantage of batched updates, either with the batch utility or relying on automatic batching. As long as all the updates are running inside a single batch, I think something like the following might work:
const wrapSignalMaybeWithCycles = ([accessor, setter]) => {
let insideAccessorCall = false;
return ([
(...args) => {
if (insideAccessorCall) {
throw new Error("Cycle!");
}
insideAccessorCall = true;
const val = accessor(...args);
insideAccessorCall = false;
return val;
},
setter,
})

// Can use like any normal signal, I think, hopefully?
const [getIt, setIt] = wrapSignalMaybeWithCycles(createSignal());
const wrapSignalMaybeWithCycles = ([accessor, setter]) => {
let insideAccessorCall = false;
return ([
(...args) => {
if (insideAccessorCall) {
throw new Error("Cycle!");
}
insideAccessorCall = true;
const val = accessor(...args);
insideAccessorCall = false;
return val;
},
setter,
})

// Can use like any normal signal, I think, hopefully?
const [getIt, setIt] = wrapSignalMaybeWithCycles(createSignal());
Just a thought, not sure 🤷 And of course you could handle cycle errors however you want, return some null/undefined/NaN/empty object value, whatever Would be curious to see the Excel-like application as well! Seems like potentially a great use case for Solid.js 🙂
Andreas Roth
Andreas RothOP•2mo ago
Aah that is a really cool idea. I think we are using stores so we would probably need to wrap the proxy/getters in some way but the idea could still work out! Thanks for the input!
Adam Goldstein
Adam Goldstein•2mo ago
Happy to help! I think it should work basically the exact same but just:
const wrapStoreMaybeWithCycles = (storeProxy) => {
let insideAccessorCall = false;
return new Proxy(storeProxy, {
get(...args) {
if (insideAccessorCall) {
throw new Error("Cycle!");
}
insideAccessorCall = true;
const val = Reflect.get(...args);
insideAccessorCall = false;
return val;
},
});
}
const wrapStoreMaybeWithCycles = (storeProxy) => {
let insideAccessorCall = false;
return new Proxy(storeProxy, {
get(...args) {
if (insideAccessorCall) {
throw new Error("Cycle!");
}
insideAccessorCall = true;
const val = Reflect.get(...args);
insideAccessorCall = false;
return val;
},
});
}
One of the joys of having a library where the abstractions aren't crazy deep 🙂 If that does end up working, please let me know! Would be great to note
Andreas Roth
Andreas RothOP•2mo ago
Wait no... The signal is the wrong place. The signal always already has a value, it can directly return it. Computeds can create cycles, so we need to wrap the computed, not the signal
Andreas Roth
Andreas RothOP•2mo ago
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
Adam Goldstein
Adam Goldstein•2mo ago
Oh of course, duh 🤦. For some reason I was thinking of the signals calling themselves, of course it would be the computeds. The memo-like approach makes a lot of sense though, glad to see that seems to be working!
Andreas Roth
Andreas RothOP•2mo ago
We noticed that we are not even using createMemo, but createComputed that write into a shared store. But the same concept applies for computeds as well 🙂
No description
Want results from more Discord servers?
Add your server