javascriptstatesveltewritable

Local variable updates then when props is passed it reverts to old value


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}


Solution

  • 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.