I have an array of nodes, which in my case are objects that have some data, including an id
. I only want to display the active node, which is found by comparing the node ID with the active node ID. I can do that successfully using this:
let active_node_id: string;
active_node_stream.subscribe((value) => (active_node_id = value));
...
{#each nodes as node (node.id)}
{#if node.id === active_node_id}
<Node {...node} />
{/if}
{/each}
However, this pattern of looping through all the nodes only to display 1 node seems wrong. Is there a better way of doing this?
I thought to try to use Svelte's reactive declarations, so as to stop looping through the nodes once the active one was found, like so:
$: active_node_data = nodes.find(n => n.id === active_node_id)
...
<Node {...active_node_data} />
But this gives me the following error (I'm using TypeScript -- it seems to work fine with JavaScript when I try to reproduce it):
Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'length')
at $$self.$$.update
I'm not sure why this error occurs as the active_node_id
should always match a node's ID... I have logged the node values and the active_node_id
when this error happens and they do indeed match...
Regardless, this approach doesn't feel significantly better than the each
with if
approach. Is there a better way that's still Svelte-y?
I chatted with my acquaintance yuanchuan about this. He recommended using a Map
paired with an if
for instances like this, which feels a lot nicer.
First you need to build a map (I did it where my nodes data was):
export const node_map = build_data_map(nodes);
function build_data_map(nodes: node[]) {
const map = new Map<string, node>();
for (const node of nodes) {
map.set(node.id, node);
}
return map;
}
Then you can import that map and use it to find the currently active node:
let active_node_id: string;
active_node_stream.subscribe((value) => (active_node_id = value));
$: active_node_data = node_map.get(active_node_id);
...
{#if active_node_data}
<Node {...active_node_data} />
{/if}
So using Svelte's reactivity is the way to go, but there are nicer ways to do it like using a map.
However, the approach provided above prevents transitions when the active node is changed from occurring (in my case I am hiding non-active ones). This is because the above approach uses a single instance of a node, only switching out the data, as opposed to truly transitioning an element in or out. As a result, I stuck with the original approach I had, using the each
with if
.
Side note: It turns out the Cannot read properties of undefined (reading 'length')
error was coming from inside of my Node
component when a property was not being passed in for certain nodes. It was caused by these lines:
export let options: answer_obj[] = [];
$: is_choice = options.length !== 0;
I expected options to always have a length (at least be 0) and not be undefined
sometimes. $: is_choice = options?.length !== 0;
also threw the error, which is surprising. But $: is_choice = options ? options.length !== 0 : false;
worked without error. I don't understand why the optional chaining method didn't work (maybe it's an issue with the compiler?) nor do I why this was not an issue with the each
approach...