Sorting in VueJS Components and Vuex State

Part 48 of 48 in API Driven Development With Laravel and VueJS
Dan Pastori avatar
Dan Pastori September 25th, 2018
⚡️ Updated content is available We learned a lot since we originally wrote this article. We now have this updated for Laravel 10, Vue 3/NuxtJS 3, and Capacitor 3.

At this point we can all agree VueJS is pretty amazing. At it’s core, the reactivity of the data that VueJS watches is what makes it incredible. Combine that with some simple searching and sorting and you can make an incredibly powerful interface with loads of useful features.

In this tutorial we will be going through 2 ways to sort VueJS data. The first way is sorting on a Vue component. This will be sorting data loaded locally and accessible through the data() property. We will be using the /admin/companies page for this example and showing how you can sort the table data and order by certain columns for the companies.

The second way we will be sorting will be through dispatching actions with filters and sorting data in Vuex. Very similar the actual sorting methods, the process is a little different. We will be storing the sorting parameters in the Vuex data store, along with dispatching events to modify the order with in Vuex to reflect in multiple areas of the application. Like I mentioned, the methods for doing the actual sorting will be the same, but the way we get to the method will be different. We will be using the filters from the homepage in a map view and syncing those filters on list view with sorting capabilities.

For reference, a lot of the mixins you will see when looking at the code can be found here: Filtering with VueJS Mixins – Server Side Up. These determine how the coffee shop should be displayed and how the text search works. This goes hand in hand with what we are doing in this tutorial because searching and sorting are very similar. We are just focused on sorting the data in this tutorial.

Let’s get started!

Sorting Component Data

For this example, we will be looking in the /resources/assets/js/pages/admin/Companies.vue file. If you are new to this project, make sure to check out the repo here: Roast.

What this page does is list out all of the companies in the application for the admin to click on and edit. If the user who is viewing the page is a coffee shop owner it will only show the shops they own. For more information on the structure of our permissions, check out this article: https://serversideup.net/planning-your-laravel-and-vuejs-spa-application-admin-section/.

Let’s assume the user logging in is an admin. There will be a lot of companies! We will need to sort it to make finding a specific company a heck of a lot easier.

Determine Sorting Parameters

This is really a discretionary step in the process, but a completely necessary one. Determine which columns in the table you want to sort by. There are columns that make no sense to sort by. For example, the website column. We don’t really need that to be sorted. For this example we will be sorting by Company Name, Cafe Count, and Pending Actions Count. All of which are extremely valuable to the user.

Set Up Local Data

In our /resources/assets/js/pages/admin/Companies.vue file, we will need to add two pieces of data to our local data (sortBy, sortDirection):

/*
  Defines the data used by the page.
*/
data(){
  return {
    sortBy: 'name',
    sortDirection: 'ASC',

    search: ''
  }
},

The sortBy field will be the column that we are sorting by and the sortDirection field will be the direction we are sorting the data.

We will be toggling this data in the next method.

Define Starting Point For Sorting

I like to define one starting point for the sorting of data. I feel like it’s cleaner to have a method that determines how we should sort the data. Essentially routes all sorting through the individual method out to smaller methods that handles the sorting of the data.

In this page, I will call the method resortCafes( by ). The parameter by will determine which column we sort the cafes by.

Let’s add the method like this:

/*
  Re-sorts the cafes by what was selected.
*/
resortCafes( by ){
  /*
    Checks to see if what the user selected to sort by
    is the same as it's been. If it is, then we toggle the
    direction.
  */
  if( by == this.sortBy ){
    if( this.sortDirection == 'ASC' ){
      this.sortDirection = 'DESC';
    }else{
      this.sortDirection = 'ASC';
    }
  }

  /*
    If the sort by is different we set the sort by to what the
    user selected and defualt the direction to 'ASC'
  */
  if( by != this.sortBy ){
    this.sortDirection = 'ASC';
    this.sortBy = by;
  }

  /*
    Switch by what the sort by is set to, and run the method
    to sort by that column.
  */
  switch( this.sortBy ){
    case 'name':
      this.sortCompaniesByName();
    break;
    case 'cafes':
      this.sortCompaniesByCafes();
    break;
    case 'pending-actions':
      this.sortCompaniesByPendingActions();
    break;
  }
},

When we get to adding a click to an element to sort the cafes, we will route it all through the single method. Let’s break it down chunk by chunk.

First, we determine the sort by:

if( by == this.sortBy ){
   if( this.sortDirection == 'ASC' ){
     this.sortDirection = 'DESC';
   }else{
     this.sortDirection = 'ASC';
   }
 }

What this does is we check to see if what we are going to be sorting by changes or not. If it’s the same, then we toggle the order. So if I click on the same column, we toggle the direction that the data is sorted in.

Second, we check to see if the order changes:

if( by != this.sortBy ){
  this.sortDirection = 'ASC';
  this.sortBy = by;
}

If the order changes, we set the local direction to the default of ASC and the sortBy field to what the user is sorting by.

So what we did at this point is configure what the user wanted. Did they want to change a field or toggle a direction?

Now that we have the field set, we go to the third step:

switch( this.sortBy ){
  case 'name':
    this.sortCompaniesByName();
  break;
  case 'cafes':
    this.sortCompaniesByCafes();
  break;
  case 'pending-actions':
    this.sortCompaniesByPendingActions();
  break;
}

What this does is switch by what the user is sorting by. We then call the method to run the sort depending on the column being sorted. Let’s add these methods and discuss how the sorting works.

Sorting By String

We are sorting by string for the name. This gets handled a little differently than sorting by number. The reason for the difference is that the sorting method which is a method on arrays in javascript has to be determined by a number.

A quick over view of the sort() method comes from here: JavaScript Array sort() Method.

Essentially, you take an array and pass it a method that has two parameters. This is the compare function. The compare function determines which order the value fits in the array. The definition of the compare function from w3schools is:

Optional. A function that defines an alternative sort order. The function should return a negative, zero, or positive value, depending on the arguments, like:

function(a, b){return a-b}
When the sort() method compares two values, it sends the values to the compare function, and sorts the values according to the returned (negative, zero, positive) value.

Example:

When comparing 40 and 100, the sort() method calls the compare function(40,100).

The function calculates 40-100, and returns -60 (a negative value).

The sort function will sort 40 as a value lower than 100.

We can use this compare function in the sort() method to search our objects. Since we are relying on JS string compare, we have to account for the values of a string to be the numerical value.

Let’s add the sortCompaniesByName() method to our methods object:

/*
  Sorts the companies by name.
*/
sortCompaniesByName(){
  this.companies.sort( function( a, b ){
    if( this.sortDirection == 'ASC' ){
      return ( ( a.name == b.name ) ? 0 : ( ( a.name > b.name ) ? 1 : -1 ) );
    }

    if( this.sortDirection == 'DESC' ){
      return ( ( a.name == b.name ) ? 0 : ( ( a.name < b.name ) ? 1 : -1 ) );
    }
  }.bind(this));
},

What we do here, is we apply the sort() method to the local companies object. At the end of the function we bind this. this allows us to use component local data within our compare function. We need to determine whether we are sorting in ascending or descending order based on the user’s preference.

When we determine that, we return a comparison of the name (since in this method we are using the name field for the company). Remember, since this is an array of objects, a is the first company, b is the second company. We compare the name values and return what the comparison is whether the name is equal, less than or greater than depending on if we are sorting ascending or descending.

That’s it for sorting! That little function will not only sort our companies, VueJS will re-render in the proper order on the screen as well after we are done!

Sorting By Number

So, very similar to the string sorting is the number sorting. In both the sorting by cafes count and pending actions, we are sorting by a number already. This is an easy comparison, however, the only gotcha is you should apply the parseInt() method to the number to ensure Javascript reads it as a number.

/*
  Sorts the companies by cafe count.
*/
sortCompaniesByCafes(){
  this.companies.sort( function( a, b ){
    if( this.sortDirection == 'ASC' ){
      return parseInt( a.cafes_count ) < parseInt( b.cafes_count ) ? 1 : -1;
    }

    if( this.sortDirection == 'DESC' ){
      return parseInt( a.cafes_count ) > parseInt( b.cafes_count ) ? 1 : -1;
    }
  }.bind(this));
},

/*
  Sorts the companies by pending actions.
*/
sortCompaniesByPendingActions(){
  this.companies.sort( function( a, b ){
    if( this.sortDirection == 'ASC' ){
      return parseInt( a.actions_count ) < parseInt( b.actions_count ) ? 1 : -1;
    }

    if( this.sortDirection == 'DESC' ){
      return parseInt( a.actions_count ) > parseInt( b.actions_count ) ? 1 : -1;
    }
  }.bind(this));
}

In both sorting methods, we parse the integer from the string when we run our comparison. This ensures that javascript interprets the data as an integer instead of a string.

It’s time to hook it up to our interface!

Hooking Up The Interface

In the template you will only need to focus on one part, and that will be the table header. This will be where the user clicks to sort the data making a nice UX.

Let’s look at the head of the table:

<div class="grid-x companies-header">
  <div class="large-3 medium-3 cell sortable-header" v-on:click="resortCafes('name')">
    Company
    <img class="sort-icon" src="/img/sort-asc.svg" v-show="sortBy == 'name' && sortDirection == 'ASC'"/>
    <img class="sort-icon" src="/img/sort-desc.svg" v-show="sortBy == 'name' && sortDirection == 'DESC'"/>
  </div>
  <div class="large-5 medium-5 cell">
    Website
  </div>
  <div class="large-2 medium-2 cell sortable-header" v-on:click="resortCafes('cafes')">
    Cafes
    <img class="sort-icon" src="/img/sort-asc.svg" v-show="sortBy == 'cafes' && sortDirection == 'ASC'"/>
    <img class="sort-icon" src="/img/sort-desc.svg" v-show="sortBy == 'cafes' && sortDirection == 'DESC'"/>
  </div>
  <div class="large-2 medium-2 cell sortable-header" v-on:click="resortCafes('pending-actions')">
    Actions Pending
    <img class="sort-icon" src="/img/sort-asc.svg" v-show="sortBy == 'pending-actions' && sortDirection == 'ASC'"/>
    <img class="sort-icon" src="/img/sort-desc.svg" v-show="sortBy == 'pending-actions' && sortDirection == 'DESC'"/>
  </div>
</div>

So we have 4 columns. We want to sort by the first one, company name, the third one, cafes, and the fourth one, actions pending. What we did is on the column added the v-on:click="resortCafes('field_value’)”. Now when the user clicks that, the method will be called to re-sort the cafes by the field passed in. The next thing we did is added an ascending and descending arrow in the column header. This will show based on whether we are sorting by the column and it’s ascending or descending.

<img class="sort-icon" src="/img/sort-asc.svg" v-show="sortBy == 'pending-actions' && sortDirection == 'ASC'"/>
<img class="sort-icon" src="/img/sort-desc.svg" v-show="sortBy == 'pending-actions' && sortDirection == 'DESC'"/>

This will show the user what state the current sorting is in.

That about does it! The next section will show you how to sort through Vuex actions and keep state across multiple views. There is a use to each type, so might as well dive into the next section!

Sorting Vuex Data

There will be times you will want to keep the order of your search across multiple views. In those scenarios, you will have to store the sorting preferences in the Vuex Data Store. When you are doing it on one view, like the previous example, it makes sense to keep the sorting local.

For this example we will want to keep the filters from Filtering with VueJS Mixins – Server Side Up present across the map an list views on the /cafes url. When we are on the list view, we should apply sorting to the cafes as well. Since we are using the same filters across the popout and the slide down, we will be storing this data in a Vuex data store.

Now, I took the filters from: Filtering with VueJS Mixins – Server Side Up and migrated it to a store /resources/assets/js/modules/filters.js. We will be going through the sorting examples here as well.

I also added a nice drop down sort on the list view in the /resources/assets/js/components/cafes/ListFilters.vue component. This will be shared with the /resources/assets/js/components/global/Filters.vue filters. These two components will share the selected filter state. So if the user switches views their filters will still be applied. Let’s get started!

Overview Of Vuex Filter Changes

Just a quick overview of the filter and sort states. In this article Filtering with VueJS Mixins – Server Side Up we added a whole bunch of filters and made them re-usable using VueJS Mixins. When a user updated the filters, we dispatched an event saying “hey filters updated, re process!”.

What we did to update this is we made all of the filters set a specific piece of state in the Vuex Data Store: /resources/assets/js/modules/filters.js.

In the CafeMap.vue and CafeList.vue components, instead of listen for an event, we watched all of the filters in the application. When one of the filters changes, we re-process the filters to ensure that the data stays in sync. The reason we moved the filters to a Vuex store is because we have two places now where the filter data needs to be synced, the Cafe Map page and the Cafe List page. These filters are in two different components but should remain consistent across the states.

Now, what we will be doing in this section of the tutorial, is focusing on the sorting of the data that’s in the vuex store. I added all sorting we will be doing to the /resources/assets/js/components/cafes/ListFilters.vue component.

What We Will Be Sorting

In the /resources/assets/js/components/cafes/ListFilters.vue component, we will be sorting the cafes listed by the name of the cafe and the number of likes the cafe has. We will sort these fields in both ascending and descending order.

If you notice, we only apply this sorting to the list view and not the map view. In the list view, this makes sense, where the map view, the display will not be affected by how we sort the cafes since it’s display is based on location. That way, even if we sort the Vuex store, it will only display in that order on the list view page where the cafe view will remain un-affected.

Let’s get started!

Add Sorting Fields to Our Filters State

First, let’s open up our /resources/assets/js/modules/filters.js file and add some fields to make this sorting possible.

Let’s start with adding some default values to our state:

/*
  Defines the state used by the module
*/
state: {
  textSearch: '',
  activeLocationFilter: 'all',
  onlyLiked: false,
  brewMethodsFilter: [],
  hasMatcha: false,
  hasTea: false,
  hasSubscription: false,
  orderBy: 'name',
  orderDirection: 'asc'
},

We have added the orderBy and defaulted it to name and the orderDirection and defaulted it to asc.

Next, let’s add two actions to set the order information:

/*
  Updates the order by setting and sorts the cafes.
*/
updateOrderBy( { commit, state, dispatch }, data ){
  commit( 'setOrderBy', data );
},

/*
  Updates the order direction and sorts the cafes.
*/
updateOrderDirection( { commit, state, dispatch }, data ){
  commit( 'setOrderDirection', data );
},

We can now call these actions to set the way we want our cafes ordered.

We will then need to add the mutations and getters for both pieces of state.

Mutations:

/*
  Sets the order by filter.
*/
setOrderBy( state, orderBy ){
  state.orderBy = orderBy;
},

/*
  Sets the order direction filter.
*/
setOrderDirection( state, orderDirection ){
  state.orderDirection = orderDirection;
},

Getters:

/*
  Gets the order by filter.
*/
getOrderBy( state ){
  return state.orderBy;
},

/*
  Gets the order direction filter.
*/
getOrderDirection( state ){
  return state.orderDirection;
}

Nothing too fancy, but at least we are set up on the state side for handling our sorting.

Using the Vuex In the List Filters Component

Since we set up our Vuex states, let’s start to implement them in our /resources/assets/js/components/cafes/ListFilters.vue component.

Like with using all Vuex state in Vue Components, they are computed properties. We include all of the filters and sorting state in a computed property, for more information check out: https://serversideup.net/using-vuex-modules-inside-components/.

Let’s add the following pieces of state to the ListFilters.vue component as computed data:

/*
  Gets the order by setting.
*/
orderBy: {
  /*
    Define setter so we can use it as vue model.
  */
  set( orderBy ){
    this.$store.dispatch( 'updateOrderBy', orderBy );
  },

  /*
    Define getter so we can use it as a vue model.
  */
  get(){
    return this.$store.getters.getOrderBy;
  }
},

/*
  Gets the order direction.
*/
orderDirection(){
  return this.$store.getters.getOrderDirection;
}

If you notice, there’s something interesting about the orderBy computed property. That’s because we will be using that property as a model. Thanks to this article: Anyway, this is how to use v-model with Vuex. Computed setter in action., you can actually use a piece of Vuex state as a model on an input.

All we do for this, is define two methods inside of the computed data, a get() and a set() method. The set() method takes the name of the computed property as the first parameter. It then dispatches the data when set, using the action that we use to update the order by information. We do a dispatch, because we do two things when the updateOrderBy action is called. First, we set the mutation in the state. Second, we dispatch another event to order the cafes which we will talk about soon.

The get() method simply returns the property like we are used to. This level of control is awesome because now we can do what we need, and instead of writing an intermediate method, we can directly mutate the state properly as a Vuex module.

If you browse some of the filter data, we do this a few more times for filters. It’s the same concept and same implementation where we define a set() and get() method explicitly for the Vuex state and run a direct mutation command when getting set.

Add The Visual Piece

Now to add the visual side to allow our users to select filters accordingly. We will add the following HTML to the template. I added it below the text search just so it’d make more sense from a UX perspective:

<div class="grid-x grid-padding-x">
  <div class="large-6 medium-6 small-12 cell">
    <label class="filter-label">Order By</label>
    <select v-model="orderBy">
      <option value="name">Name</option>
      <option value="most-liked">Most Liked</option>
    </select>
  </div>
  <div class="large-6 medium-6 small-12 cell">
    <label class="filter-label">Order Direction</label>
    <span class="asc order-direction" v-bind:class="{ 'active': orderDirection == 'asc' }" v-on:click="setOrderDirection('asc')">Asc</span><span class="desc order-direction" v-bind:class="{ 'active': orderDirection == 'desc' }" v-on:click="setOrderDirection('desc')">Desc</span>
  </div>
</div>

Let’s step through the HTML. First, we have the Order By select box. Notice how we set the v-model parameter to be orderBy which is a computed component? Sweet huh? Now when the user selects what is ordered by the state gets updated accordingly.

Next, we have the order direction. It’s a simple toggle. We bind the active class if the order direction from the state matches the button. We also have an v-on:click="setOrderDirection(direction) method attached to the asc or desc buttons. What this does is dispatch an action to change the state accordingly. Why didn’t we use a model? Because we can’t apply a model to an element that’s not an input. So, in this case, we have to have an intermediate method that actually runs the action.

All we have to do is add the method to our methods like this:

/*
  Sets the order direction.
*/
setOrderDirection( direction ){
  this.$store.dispatch( 'updateOrderDirection', direction );
}

What this does is dispatches the action updateOrderDirection and sets the order direction in the state.

Now we have everything hooked up, our sorting and direction gets changed, but we don’t actually sort the cafes in the state yet. Remember in our /resources/assets/js/components/cafes/CafeList.vue component, we read from the data store for cafes. Our CafeCard.vue files are rendered in the order from the state.

Let’s actually do the sorting so this reflects accordingly on our screen.

Apply Sorting To Cafes In State

So we have everything set up to sort the cafes. When the user selects how they want to sort the data, those settings get persisted in the Vuex filters store (/resources/assets/js/modules/filters.js). Now how do we get that to sort the cafes?

First, re-open /resources/assets/js/modules/filters.js and navigate to the updateOrderBy() action. In this action, we commit the order by that’s been passed in to the state. What we will do now is dispatch an action to the /resources/assets/js/modules/cafes.js module that allows us to order the cafes.

To do this, change the method to look like this:

/*
  Updates the order by setting and sorts the cafes.
*/
updateOrderBy( { commit, state, dispatch }, data ){
  commit( 'setOrderBy', data );
  dispatch( 'orderCafes', { order: state.orderBy, direction: state.orderDirection } );
},

What we do first is save the state for what the user selected. Next, we dispatch the orderCafes action and pass the current state’s orderBy field and the direction.

Update the updateOrderDirection() action to the same thing:

/*
  Updates the order direction and sorts the cafes.
*/
updateOrderDirection( { commit, state, dispatch }, data ){
  commit( 'setOrderDirection', data );
  dispatch( 'orderCafes', { order: state.orderBy, direction: state.orderDirection } );
},

Now when either of these are updated, the action is dispatched to order the cafes. Let’s implement that action.

Open up the /resources/assets/js/modules/cafes.js file.

At the end of the actions, add the following action:

/*
  Orders the cafes in the data store.
*/
orderCafes( { commit, state, dispatch }, data ){
  /*
    Set the cafes to a local variable.
  */
  let localCafes = state.cafes;

  /*
    Switch what we are ordering by.
  */
  switch( data.order ){
    case 'name':
      /*
        Sort cafes by name.
      */
      localCafes.sort( function( a, b ){
        if( data.direction == 'desc' ){
          return ( ( a.company.name == b.company.name ) ? 0 : ( ( a.company.name < b.company.name ) ? 1 : -1 ) );
        }else{
          return ( ( a.company.name == b.company.name ) ? 0 : ( ( a.company.name > b.company.name ) ? 1 : -1 ) );
        }
      });
    break;
    case 'most-liked':
      /*
        Sort cafes by most liked.
      */
      localCafes.sort( function( a, b ){
        if( data.direction == 'desc' ){
          return ( ( a.likes_count == b.likes_count ) ? 0 : ( ( a.likes_count < b.likes_count ) ? 1 : -1 ) );
        }else{
          return ( ( a.likes_count == b.likes_count ) ? 0 : ( ( a.likes_count > b.likes_count ) ? 1 : -1 ) );
        }
      });
    break;
  }

  /*
    Update the cafes state.
  */
  commit( 'setCafes', localCafes );
}

This is a pretty large method, let’s break it down a little bit. First, let’s make a copy of the current cafes to a variable localCafes. I just do this out of practice while we make changes.

/*
    Set the cafes to a local variable.
*/
let localCafes = state.cafes;

Next, let’s switch off of data.order . Remember, we passed in data with an order key from our action in the filters state.

We then determine how we will like to sort the cafes, either by name or most liked. In the future, maybe something like review can be added here as well ;).

Depending on how we want to sort the cafes, we run the proper sorting method. These are described in the first section where the variable gets called with the sort() method and gets passed a comparison method that determines how we should sort the objects in the array.

Let’s say the user wants to sort by most-liked. We would run the following method:

/*
  Sort cafes by most liked.
*/
localCafes.sort( function( a, b ){
  if( data.direction == 'desc' ){
    return ( ( a.likes_count == b.likes_count ) ? 0 : ( ( a.likes_count < b.likes_count ) ? 1 : -1 ) );
  }else{
    return ( ( a.likes_count == b.likes_count ) ? 0 : ( ( a.likes_count > b.likes_count ) ? 1 : -1 ) );
  }
});

Inside of our comparison function, we determine if we are sorting by the direction desc or asc. These were passed in with the data. In there, we sort the likes_count in the order defined.

Once our localCafes variable has been set, we commit the updated data to the setCafes mutation. This updates the state to the localCafes variable which is the cafes ordered correctly.

/*
  Update the cafes state.
*/
commit( 'setCafes', localCafes );

Now here’s where the magic happens. Voila, since our cafes in the cafe list are rendered off of the cafes in the state, they are automatically, reactively updated to show the proper order! That’s the power of ordering in Vuex! And on the Cafe Map, it doesn’t matter. Even though it reads from the same state, it’s displayed off of location so the order doesn’t matter!

You can now add as many sorting parameters as you need to your Vuex filter and handle them through the orderCafes action and all of your cafes will be sorted correctly!

Conclusion

There are two ways you can handle sorting, locally in the component or through Vuex state. I recommend handling it the following way. If you don’t need to share the way a piece of data is sorted to many components, do it locally. If you want to share the way it’s sorted across components, do it through state.

Hope this tutorial shed some light on how awesome VueJS can sort data. If you have any questions, definitely feel free to reach out below.

If you want to browse the code, all of it’s available here: GitHub. To use Roast, if you are a coffee drinker, or just want to check out the app, visit: Roast and Brew – Helping the coffee enthusiast find their next cup of coffee. Let me know your feed back and if you want any features added as well!

We are hammering down on our book about API Driven Development with Laravel and VueJS, so sign up at Server Side Up General List to stay in the loop!

Support future content

The Ultimate Guide to Building APIs and Single-Page Applications with Laravel + VueJS + Capacitor book cover.

Psst... any earnings that we make off of our book is being reinvested to bringing you more content. If you like what you read, consider getting our book or get sweet perks by becoming a sponsor.

Written By Dan

Dan Pastori avatar Dan Pastori

Builder, creator, and maker. Dan Pastori is a Laravel certified developer with over 10 years experience in full stack development. When you aren't finding Dan exploring new techniques in programming, catch him at the beach or hiking in the National Parks.

Like this? Subscribe

We're privacy advocates. We will never spam you and we only want to send you emails that you actually want to receive. One-click unsubscribes are instantly honored.