javascriptfrontendsveltesvelte-component

How does the parent component re-execute a function after a Svelte child component changes?


I have a svelte code.I want to implement a function that the parent component re-executes after the child component is updated. I am facing a problem now.After the child component is updated, the parent component function is re-triggered, but it will enter an infinite loop.

parent component

<script lang="ts">
  import { Button } from "$lib/components/ui/button";
  import { SquarePlus } from "lucide-svelte";
  import Datatable from "$lib/components/datatable/datatable.svelte";
  import { getUserListAPI } from "@/apis/hook";
  import type { User, UserFilter } from "@/interfaces/user";
  import { onMount } from "svelte";
  let data: User[] = [];
  let dataSize = 0;
  
  let tableFilterDetail:UserFilter = {
    pageIndex:1,
    pageLimit:10
  }
  const getData = async (req:UserFilter) => {
    //AXIOS post get remote data
    const res = await getUserListAPI(req);
    data = res.data.rows;
    dataSize = res.data.count;
  };
  onMount(()=>{
    getData(tableFilterDetail)
  })
  $:{
    getData(tableFilterDetail)
  }
</script>

<div>
  <div class="mb-2 w-full flex justify-between items-center">
    <h2 class="text-2xl font-bold tracking-tight">User</h2>
    <div class="flex items-center">
      <Button
        variant="success"
        class="ml-1 h-10 w-28 flex items-center justify-center rounded-lg "
      >
        <SquarePlus size="16" />New
      </Button>
    </div>
  </div>
  <Datatable {data} {columns} {dataSize} bind:tableFilterDetail/>
</div>

child component

<script lang="ts">
......

  //update state and update tableAttr
  $: {
    datatableAttr.update((state) => ({
      ...state,
      hiddenColumn: hideForId,
    }));
    datatableAttr.update((state) => ({
      ...state,
      dataSize: dataSize,
    }));
    datatableAttr.update((state) => ({
      ...state,
      pageSize: pageSize,
    }));
    tableFilterDetail = {
      pageIndex: $datatableAttr.currentPage,
      pageLimit: $datatableAttr.pageSize.value,
    };
    tableData.set(data);
  }

</script>
......
    <div class="flex items-center space-x-2">
      <label for="itemsPerPage" class="text-gray-700">Page shows:</label>
      <Select.Root portal={null} bind:selected={pageSize}>
        <Select.Trigger class="w-[130px]">
          <Select.Value />
        </Select.Trigger>
        <Select.Content>
          <Select.Group>
            {#each tableSize as size}
              <Select.Item value={size.value} label={size.label}
                >{size.label}</Select.Item
              >
            {/each}
          </Select.Group>
        </Select.Content>
        <Select.Input name="" />
      </Select.Root>
    </div>
    <div class="flex items-center space-x-2">
      <Pagination.Root
        count={$datatableAttr.dataSize}
        perPage={$datatableAttr.pageSize.value}
        let:pages
        let:currentPage
        bind:page={$datatableAttr.currentPage}
      >
        <Pagination.Content>
          <Pagination.Item>
            <Pagination.PrevButton />
          </Pagination.Item>
          {#each pages as page (page.key)}
            {#if page.type === "ellipsis"}
              <Pagination.Item>
                <Pagination.Ellipsis />
              </Pagination.Item>
            {:else}
              <Pagination.Item>
                <Pagination.Link {page} isActive={currentPage == page.value}>
                  {page.value}
                </Pagination.Link>
              </Pagination.Item>
            {/if}
          {/each}
          <Pagination.Item>
            <Pagination.NextButton />
          </Pagination.Item>
        </Pagination.Content>
      </Pagination.Root>
    </div>
......

It can be realized that the API is requested when the component is first loaded, and the API is requested again and the table data is updated when the table data changes.It won't enter an infinite loop.


Solution

  • That large reactive $: {} statement in your child will re-run anytime anything within the curly braces changes, the data, the store, the filter, the state etc. So as soon as it hits it will be in a loop cause the filter is there

    I would suggest removing any references to tableFilterDetail in the reactive statement because right now when data changes, it updates that filter, and when that filter changes, it updates data...

    // update store and tableData when ONLY the data variable changes
    $: data && updateStore()
    const updateStore = () => {
        datatableAttr.update((state) => ({
            ...state,
            hiddenColumn: hideForId,
            dataSize: dataSize,
            pageSize: pageSize,
        }));
        tableData.set(data);
    }
    //  find some other way to update the tableFilterDetail when the filter changes
    

    For updating the filter after removing it

    Is there a reason the perPage={$datatableAttr.pageSize.value} and (currentPage) in the Pagination has to be from the dataTableAttr store? Could it not just be bound to the incomming tableFilterDetail.pageLimit instead?

    That would just push the filter back and forth without having to use the store if so.

    bind:perPage={tableFilterDetail.pageLimit}
    bind:currentPage={tableFilterDetail.pageIndex}