1

I have a small Svelte app and love the framework, but I am running into a issue with local variables. There is a local writable variable declared in a child component and is only used in this component. It is updated when a on:click event is fired and updates as it should. However, upon another on:click (different function) event it resets this local variables value to the default value (as it should) and uses dispatch to update the passed props in the parent.

Now the issue is that once the updated props are passed back to the child, the local writable variable reverts to the previous value. What am I not seeing here? Thanks!

Parent:

<script lang="ts">
  // Node Navigator Component
  // This component contains all the sub components for the navigator

  import type { IHistoryNode, ILinkedListNode } from "../../../types/linkedListNode";
  import type { ILinkedList } from "../../../types/linkedList";
  import type { IMockData } from "../../../types/mockData";
  import { LinkedListNode } from "../../../utils/linkedListNode";
  import NodeList from '../../molecules/nodeList/index.svelte';

  // props
  export let nodes: IMockData[];

  // local vars
  let currentNodes: IMockData[] = nodes;

  // set the current node if id is passed and its connections
  // else reset to original state
  function setNodes(id?: string) {
    if (id) {
      currentNode = nodes.find(node => node.id === id);
      currentNodes = [...nodes.filter(node => currentNode.connections.includes(node.id))];
    } else {
        currentNode = undefined;
        currentNodes = nodes;
      }
    }
  ** this is the function that is called from the child to update the props **
  // add the selected node is to the history list, set the state for the current node
  function handleCurrentId(event: any) {
    const {id, name} = event.detail;
    const newNode: ILinkedListNode<IHistoryNode> = new LinkedListNode({id, name});
    historyList.addNode(newNode);
    historyList = historyList;
    setNodes(id);
  }

</script>

<section class={'nodeNavigatorWrapper'}>
** this is the child componet in question **
  <NodeList
    bind:nodes={currentNodes}
    on:newId={handleCurrentId}
  />
</section>

Child:

<script lang="ts">
  // Node List Component
  // This component will render a list of the node passed from props
  
  import { createEventDispatcher } from "svelte";
  import { shareAlt, plus, minus } from 'svelte-awesome/icons';
  import type { IMockData } from "../../../types/mockData";
  import type { IHistoryNode } from '../../../types/linkedListNode';
  import { Writable, writable } from 'svelte/store';

  // props
  export let nodes: IMockData[];

  // local vars
  const dispatch = createEventDispatcher();
  let exapndedNodeIdx: Writable<number> = writable(-1);

  // reset open row and disatch action to add selected to to navigator
  function onOpenConnection(value: IHistoryNode) {
    exapndedNodeIdx.set(-1);
    dispatch('newId', { id: value.id, name: value.name })
  };

  // if the row that is click is open, close it.  Else open it
  function handleExpand(idx: number) {
    exapndedNodeIdx.update((currIdx: number) => {
      if (currIdx === idx) currIdx = -1;
      else { currIdx = idx; }
      return currIdx;
    })
  }

</script>

{#if nodes.length}
{console.log($exapndedNodeIdx)}
  <section>
    <table class="nodeTableWrapper">
      {#each nodes as node, idx}
        <tr
          class="nodeTableRow"
          on:click={() => handleExpand(idx)}
          title={$exapndedNodeIdx === idx ? `Close ${node.name}` : `Expand ${node.name}`}
        >
          <th>
            {#if $exapndedNodeIdx === idx}
              <Icon data={minus} style={'margin-right: 0.5em'} />
            {:else}
              <Icon data={plus} style={'margin-right: 0.5em'} />
            {/if}
            {node.name}
          </th>
          {#if $exapndedNodeIdx === idx}
            <td transition:slide="{{delay: 100, duration: 350, easing: quintOut }}">
              <p>{node.summary}</p>
              <span>Number of connected nodes: {node.connections.length}</span>
              <button
                type={'button'}
                on:click={() => onOpenConnection({id: node.id, name: node.name})}
                class="openNodeBtn"
                aria-label={`Open ${node.name}`}
                title={`Open ${node.name}`}
              >
                <Icon data={shareAlt} scale={1.2}/>
              </button>
            </td>
          {/if}
        </tr>
      {/each}
    </table>
  </section>
{/if}

H.B.
  • 166,899
  • 29
  • 327
  • 400
Ryan Carville
  • 355
  • 7
  • 15
  • Code is too incomplete. Don't show what works, show what does not. Including the props, any other access to the store and how the props are modified by the parent. – H.B. Oct 10 '22 at 10:57
  • I suspect the component is being recreated, thus resetting its local state. – H.B. Oct 10 '22 at 10:58
  • @H.B. apologizes. Updated the spinets. – Ryan Carville Oct 10 '22 at 11:06
  • If a lot of code is involved like here, you should first reduce everything to a minimal reproducible example. That way it becomes clear which parts are actually relevant and you might even find the problem and its solution yourself. – H.B. Oct 10 '22 at 11:09
  • @H.B. understood. Hope this edit helps – Ryan Carville Oct 10 '22 at 11:15
  • It's better but still far from minimal. The ideal example has no additional dependencies (like components that are not shown) and can be run in the [REPL](https://svelte.dev/repl). For something this involved it probably would be easiest to build the example from scratch, rather than trying to reduce it manually. – H.B. Oct 10 '22 at 11:18
  • I understand. But maybe this will help with insight for now. I debugged it so that when I add a reactive deceleration (e.g `$: {exapndedNodeIdx.set(-1)}`) to reset the writable value when props changes it updates corrects. Just not pretty as the new list is rendered, then the list will style which depends on this writable is corrected. – Ryan Carville Oct 10 '22 at 11:38

1 Answers1

0

The store state should not be affected, but the component UI state might be inconsistent. Since you are switching out the entire node list, you probably need to use a keyed #each:

{#each nodes as node, idx (node.id)}

See docs.

H.B.
  • 166,899
  • 29
  • 327
  • 400
  • Thank you @H.B. ! This adjustment does fix the issue. It seems like it is a cleaner way to do this instead of a reactive declaration as well. Thank you very much! I will comb the docs again as well. – Ryan Carville Oct 10 '22 at 12:20