livewire: Livewire encountered corrupt data when trying to hydrate the component issue.

Describe the bug Short: I have an array of tasks, just a 2d array with text in keys, that livewire is loosing state on.

Longer: This is a volunteer school board project that I’m going VERY YANGI on, in the hopes it takes just a few days of spare time. We have a bunch of shared tasks, that I’m putting online for the right people to see.

I made a simple checklist component and added it to the site, I haven’t even put items in a DB at this point, (perhaps that could help, but I decided to go all the way for the YAGNI)

Here is the component model, I"m just putting the whole thing in as is so it’s easy to test:

<?php

namespace App\Http\Livewire;

use Livewire\Component;

class Checklist extends Component
{
    public $flash = '';
    public $question = '';
    public $tasks = [
        'setup_the_system'=>[
            'title_note' => 'Most of this section is stuff that you won\'t readily be able to see,
            for example programming this checklist and the ability to ask questions here. However,
            it\'s all very helpful in moving this forward in the easiest way possible to solve problems
            in the easiest ways we can think of.',
            'create_visual_checklist_format' => 1,
            'create_version_repository'=>1,
            'create_version_repository_note'=> 'This basically will show all versions of the project, it\'s a <em>DR</em>
            (Disaster recovery) mechanism, in case I kick the bucket, another person can take over with a list of everything
            that\'s ever been done and things that need to get done in order to move forward. I will also endeavour to
            find someone to take over for me in case that should occur.',
            'kick_bucket_replacement_volunteer_found' => 0,
            'put_up_temporary_site'=>1,
            'put_up_temporary_site_note'=>'you are on it, the data on here will constantly be destroyed and re-written
            as we get ready to make something real! As real as this feels, don\'t save anything here that is important.
            Instead email me or keep a word doc, or basically anything else other than saving real data here.',
        ],
        'PAC USE CASES (A CHECK MEANS IT\'S AGREED UPON)' => [
            'Gatekeeper authorizes guardians who register'=>1,
            'Gatekeeper authorizes guardians who register_note'=> 'The <b>"Gatekeeper"</b> is a PAC member who has access
             to information and knows which families are attending the school and which ones are not.<br><br>
             Anyone who registers need to go through a 2 step process to have access to <b><em>any</em></b> information.
             Step 1 is simply verifying their email address which they can do without any intervention from the gatekeeper.
             Step 2 requires a person to actually allow the verified guardian access to see our internal information.
             Step 2 will also need to be re-done every year.',
            'Gatekeeper is notified each time someone registers'=>1,
            'Gatekeeper is notified each time a child is registered'=>1,
            'Gatekeeper confirms child details including homeroom'=>1,
            'Gatekeeper confirms child details including homeroom_note'=> '
            This seems overwhelming for 1 person, it could even be 2 people sitting together on skype or in an office
            calling out names, it should take more than an evening or 2.
            ',

            'pac members / room reps can send notifications to their classes'=>0,
            'pac members / room reps can send notifications to their classes_note'=>'
            This is an idea, notice the unchecked box, and opinions are welcome. <br><br>
            Instead of a cumbersome trickle down human information system PAC emails me, I email my room constituents,
            perhaps school wide updates like the PAC meeting details can just be summarized and sent to the people who
            want notification. Either everyone if it\'s deemed important or, people who subscribed to news in some fashion.<br><br>
            When there are room specific issues, we could then pass those on directly as room reps.
            ',
        ],

        'GUARDIAN USE CASES (A CHECK MEANS IT\'S AGREED UPON)' => [
            'Guardian can register their children'=>1,
            'Guardian can add other guardians to their family' => 1,
            'Guardian can add other guardians to their family_note' => '
            Guardians who are added get a notification email to register',
            'Should invited guardians need to be confirmed by gatekeeper?'=>0,
            'Should invited guardians need to be confirmed by gatekeeper?_note'=>'
            For example, I go through registration and get confirmed. I then add another guardian Jane, and
            Jane gets an email to register. Jane registers and gets verified via email.<br><br>
            Does she need to get confirmed too?',
            'Guardians add children'=>1,
        ]
    ];
    
    public function render()
    {
        return view('livewire.checklist');
    }
    
    
    public function askMe()
    {
        $this->flash = $this->question;
        $this->question = '';
    }
}
<div class="">
    @if(!empty($flash))
        <div class="p-12 bg-blue-100">{{$flash}}</div>
    @endif

    <div class="flex flex-col md:flex-row">
        <div class="w-full md:w-1/2">
            @foreach($tasks as $title => $group)
                <div
                    class="font-semibold mt-4 tracking-wide text-sm uppercase text-gray-600">{{ str_replace('_',' ',$title) }}</div>
                @foreach($group as $key=>$value)
                    @if(is_array($value))
                        <div
                            class="font-semibold mt-4 ml-8 tracking-wide text-sm uppercase text-gray-600">{{ str_replace('_',' ',$key) }}</div>
                        @foreach($value as $childKey=>$childValue)
                            @if(Str::endsWith($childKey,'_note'))
                                <div
                                    class="mt-1 ml-12 pl-3 tracking-wide text-sm text-gray-600">{!! $childValue !!}</div>
                            @else
                                <label class="ml-8 flex items-center mt-2">
                                    <input type="checkbox"
                                           wire:model.lazy="tasks.{{ $title }}.{{ $key }}.{{ $childKey }}"
                                           class="form-checkbox" @selected($tasks[$title][$key][$childKey],1)>
                                    <span class="ml-2">{{ str_replace('_',' ',$childKey) }}</span>
                                </label>
                            @endif
                        @endforeach
                    @else
                        @if(Str::endsWith($key,'_note'))
                            <div
                                class="mt-1 {{ ($key != 'title_note'? 'ml-4 pl-3': '') }} tracking-wide text-sm text-gray-600">{!! $value !!}</div>
                        @else
                            <label class="flex items-center mt-2">
                                <input type="checkbox" wire:model.lazy="tasks.{{ $title }}.{{ $key }}"
                                       class="form-checkbox" @selected($tasks[$title][$key],1)>
                                <span class="ml-2">{{ str_replace('_',' ',$key) }}</span>
                            </label>
                        @endif
                    @endif
                @endforeach
            @endforeach


        </div>
        <div class="w-full md:w-1/2 mt-4">
            <div class="md:fixed md:w-5/12">
                <div class="flex flex-col w-full md:ml-12">
                    <div class="font-semibold tracking-wide text-sm uppercase text-gray-600">Questions / Concerns</div>

                    <label class="block mt-2 w-full mx-auto">
                        <div class="form-label">I'd like to know ... @error('question')<br>{{ $message }}@enderror</div>
                        <textarea wire:model.lazy="question" name="question" id="question" autocomplete=""
                                  autocapitalize="off"
                                  rows="15"
                                  class="form-input block w-full @error('question') error @enderror"
                                  placeholder="how can I help you?">{{ $question }}</textarea>
                    </label>

                    <button wire:click.prevent="askMe" class="btn brand w-full mt-6">Ask Me Stuff!</button>
                </div>
            </div>
        </div>
    </div>
</div>

It produces something like this: Screen Shot 2019-11-08 at 1 48 11 PM

To Reproduce Steps to reproduce the behavior:

  1. Make a livewire enabled project
  2. Add the checklist component
  3. Add any input to the Ask Me a Question Field
  4. Click the button

Expected behavior The flash public item will get the data from the text area

Screenshots If applicable, add screenshots to help explain your problem. Screen Shot 2019-11-08 at 1 51 29 PM

Desktop (please complete the following information): Macos Catalina Brave browser, same thing happens in google chrome.

Additional context When I shrink the array down it will work or fail at some random size.

There is no way here to “key” this up without a re-implementation maybe numeric keys or task objects. But it feels unnecessary for something so small and simple.

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Reactions: 2
  • Comments: 86 (27 by maintainers)

Most upvoted comments

Ok, the problem is that Livewire only ignores the vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php middleware, not the local app one (app/Http/Middleware/TrimStrings.php).

I added both now to the bypass.

This fix will be out in the next release.

Thanks for uploading that repo @roni-estein - big help!

Great tips, thanks @lucasromanojf !

@drj378

In my case the problem had to do with indexed arrays, that got converted from PHP to JSON and back. Also, the sorting in the SQL table had something to do with it. I’m sure this is documented somewhere, but I can’t find it right now. I’ll try my best to recall what happened:

I had Components that used entries from an SQL table (as models and collections, not retrieved by hand). Table looked like this:

id name
1 bar
2 baz
3 foo

The collection that was used by Livewire behaved like this array:

$data[1] = 'bar';
$data[2] = 'baz';
$data[3] = 'foo';

Since Livewire converts PHP data to JavaScript data, the resulting JSON is this:

{"1":"bar","2":"baz","3":"foo"}

I don’t have the problematic code anymore, and I can’t remember all details of this. I used the array in an HTML form for a select list. This all was working UNTIL the data in my table was not sorted alphabetically together with the ids anymore. So when the table looked like this:

id name
2 baz
3 foo
11 bar

I got the dreaded “Livewire encountered corrupt data” exception. Somewhere in this mess the data was sorted differently than it came out of the database and the checksum for livewire was not right anymore. I really can’t remember all the details, but I learned that a) livewire has its rough edges around data types b) in livewire it is better to write dumb code that converts from collections to arrays and let this run through livewire instead of using collections as public data in livewire. This is only partly livewires fault. This has also to do with the strange behavior of HTML Forms and PHP. c) I had a very hard time to find the solution, because I searched in the code, but the bug was triggered by the order of entries in the SQL table.

@evildabbit I think because you have defaulted the tasks, users, and products to arrays, but then populate them with Eloquent results (a collection).

Try removing the array assignments and give it another shot 👍. I.e.:

public function mount()
{
    $this->query = '';
}

@stevebauman, the next release of Livewire (v1.0.2 probably) will now throw an exception if you try to set public $id in your component.

@skoontastic The reason it didn’t work was because you override the $id of the component. You should not use $this->id = $student->id;. $id is used by the ComponentChecksumManager uses the id when comparing checksums. If you used $this->studentid = $student->id; it would have worked (I think)

Guys thanks so much for checking it out, so busy with kids and “play dates” for them that I haven’t sat down at a screen until now. I’ll update livewire and try to clear some caches. I did want to bring up, that this project has insane potential and with maintainers who are so quick to solve issues and help point out our “own” errors, it’s a pleasure to work on.

I really appreciate it.

@roni-estein, I pulled down your “checklist” component and it seemed to work how it should: Nov-11-2019 11-23-20

@tabuna, thanks for boiling the issue down further. I tried the tasks data you posted and it worked fine as well.

Is that data breaking for you both? (the data with the space in it)

I’m guessing this is related to the following feature of Livewire:

  • By default Laravel adds a “TrimStrings” middleware to requests that would normally cause breakages like this.
  • Livewire has some code in the Service Provider (will post after) that bypasses this middleware for Livewire requests so there aren’t data mismatches like this.
  • It’s possible for some reason that this bypassing isn’t happening in your projects. In which case, we should investigate why.

(the bypassing middleware bit from: src/LivewireServiceProvider.php):

        if ($this->app['livewire']->isLivewireRequest()) {
            $this->bypassMiddleware([
                TrimStrings::class,
                ConvertEmptyStringsToNull::class,
            ]);
        }

Thanks for submitting a thorough issue, and thanks everyone else for pitching in. Insanely helpful, really.

Also, you may have to use this for your Route::livewire() call if you’re using route-model binding:

Route::livewire('products/edit/{product}', 'products.product-edit')->name('products.edit');

I’m closing this for now.

Re-open if someone encounters an issue that hasn’t been already addressed in this thread.

This error can be thrown for a number of different reasons, so I imagine new cases will crop up. Thanks!

@calebporzio you’re always welcome but I am not @lucasmichot hahahaha

Great tips, thanks @lucasromanojf !

@calebporzio, I refuse to take any credit for @lucasromanojf 's tip! Even if, indeed, it’s a good one 👍

I was able to fix my issue. The issue was that I had a middleware that was responsible for sanitizing request inputs to avoid XSS attacks. Removing this middleware (that is not needed for me anymore but was years ago) fixed the issue. Probably you guys that are having this same issue do not have this middleware like me, so I suggest checking if there is something somehow changing your request input before reaching the Livewire component.

Being more specific, this code was making the issue happen with non-string public properties:

private function arrayStripTags($array)
{
        $result = [];

        foreach ($array as $key => $value) {
            // Don't allow tags on key either, maybe useful for dynamic forms.
            $key = strip_tags($key);

            // If the value is an array, we will just recurse back into the
            // function to keep stripping the tags out of the array,
            // otherwise we will set the stripped value.
            if (is_array($value)) {
                $result[$key] = $this->arrayStripTags($value);
            } else {
                // I am using strip_tags(), you may use htmlentities(),
                // also I am doing trim() here, you may remove it, if you wish.
                $result[$key] = trim(strip_tags($value));
            }
        }

        return $result;
}

private function globalXssClean()
{
        // Recursive cleaning for array [] inputs, not just strings.
        $sanitized = $this->arrayStripTags(Request::all());
        Request::merge($sanitized);
}

public function handle($request, Closure $next)
{
        //XSS protection
        $this->globalXssClean();

        return $next($request);
}

I was able to fix my issue. The issue was that I had a middleware that was responsible for sanitizing request inputs to avoid XSS attacks. Removing this middleware (that is not needed for me anymore but was years ago) fixed the issue. Probably you guys that are having this same issue do not have this middleware like me, so I suggest checking if there is something somehow changing your request input before reaching the Livewire component.

Hey everyone,

I finally narrowed my issue down to one of my public properties… I switched from this…

<?php

namespace App\Http\Livewire;

use Livewire\Component;

class RecentWeights extends Component {
	public $recent_weigh_ins;
	public $display_weigh_ins = false;

	public function mount() {
		$this->recent_weigh_ins = auth()->user()->weigh_ins->sortByDesc( 'when' );
	}


	public function render() {
		return view( 'livewire.recent-weights' );
	}

	public function toggle_weigh_ins() {
		$this->display_weigh_ins = ! $this->display_weigh_ins;
	}
}

to this…

<?php

namespace App\Http\Livewire;

use App\WeighIn;
use Livewire\Component;

class RecentWeights extends Component {

	public $recent_weigh_ins;
	public $display_weigh_ins;
	public $user_id;

	public function mount() {
		$this->user_id           = auth()->user()->id;
		$this->recent_weigh_ins  = WeighIn::where( 'user_id', $this->user_id )
												->orderBy( 'when', 'asc' )
												->get();
		$this->display_weigh_ins = false;
	}


	public function render() {
		return view( 'livewire.recent-weights' );
	}

	public function toggle_weigh_ins() {
		$this->display_weigh_ins = ! $this->display_weigh_ins;
	}
}

…and that fixed my hydration issue…

Again, I’m not entirely sure why. The first method seemed fine until I added any interactivity to it.

Hope thats helpful to someone!

I did the same thing Caleb in another issue 😅

Could you further elaborate why you believe this is related to unique-keys? I have the same problem in a form.