Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

dmytroshved's avatar

How to avoid issues with step updates in upsertGroupedSteps caused by array reindexing when using dynamic fields? Livewire 3

I have a dynamic form fields logic in my Livewire 3 class:

public properties:

public $steps = [
    ['image' => null, 'text' => null],
];

public $step = ['image' => null, 'text' => null];

Add/Remove step logic:

public function removeStep($index): void
{
    unset($this->steps[$index]);// unset step
    $this->steps = array_values($this->steps); // reshuffle indexes after deleting
}

public function addStep(): void
{
    $this->steps[] = $this->step;
}

After adding all the necessary steps, they should be saved in the guide_steps table:

guide_steps:

Schema::create('guide_steps', function (Blueprint $table) {
    $table->id();
    $table->foreignId('recipe_id')->constrained()->cascadeOnDelete();
    $table->integer('step_number');
    $table->text('step_text');
    $table->string('step_image')->default('recipes-images/default/default_photo.png');
    $table->timestamps();

    $table->unique(['recipe_id', 'step_number']); // required for upsert()
});

Here is my approach for upserting new and updated steps, as well as removing steps that no longer exist in the new request:

upsertGroupedSteps:

public function upsertGroupedSteps($recipeId): void
    {
        $groupedSteps = collect($this->steps)->map(function ($step, $index) use ($recipeId){
            return [
                'recipe_id' => $recipeId,
                'step_number' => $index + 1,
                'step_text' => trim($step['text']),
                'step_image' => $step['image']
                    ? $step['image']->store('guides-images', 'public')
                    : 'recipes-images/default/default_photo.png',
                'created_at' => now(),
                'updated_at' => now(),
            ];
        })->toArray();

        // if recipeId != 0 it means we are updating existing steps
        if ($this->recipeId != 0){
            $newStepsNumbers = collect($groupedSteps)->pluck('step_number')->toArray();

            GuideStep::where('recipe_id', $recipeId)
                ->whereNotIn('step_number', $newStepsNumbers)
                ->delete();
        }

        GuideStep::upsert(
            $groupedSteps, ['recipe_id', 'step_number'], ['step_text', 'step_image']
        );
    }

Let’s say there are three steps with id 1, 2, 3 in the guide_steps table. When I delete the step with id 2, logically, steps with id 1 and 3 should remain. However, the step with id 3 gets deleted, and its data is overwritten into the step with id 2. I didn’t expect this behavior and am not sure if it’s correct.

0 likes
0 replies

Please or to participate in this conversation.