That is because the effect can not re-subscribe to the signal once it is run. Here is why:
Solid runs synchronously. Every signal keeps its own subscribers list. Effects are added to the subscribers list when they read the signal and removed when they are called back. So, subscribers list is renewed in each update cycle and it happens synchronously.
However setTimeout's callback is run asynchronously in the event loop.
When the callback runs, it will update the signal's value and the effect wrapping the setTimeout
function will be added to the subscribers list. However this subscribers list gets discarded when the signal completes its execution cycle. So, the effect will never be called back. In other words, the effect will be subscribing to the subscribers list of the previous execution cycle.
The problem is not that the callback is unable to update the signal, (actually it does, that is why counter increments by one), but the effect is unable to re-subscribe to the signal's queue. So, we need to find a way to make the effect re-subscribe to the signal.
You have two options which produce different outputs:
- Reads the signal synchronously which makes the effect re-subscribe and set a new timer whenever signal updates:
import { render } from "solid-js/web";
import { createSignal, createEffect } from "solid-js";
function Counter() {
const [count, setCount] = createSignal(0);
createEffect(() => {
const c = count();
setTimeout(() => {
setCount(c + 1);
}, 1000);
})
return (
<>
{count()}
</>
);
}
We read the signal's value in advance, long before the setTimeout's callback gets fired.
- Get the right owner and subscribe to its list:
function Counter() {
const [count, setCount] = createSignal(0);
const owner = getOwner();
setTimeout(() => {
runWithOwner(owner!, () => {
createEffect(() => {
console.log('Running Effect');
setCount(count() + 1);
});
});
}, 1000);
return (
<>
{count()}
</>
);
}
In this solution, we create the effect when the setTimeout's callback is fired and bind the effect to the current owner.
An important side note: Your code will cause an infinite loop because you are setting the signal inside the effect, which runs whenever signal updates.
createEffect(() => {
setCount(count() + 1);
});
You can read more about runWithOwner
function here: https://www.solidjs.com/docs/latest/api#runwithowner