Reputation: 1642
Novice Question -- Using SolidJS, how do I make DOM elements reactive to signal updates?
I have two instances which I cannot get to update as expected.
Origin Component
import { Component } from 'solid-js'
import { isEqual as _isEqual } from 'lodash-es'
const [getItems, setItems] = createSignal<Array<Item>>([])
const OriginComponent: Component = () => {
const updateItems = (item: Item) => {
const init = getItems()
const index = init.findIndex(i => _isEqual(i, item))
index == -1 ? init.push(item) : init.splice(index, 1)
setItems(init)
}
return (
<>
<span>Item Count: {getItems().length}</span> // << -- WILL NOT CHANGE.
<button onclick={() => updateItems(x: Item)}>Click Me</button>
</>
)
}
export { OriginComponent, getItems }
Remote Component
import { Component } from 'solid-js'
import { getItems } from '../OriginComponent'
const RemoteComponent: Component = () => {
return (
<p class='results'>
{getItems() ? 'Has Items' : 'Has No Items'} // << -- WILL NOT CHANGE.
</p>
)
}
Upvotes: 1
Views: 2670
Reputation: 13698
Components are not DOM elements, they are compiled to DOM elements during compilation but they have extra stuff for reactivity.
You are trying to mutate the array but the way signal works is you need to set a new array in order to trigger a state update.
const [items, setItems] = createSignal([]);
setInterval(() => {
setItems([ ...items(), items().length ]);
}, 1000);
So, setting a new array and following JSX rules will be enough to solve your problem.
Now to make the question future proof lets see how to update an actual DOM element that lives outside the Solid's system.
First, I will create a DOM element and append it to the body but the mechanism will work the same for an already existing DOM element.
import { createSignal, createEffect } from "solid-js";
// Get the DOM element
const el = document.createElement('div');
document.querySelector('body').appendChild(el);
// Signal that will be the content of our DOM element
const [count, setCount] = createSignal(0);
// I will use setInterval to update the signal for simplicity
setInterval(() => {
setCount(c => c + 1);
}, 1000);
// I will create an effect to update the element's innerText or innerHTML
// whenever the signal updates.
createEffect(() => {
el.innerHTML = count();
});
There is a slight problem with our solution here, the effect we created will not be owned by any tracking scope so it may not be disposed when its scope gets disposed.
It is best to run this code inside render
function which creates a root automatically or create one yourself using createRoot
and dispose it when you don't need it.
Upvotes: 1
Reputation: 1061
According to Solid's API docs for createSignal
(in the options section):
By default, when calling a signal's setter, the signal only updates (and causes dependents to rerun) if the new value is actually different than the old value, according to JavaScript's === operator.
This is the case here, as the array you are setting in the signal is the same (===
) as the old one. One workaround is to set turn off referential equality checks for the signal, like so: createSignal<Array<Item>>([], { equals: false })
.
Another option is to create a new array when setting the array again, like so: setItems([...init])
Upvotes: 4