Solving A Typecasting "Gotcha" When Passing Data From Laravel To Vue.js

by Nick Basile
on Apr 11, 2018

When working with Laravel and Vue.js, a typical pattern for initializing our Vue components with data is to pass controller data from Laravel into Vue props. Doing this allows us to leverage Laravel's controllers to manage our data in an MVC fashion.

While this is a common practice, I've run into a tricky casting issue when I'm working with filtered data. I'll show you how to re-create the problem, and solve it so you can avoid it in your projects.

Set Up

We'll start with a regular Laravel project. For a guide on setting that up, you can check out this checklist. Beyond our basic set up, we'll need to fake some data in our HomeController.php.

In this file, we'll add an Array of $results in the index function.

public function index()
{
    $results = [
        [
            'id' => 1,
            'name' => 'Foo',
            'status' => 'active',
        ],
        [
            'id' => 2,
            'name' => 'Bar',
            'status' => 'inactive',
        ],
        [
            'id' => 3,
            'name' => 'Baz',
            'status' => 'active',
        ],
        [
            'id' => 4,
            'name' => 'Biz',
            'status' => 'inactive',
        ],
    ];
}

Next, we'll turn this Array into a Collection so we can apply the transforms that cause the issue.

public function index()
{
    $results = collect([
        [
            'id' => 1,
            'name' => 'Foo',
            'status' => 'active',
        ],
        [
            'id' => 2,
            'name' => 'Bar',
            'status' => 'inactive',
        ],
        [
            'id' => 3,
            'name' => 'Baz',
            'status' => 'active',
        ],
        [
            'id' => 4,
            'name' => 'Biz',
            'status' => 'inactive',
        ],
    ]);
}

Then we'll return our home view with this data so we can use it in our Vue component.

public function index()
{
    $results = collect([
        [
            'id' => 1,
            'name' => 'Foo',
            'status' => 'active',
        ],
        [
            'id' => 2,
            'name' => 'Bar',
            'status' => 'inactive',
        ],
        [
            'id' => 3,
            'name' => 'Baz',
            'status' => 'active',
        ],
        [
            'id' => 4,
            'name' => 'Biz',
            'status' => 'inactive',
        ],
    ]);
    
    return view('home', compact('results'));
}

With our data taken care of, we can focus on adding our Vue component.

Inside of our components folder, we can add a WhereExample.vue file. In here, we'll simply accept a results prop and render it with a v-for.

<template>
    <div>
        <p v-for="result in results" :key="result.id">{{ result.name }}</p><br/>
    </div>
</template>

<script>
    export default {
        props: {
            results: {
               type: Array,
               required: true,
            },
        },
    }
</script>

Now we can register this in our app.js file and add it to home.blade.php.

// app.js
Vue.component('where-example', require('./components/WhereExample.vue'));

//home.blade.php
<where-example :results="{{ $results }}"></where-example>

With our set up out of the way, let's dive into the issue.

The Problem

Notice that when we defined our results prop in our Vue component, we specified that we're expecting an Array. Right now, if we check out what's happening in our Vue Devtools, we can see that everything is working as expected.

Now for the problem. When we want to filter our data in the controller, we'll see that our prop is being cast as an Object instead of as an Array. To do that, we'll add a ->where() clause to our $results Collection to only show the active results.

public function index()
{
    $results = collect([
        [
            'id' => 1,
            'name' => 'Foo',
            'status' => 'active',
        ],
        [
            'id' => 2,
            'name' => 'Bar',
            'status' => 'inactive',
        ],
        [
            'id' => 3,
            'name' => 'Baz',
            'status' => 'active',
        ],
        [
            'id' => 4,
            'name' => 'Biz',
            'status' => 'inactive',
        ],
    ])
    ->where('status', 'active');
    
    return view('home', compact('results'));
}

Hopping back over to our Vue Devtools, we can see that our Array has turned into an Object! And, we can see that Vue provides a helpful error in the console.

This is a pretty big deal. Imagine that we wanted to show a message if we didn't have any results. With an Array, we could easily use .length to see if there were any results. But, we can't use .length with an Object. All of a sudden, our Vue component would be broken!

That's just one example, but there are plenty of features we could build expecting an Array as our data that would break if they received an Object. Fortunately, there is a pretty simple fix for this issue.

The Solution

The reason the ->where() clause changes our Array into an Object is because it maintains the index of the filtered results. When Vue receives this data with non-linear indexes, it assumes that this must be an Object.

While this is pretty smart and even desirable in most cases, it does leave us in a pickle here. But, we can use the ->values() clause after ->where() to reset the indexes to their linear values.

Doing this makes our results look like an Array again to Vue. So, in our example, we can add ->values() to the end of our $results Collection and everything will start working again.

public function index()
{
    $results = collect([
        [
            'id' => 1,
            'name' => 'Foo',
            'status' => 'active',
        ],
        [
            'id' => 2,
            'name' => 'Bar',
            'status' => 'inactive',
        ],
        [
            'id' => 3,
            'name' => 'Baz',
            'status' => 'active',
        ],
        [
            'id' => 4,
            'name' => 'Biz',
            'status' => 'inactive',
        ],
    ])
    ->where('status', 'active')
    ->values();
    
    return view('home', compact('results'));
}

Now we have the filtered results working the way we would expect.

The Wrap-Up

I've certainly spent some time trying to figure out why my Vue components have stopped working because of a simple ->where() clause in my controller. This is often tricky to debug because the issue is so far removed from where it shows up.

When I first encountered this issue, I probably wouldn't have figured it out if I hadn't been using Vue best practices by defining my prop definitions. It goes to show how helpful they can be in a tricky situation.

I hope this post will help you avoid, or at least debug, this issue in your projects. As always, feel free to ask me any questions on Twitter. And until next time, happy coding!

A photo of Nick Basile.

Nick Basile

I'm a full-stack developer working with Vue.js, Laravel, and more. In my spare time, I read, tweet, blog, and put together a newsletter.

Never Miss a Beat

Get weekly digests full of design, code, and business articles delivered right to your inbox.