Adapting a list of objects to signals

I've got an API that returns a list of objects like
[
{ name: "foo", value: 200, message: "whatever" },
{ name: "foo", value: 44, message: "" },
{ name: "baz", value: 0, message: "blah" }
]
[
{ name: "foo", value: 200, message: "whatever" },
{ name: "foo", value: 44, message: "" },
{ name: "baz", value: 0, message: "blah" }
]
I fetch this data using useResource and I render it out with a tree of components like
function List(props: { results: Result[] }) {
return <For each={props.results}>{(result) => <Result result={result}/>}</For>
}

function Result(props: { result: Result }) {
return <ul>
<li>{props.result.name}</li>
<li><input type="number" value={props.result.value}/></li>
<li><input type="text" value={props.result.message}/></li>
</ul>
}
function List(props: { results: Result[] }) {
return <For each={props.results}>{(result) => <Result result={result}/>}</For>
}

function Result(props: { result: Result }) {
return <ul>
<li>{props.result.name}</li>
<li><input type="number" value={props.result.value}/></li>
<li><input type="text" value={props.result.message}/></li>
</ul>
}
The problem comes when I invalidate the resource and re-fetch it, all of the Result components get re-rendered from scratch, because the objects are different. And even if the objects were the "same" then they wouldn't update properly because props.result.value isn't a signal. Does solid provide any tools for mapping data like this into signals so that my components can be reactive to it?
5 Replies
webstrand
webstrandOP8mo ago
mapArray isn't the right solution, since there's no way to provide it with a surrogate key I've written my own remap<U, V>(input: Iterable<U>, target: V[], { key: (i: U) => unknown, create: (i: U) => V, update: (i: U, e: V) => V, destroy: (e: V) => void }) but it's annoyingly complex and doesn't currently handle reordering well
REEEEE
REEEEE8mo ago
You can take a look at the storage option for resources and make use of the createDeepSignal example provided in the docs
peerreynders
peerreynders8mo ago
I think you are looking for the storage option on createResource which usually is implemented using reconcile.
function createDeepSignal<T>(value: T): Signal<T> {
const [store, setStore] = createStore({
value,
})
return [
() => store.value,
(v: T) => {
const unwrapped = unwrap(store.value)
typeof v === "function" && (v = v(unwrapped))
setStore("value", reconcile(v))
return store.value
},
] as Signal<T>
}

const [resource] = createResource(fetcher, {
storage: createDeepSignal,
})
function createDeepSignal<T>(value: T): Signal<T> {
const [store, setStore] = createStore({
value,
})
return [
() => store.value,
(v: T) => {
const unwrapped = unwrap(store.value)
typeof v === "function" && (v = v(unwrapped))
setStore("value", reconcile(v))
return store.value
},
] as Signal<T>
}

const [resource] = createResource(fetcher, {
storage: createDeepSignal,
})
Maciek50322
Maciek503228mo ago
The simplest way to make it not rerender from scratch is to use <Index> instead of <For>, but if you need them keyed by something other than index there are other community primitives / components that you can specify your own key, like <Key> https://github.com/solidjs-community/solid-primitives/tree/main/packages/keyed#readme
GitHub
solid-primitives/packages/keyed at main · solidjs-community/solid-p...
A library of high-quality primitives that extend SolidJS reactivity. - solidjs-community/solid-primitives
Maciek50322
Maciek503228mo ago
Also if you want it keyed by reference, like <For>, but also don't want it to reattach everything when everything changes, like <Index> avoids that, there also <List> (I did that actually), it's something in between these two, but it's pretty new https://github.com/solidjs-community/solid-primitives/tree/main/packages/list#readme
GitHub
solid-primitives/packages/list at main · solidjs-community/solid-pr...
A library of high-quality primitives that extend SolidJS reactivity. - solidjs-community/solid-primitives
Want results from more Discord servers?
Add your server