删除 Sveltekit 中动态加载的组件时出现的问题

问题描述 投票:0回答:1

我有一个页面(此时)仅包含一个按钮。单击此按钮时,会动态加载一个组件,因此如果多次单击该按钮,页面上会出现多个组件。每个组件都是一个小表单,单击表单的保存按钮即可提交。

到目前为止一切顺利,现在,当保存组件时,

save
按钮将被隐藏,并且垃圾按钮将可见。当点击这个
garbage
按钮时,该组件应该被删除。

为此,我需要跟踪创建的组件,因此我随机创建一个字符串,将其作为属性的一部分传递。作为属性的一部分,我还传递了一个回调函数,该函数接收组件 ID,并将在单击

garbage
按钮时执行。

单击

garbage
按钮后,它将调用回调函数,该函数将通过运行过滤器从组件列表中删除该组件。

假设我有 2 个元素。在每个元素上,我显示了创建它所用的随机 ID。

如果我删除第一个元素,我希望只保留第二个元素。这是以某种方式发生的,我在屏幕上看到的第二个元素具有正确的 ID,但由于某种原因,它的数据(文本字段中的数据)不正确,但它是刚刚删除的元素的数据。

知道发生了什么事吗?

这是包含添加元素按钮的主页:

<script>

    import InventoryItem from "../components/InventoryItem.svelte";

    export let formData = {};
    export let inventoryID;

    let inventoryItemComponents = [];

    // Keeps track of the number of components added to inventoryItemComponents and the same

    /**
     * loadNewItemForm loads a new InventoryItem component and adds it to a
     * list of existing components inventoryItemComponents
     */
    const loadNewItemForm = () => {

        // create random string that will be used as a component identifier.
        const componentID = Math.random().toString(36).slice(2);

        inventoryItemComponents = [
            ...inventoryItemComponents,
            [InventoryItem, {
                formAction: "?/saveItem",
                inventoryID: inventoryID,
                componentID: componentID,
                deleteCallbackFn: removeFromList
            }]
        ];

    }

    const removeFromList = id => {

        let updatedList = inventoryItemComponents.filter( inventoryComponent => inventoryComponent[1].componentID != id )
        inventoryItemComponents = [...updatedList];

    }

</script>

<div>

    <div class="inventory-items-list">

        {#each inventoryItemComponents as [component, props]}
            <svelte:component this={component} {...props}>
            </svelte:component>
        {/each}

        <!-- New item button -->
        <div class="float-right">
            <div class="md:p-4  flex item-center justify-center">
                <button
                        class="btn btn-active btn-primary"
                        on:click={loadNewItemForm}>
                    New Item
                </button>
            </div>
        </div>
    </div>
</div>

这是正在加载的组件:

<script>
    import {enhance} from '$app/forms';
    import ProductLookup from "./ProductLookup.svelte";

    export let inventoryID;
    export let componentID;
    export let deleteCallbackFn;

    /**
     * Local props.
     */
    let inventoryItemID = "";
    let total;
    let productData;
    let purchasedPrice = 0;
    let quantity = 1;

    // Determines whether the save button should be displayed or not.
    let hideSaveBtn = false;

    // Holds a list of errors.
    let errors = {};

    /**
     * Builds the request's payload to the add-inventory-item endpoint.
     * We won't perform any validation at this point.
     */
    const buildPayload = () => {

        return {
            "inventory_id": inventoryID,
            "product": productData,
            "purchased_price": purchasedPrice.toString(),
            "quantity": quantity,
        }
    }

    /**
     * Saves a new inventory item.
     *
     * @param e
     * @returns {Promise<void>}
     */
    const save = async e => {

        e.preventDefault();

        // reset all errors, so they can be repopulated again from scratch.
        errors = {};

        // perform call to internal API.
        const response = await fetch('/api/add-inventory-item', {
            method: 'POST',
            body: JSON.stringify(buildPayload()),
        });

        const parsedResponse = await response.json()

        if (parsedResponse.errors) {
            errors = parsedResponse.errors;
            return;
        }

        inventoryItemID = getInventoryItemIDFromResponse(parsedResponse);

        // if the record was successfully saved then hide the save button since it shouldn't be allowed to be saved again.
        hideSaveBtn = true;
    }

    /**
     * Goes through the API response from adding an item into an inventory and
     * gets the inventory item ID. This has to be done because from the API we're returning
     * the whole inventory object when adding a new item, so we need to figure the ID out.
     *
     * @param apiResponse
     */
    const getInventoryItemIDFromResponse = apiResponse => {

        const inventoryItems = apiResponse.data.attributes.items;
        const item = inventoryItems.filter(item => item.product.id === productData.value);
        return item[0].id;
    }

    const deleteItem = () => {
        deleteCallbackFn(componentID);
    }

    /*
     * Reactive properties.
     */

    // Recalculate total every time either purchasedPrice or quantity change.
    $: total = purchasedPrice * quantity;

</script>

<div class="card w-full bg-base-100 shadow-xl mt-5">

    <form method="POST" class="p-6" use:enhance>
        <h2>ID: {componentID}</h2>
        <div class="md:grid md:grid-cols-12 gap-0.5">

            <!-- Inventory Item ID -->
            <input name="inventory_item_id" type="hidden" value={inventoryItemID} />

            <!-- ID -->
            <input name="inventory_id" type="hidden" value={inventoryID} />

            <!-- Product lookup -->
            <div class="col-span-4">
                <div class="md:w-full md:px-3 mb-6 md:mb-0">

                    <label class="block tracking-wide text-gray-700 text-xs font-bold mb-1" for="product">
                        Product <span class="text-red-600">*</span>
                    </label>

                    <ProductLookup
                            bind:value={productData}
                            customClass="input w-full py-4 font-medium bg-gray-100 border-gray-200 text-sm
                      focus:outline-none focus:border-gray-400 focus:bg-white"
                            id="product"
                            name="product"
                    />
                    {#if errors?.product_id }
                        <small class="text-amber-800">{ errors?.product_id[0]}</small>
                    {/if}
                </div>
            </div>

            <!-- Quantity -->
            <div class="col-span-2">
                <div class="md:w-full md:px-3 mb-6 md:mb-0">
                    <label class="block tracking-wide text-gray-700 text-xs font-bold mb-1" for="quantity">
                        Qty <span class="text-red-600">*</span>
                    </label>
                    <input
                            bind:value={ quantity }
                            class="input w-full md:max-w-xs py-4 font-medium bg-gray-100 border-gray-200 text-sm
                                  focus:outline-none focus:border-gray-400 focus:bg-white"
                            id="quantity"
                            min="1"
                            name="quantity"
                            type="number"
                    />
                    {#if errors?.quantity }
                        <small class="text-amber-800">{ errors?.quantity[0]}</small>
                    {/if}
                </div>
            </div>

            <!-- Purchased Price -->
            <div class="col-span-2">
                <div class="md:w-full md:px-3 mb-6 md:mb-0">
                    <label class="block tracking-wide text-gray-700 text-xs font-bold mb-1" for="purchased_price">
                        Purchased Price <span class="text-red-600">*</span>
                    </label>
                    <input
                            bind:value={ purchasedPrice }
                            class="input w-full md:max-w-xs py-4 font-medium bg-gray-100 border-gray-200 text-sm
                                  focus:outline-none focus:border-gray-400 focus:bg-white"
                            id="purchased_price"
                            min="0"
                            name="purchased_price"
                            type="number"
                    />
                    {#if errors?.purchased_price }
                        <small class="text-amber-800">{ errors?.purchased_price[0]}</small>
                    {/if}
                </div>
            </div>

            <!-- Total -->
            <div class="col-span-2">
                <div class="md:w-full md:px-3 mb-6 md:mb-0">
                    <label class="block tracking-wide text-gray-700 text-xs font-bold mb-1" for="total">
                        Total
                    </label>
                    <input class="input input-ghost w-full max-w-xs" disabled id="total" type="text" value={total}/>
                </div>
            </div>

            <!-- Buttons -->
            <div class="col-span-2">

                <!-- Save button -->
                {#if (!hideSaveBtn)}
                    <div class="">
                        <a href="#" class="flex items-center justify-center link link-hover" on:click={save}>
                            <svg class="w-6 h-6 text-gray-800" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 20">
                                <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 19H1.933A.97.97 0 0 1 1 18V5.828a2 2 0 0 1 .586-1.414l2.828-2.828A2 2 0 0 1 5.828 1h8.239A.97.97 0 0 1 15 2v4M6 1v4a1 1 0 0 1-1 1H1m11 8h4m-2 2v-4m5 2a5 5 0 1 1-10 0 5 5 0 0 1 10 0Z"/>
                            </svg>
                        </a>
                    </div>
                {:else }
                    <div class="">
                        <svg class="w-6 h-6 text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 20">
                            <path stroke="green" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m7 10 2 2 4-4m6 2a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"/>
                        </svg>
                        <a href="#" class="flex items-center justify-center link link-hover" on:click={deleteItem}>
                            <svg class="w-6 h-6 text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 18 20">
                                <path stroke="darkred" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M1 5h16M7 8v8m4-8v8M7 1h4a1 1 0 0 1 1 1v3H6V2a1 1 0 0 1 1-1ZM3 5h12v13a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V5Z"/>
                            </svg>
                        </a>
                    </div>
                {/if}
            </div>

        </div>

        <div class="flex-row flex">
            <div class="p-2 float-left">
                <span class="px-3 mb-6 text-2xs">(<span class="text-red-600">*</span>) Required</span>
            </div>
        </div>

    </form>


</div>

javascript html svelte sveltekit svelte-component
1个回答
0
投票

Corrl 是正确的,

{#each}
需要一把钥匙,例如

{#each inventoryItemComponents as [component, props] (props.componentID)}

除非使用密钥,否则 Svelte 不知道哪些数据属于哪个组件。删除时,last组件实例将被删除,并且每个剩余组件的 props 都会被更新。

这意味着最后一个组件的 props 是在第一个实例上设置的,因此依赖于 props 的任何反应性更新都会正确更新,但本地状态不会。

© www.soinside.com 2019 - 2024. All rights reserved.