S
SolidJS9mo ago
Wes

How to create a nested menu from a nested object?

I'm building a system where I have data from an external source that is structured as an object with nested levels, such as:
const menu = {
id: 1,
label: "One",
items: [
{
id: 2,
label: "Two",
items: [
{
id: 3,
label: "Three",
},
],
},
],
};
const menu = {
id: 1,
label: "One",
items: [
{
id: 2,
label: "Two",
items: [
{
id: 3,
label: "Three",
},
],
},
],
};
It can have an arbitrary number of options and submenus. How do I make a <Menu /> component that reacts to this object and renders recursively? My current solution is passing the properties after reading them, which is not reactive and therefore never updates.
8 Replies
thetarnav
thetarnav9mo ago
can label change? make it a signal can items change? make it a signal then you just read and write to those signals
Wes
WesOP9mo ago
But everything can change, including the arrays
thetarnav
thetarnav9mo ago
each node has its signals items array can be a single signal, you just copy it in each write basically you treat each node as a class that you want to maintain the instance of, and update its signal properties if you are getting the data as a immutable structure from somewhere, then you need to diff and reconcile your own “reactive” structure stores can abstract most of it but you can also do it yourself
Wes
WesOP9mo ago
I see, so either I or the store is responsible for say understanding that an array size changed and call that signal setter, but if one of that array's object changed, the object's property is also a signal and should be updated instead
thetarnav
thetarnav9mo ago
ah and another option is to use Key from solid-primitives and keep your data an an immutable structure inside one signal then you “diff in the template” instead of “diffing in the data” up to you
Wes
WesOP9mo ago
The Rerun looks rather tempting
thetarnav
thetarnav9mo ago
rerun is basically this <>{signal(), <Component/>}</> but not sure how that helps here it's basically this
type MenuNode {
id: number
label: solid.Accessor<string>
setLabel: solid.Setter <string>
items: solid.Accessor<MenuNode[]>
setItems: solid.Setter <MenuNode[]>
}

function makeNode(id: number): MenuNode {
const [label, setLabel] = solid.createSignal("")
const [items, setItems] = solid.createSignal<MenuNode[]>([])
return {
id,
label,
setLabel,
items,
setItems,
}
}

function addItems(node: MenuNode, ids: number[]): void {
node.setItems(p => [...p, ...ids.map(makeNode)])
}
type MenuNode {
id: number
label: solid.Accessor<string>
setLabel: solid.Setter <string>
items: solid.Accessor<MenuNode[]>
setItems: solid.Setter <MenuNode[]>
}

function makeNode(id: number): MenuNode {
const [label, setLabel] = solid.createSignal("")
const [items, setItems] = solid.createSignal<MenuNode[]>([])
return {
id,
label,
setLabel,
items,
setItems,
}
}

function addItems(node: MenuNode, ids: number[]): void {
node.setItems(p => [...p, ...ids.map(makeNode)])
}
then if you iterate the items with For it will stay reactive, and keep the previous items unchanged.
<For each={root.items()}>
{node => <DisplayMenuNode node={node}/>}
</For>
<For each={root.items()}>
{node => <DisplayMenuNode node={node}/>}
</For>
Wes
WesOP9mo ago
I ended up cheating and wrapping the menu on a Show with keyed, where the menu data is memoized. For sure that's not the best performance possible, but I'm ok with extra rendering for this given the data dont change often.
Want results from more Discord servers?
Add your server