Solid JS force rerender on signal change

I need to force Solid JS to make changes to the DOM every time a signal changes. Currently it seems that it is batched, probably for performance reasons. I can get around this by wrapping my second signal change in a setTimeout, but I want to make sure I am not implementing a hacky solution. For example, could I reliably get away with using a requestAnimationFrame or is there some Solid JS provided function that I missed? Here is a code snippet that demonstrates the problem and the timeout fix. If you remove the setTimeout, you will see no change.
import { render } from "solid-js/web";
import { createSignal } from "solid-js";

function Component() {

const [state, setState] = createSignal(true);

const x = () => {
setState(false);
setTimeout(() => {
setState(true);
}, 0);
};

return <button
onClick={() => x()}
style={{
transition: state() ? 'background-color 5s' : 'background-color 0s',
'background-color': state() ? 'blue' : 'red',
}}
>Click</button>;
}

render(() => <Component />, document.getElementById("app")!);
import { render } from "solid-js/web";
import { createSignal } from "solid-js";

function Component() {

const [state, setState] = createSignal(true);

const x = () => {
setState(false);
setTimeout(() => {
setState(true);
}, 0);
};

return <button
onClick={() => x()}
style={{
transition: state() ? 'background-color 5s' : 'background-color 0s',
'background-color': state() ? 'blue' : 'red',
}}
>Click</button>;
}

render(() => <Component />, document.getElementById("app")!);
3 Replies
foolswisdom
foolswisdom2y ago
This is not exactly a solidjs problem, I believe that the browser is actually batching in this case. You can see that solid is not batching with this code (https://playground.solidjs.com/anonymous/2a5cf4dc-55ca-4385-8744-8dcd6d41ebc1):
const [state, setState] = createSignal(true);

createEffect(() => {
console.log(state())
})

const x = () => {
setState(false); // logs `false`
// setTimeout(() => {
setState(true); // logs `true`
// }, 0);
};
const [state, setState] = createSignal(true);

createEffect(() => {
console.log(state())
})

const x = () => {
setState(false); // logs `false`
// setTimeout(() => {
setState(true); // logs `true`
// }, 0);
};
(That said, solid does batch updates if you set values from inside an effect or you explicitly use the batch primitive. But not in a case like this, where you are simply setting the value in a callback). So, what I think is happening here, is that since the code is immediately changing the transition value back, the change was never "committed" by the browser, even though you did change it at some point. I guess the same thing would happen if you would write this with vanilla JS
element.style.transition = "background-color 0s"
element.style.transition = "background-color 5s"
element.style.transition = "background-color 0s"
element.style.transition = "background-color 5s"
I don't know how to solve your problem though, maybe someone else has an idea. I just wanted to point out that this doesn't appear to be due to solid batching updates
thisbeyond
thisbeyond2y ago
Typically you'd need to force the browser to re-evaluate styles (e.g. by requesting getComputedStyle or something), but note that could negatively affect performance. Raf can work too. Alternatively, get a ref to the element and kick off an animation explicitly.
FutureLights
FutureLights2y ago
That makes sense. setTimeout has been working consistently for me and I don't see it having a big impact performance.