Converting options API to composition API in Vue.js: A step-by-step guide

I have successfully implemented Vue.js version 3 in my project. The code below showcases a fully functional setup using dummy data, options API with filters and sorting:

<template>
  <h5>List of Products</h5>

        <h3>Filter</h3> 
        <button v-on:click="resetOptions">Reset</button>
        <button v-on:click="sorting">Sorting</button>
        <button v-on:click="sorting2">Sorting2</button>
        <select v-model="category">
            <option value="Accessories">Accessories</option>
            <option value="Laptop">Laptop</option>
            <option value="Stationary">Stationary</option>
        </select> 
        <div>
        <input type="checkbox" name="test" id="test" v-model="city"  value='Roma' /> 
        <label for="test"> Roma</label>
        <input type="checkbox" name="test2" id="test2" v-model.trim="city" value='Barselona'  />
        <label for="test2"> Barselona</label>
        <input type="checkbox" name="test3" id="test3" v-model.trim="city" value='Milano'  />
        <label for="test3"> Milano</label>
        </div>
         <!-- <select v-model="city">
            <option value="Barselona">Barselona</option>
            <option value="Roma"> Roma </option>
            <option value="Milano">Milano</option>
        </select>  -->

        <input type="text" v-model="name" placeholder="Filter By Name"/>

        <label for="vol">Price (between 0 and 1000):</label>
    
        <input type="range" v-model.trim="range" min="0" max="1000" step="10"/>  
        <ul>
            <li v-for="product in filterProducts" :key="product.name"> Product Name : {{product.name}} - Price : {{product.price}} ({{product.category}}) 
                {{product.city}}
            </li>
        </ul>
</template>

<script>
import getPosts from '../composables/getPosts'
export default {
 data: ()=> ( {

            city:['Roma', 'Barselona', 'Milano'],
            category: '',
            name: '',
            range: '10000',
            products: [
                { name: "Keyboard", price: 44, category: 'Accessories', city:'Roma'},
                { name: "Mouse", price: 20, category: 'Accessories', city:'Barselona'},
                { name: "Monitor", price: 399, category: 'Accessories', city:'Roma'},
                { name: "Dell XPS", price: 599, category: 'Laptop', city:'Roma'},
                { name: "MacBook Pro", price: 899, category: 'Laptop', city:'Roma'},
                { name: "Pencil Box", price: 6, category: 'Stationary', city:'Barselona'},
                { name: "Pen", price: 2, category: 'Stationary', city:'Milano'},
                { name: "USB Cable", price: 7, category: 'Accessories', city:'Milano'},
                { name: "Eraser", price: 2, category: 'Stationary', city:'Roma'},
                { name: "Highlighter", price: 5, category: 'Stationary', city:'Roma'}
            ]

        }),

              computed: {
            filterProducts: function(){
                return this.filterProductsByName(this.filterProductsByRange(this.filterProductsByCity(this.filterProductsByCateg ory(this.products))))
            },

        },
        
        methods: {

            filterProductsByCategory: function(products){
                return products.filter(product => !product.category.indexOf(this.category))
            },

            filterProductsByName: function(products) {
                return products.filter(product => !product.name.toLowerCase().indexOf(this.name.toLowerCase()))
            },

            filterProductsByCity: function(products) {
                     return products.filter(product => this.city.indexOf(product.city) !== -1)
            },

             filterProductsByCity2: function(products) {
                 return products.filter(product => !product.city.indexOf(this.city))
             },

            filterProductsByRange: function(products){
                return products.filter(product => (product.price >= 0 && product.price <= this.range) ? product : '')
            },

            sorting:function(){
                this.products.sort((a,b)=>(a.price > b.price) ? 1 : -1)
            },
             sorting2:function(){
                this.products.sort((a,b)=>(a.price < b.price) ? 1 : -1)
            },

            uniqueCheck(e){
            this.city = [];
            if (e.target.checked) {
                 this.city.push(e.target.value);
               }
         },

            resetOptions:function(){
                this.category='',
                this.city='',
                this.name='',
                this.range='1000'
            },
        },

}
</script>

<style>

</style>

However, I now wish to transition to the Composition API by implementing a fake API call using mock data. I encountered some issues while converting functions from Options API to Composition API in the filtering logic. Here is a snippet of the transformed code:

<template>
  <div class='home'>
      <h1>Third</h1>
        <select v-model="category">
            <option value="Accessories">Accessories</option>
            <option value="Laptop">Laptop</option>
            <option value="Stationary">Stationary</option>
        </select> 
      <div>
        <input type="checkbox" name="test" id="test" v-model="city"  value='Roma' /> 
        <label for="test"> Roma</label>
      </div>
      <div v-if="error"> {{error}} </div>
      <div v-if="products.length">
          <div v-for="product in filterProducts" :key='product.price'>
            <h3>{{product.name }}</h3>
            <h4>{{product.category}}</h4>
          </div>
      </div>
    <div v-else> Loading...</div>
  </div>
</template>


<script>
import { computed, ref} from 'vue'
import getProducts from '../composables/getProducts'

export default {
    components: {} ,
    setup(){ 

      const category = ref('')
      const city = ref('')
      const{products,error,load} = getProducts()

      load()

        function filterProductsByCategory (products){
              return products.filter(product => !product.category.indexOf(category.value))
          }
         function filterProductsByCity (products) {
               return products.filter(product => city.value.indexOf(product.city) !== -1)
            }

       const filterProducts = computed (()=> {  
        return filterProductsByCity.value(filterProductsByCategory.value(products.value))**
    }) 

      return {products, error, category, city, filterProducts, filterProductsByCity, filterProductsByCategory}
    }
}
</script>

<style>

</style>

I believe the issue lies within the filter functions. I have made necessary changes to convert from the Options API to the Composition API, but further assistance is required to resolve the problems in the filtering logic.

If someone could provide guidance on this matter, I can easily convert the entire codebase to utilize the Composition API seamlessly.

Answer №1

data

  1. Swap out each data property with a ref. Update their references within setup() when transitioning from methods later on.

  2. Utilize onMounted to execute load() upon the component being mounted.

import { ref, onMounted } from 'vue'
import getProducts from '@/composables/getProducts'

export default {
  setup() {
    const { products, load } = getProducts()
    const category = ref('')
    const city = ref([])
    const name = ref('')
    const range = ref('10000')

    onMounted(() => load()) // load products

    return {
      // properties required by the template should be returned here
      products,
      category,
      city,
      name,
      range,
    }
  }
}

methods

  1. Define the methods as function instances inside setup().
  2. For all data properties now represented as refs, update their code references to extract the value of the ref (e.g., this.name becomes name.value, etc.).
export default {
  setup() {
    //...
    const filterProductsByCategory = (products) => {
      return category.value
        ? products.filter((product) => !product.category.indexOf(category.value))
        : products
    }

    const filterProductsByName = (products) => {
      return name.value
        ? products.filter((product) => !product.name.toLowerCase().indexOf(name.value.toLowerCase()))
        : products
    }

    const filterProductsByCity = (products) => {
      return city.value && city.value.length
        ? products.filter((product) => city.value.indexOf(product.city) !== -1)
        : products
    }

    const filterProductsByRange = (products) => {
      return range.value
        ? products.filter((product) => product.price >= 0 && product.price <= range.value)
        : products
    }

    const sorting = () => products.value.sort((a, b) => (a.price > b.price ? 1 : -1))
    const sorting2 = () => products.value.sort((a, b) => (a.price < b.price ? 1 : -1))

    const uniqueCheck = (e) => {
      city.value = []
      if (e.target.checked) {
        city.value.push(e.target.value)
      }
    }

    const resetOptions = () => {
      category.value = ''
      city.value = ''
      name.value = ''
      range.value = '1000'
    }

    return {
      //...

      // methods needed by the template should be returned here
      sorting,
      sorting2,
      uniqueCheck,
      resetOptions,
    }
  }
}

computed

  1. Employ computed to create a computed property that references the function instances and refs generated earlier in the setup().
import { computed } from 'vue'

export default {
  setup() {
    //...
    const filterProducts = computed(() => {
      return filterProductsByName(
        filterProductsByRange(
          filterProductsByCity(filterProductsByCategory(products.value))
        )
      )
    })

    return {
      //...
      filterProducts,
    }
  }
}

demo

Answer №2

<script setup> In the composition API, creating a version for code can be achieved in the following manner.

<script setup>
     import DemoComponent from "./Demo.vue"
     import { onMounted, ref, computed } from "vue";

     const demoVar = ref(""); //data property of options api
     const demoComputed = computed(()=>{
         return {};
     });  // computed property 

     //Methods declaration options
     //1st
     function demo(){
        //logic
     }

     //2nd
     const demoTwo = function(){};

     //lifecycle hook
     onMounted(() => {})
</script> 

To learn more about declaring methods and their applications, please refer to this Proper use of const for defining functions link.

No need to explicitly return const and function at the end of the script with script setup, as everything is directly accessible to the template including components.

<script setup> enhances readability and prevents errors that may arise from missed declarations in the return statement. For more information, visit script setup

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Using Meteor package to efficiently import JSON arrays into mongoDB

I am currently working on developing a meteor package that will allow users to import JSON files into collections in a mongoDB. However, I'm uncertain about the feasibility of this task. The idea is for the user to upload a JSON file and specify the ...

Display a hidden div on every page of the website after clicking a designated <a> hyperlink for the first time

I came across a question like mine on Stack Overflow. The solution provided in the link is for clicking any div in the HTML, but I need something different. I want to click on the first div to display the second div, and then click on the second div to dis ...

Is there a way to reduce the size or simplify the data in similar JSON objects using Node.js and NPM packages?

Is it possible to serialize objects of the type "ANY_TYPE" using standard Node tools.js or NPM modules? There may be multiple objects with this property. Original Json - https://pastebin.com/NL7A8amD Serialized Json - https://pastebin.com/AScG7g1R Thank ...

The value returned by $route.query.page is incorrect, or there may be an issue with type casting in the v-if condition

Currently, I am working on a posts page and the main focus is on pagination. I have developed a pagination component that has the following structure: <template> <nav aria-label="Pagination"> <ul class="pagination justify-cont ...

What is the best way to assign the variable using Firebase data?

Having trouble setting a variable with data retrieved using a get() function on Firebase. export default { name: "customer-navigation", mixins: [navigationMixin], components:{AppSnackBar, authModule,AuthForm}, data () { return { drawer: fa ...

Object.assign changes the original array object in place

I am facing a challenge while attempting to modify the value of a specific index in my state, specifically the property post_comments. The issue lies in the fact that even though I am only modifying a copy of the state, the actual state is also being alter ...

Creating an innovative Angular2 / Electron hybrid app that seamlessly integrates Electron API functionalities directly into the Angular2 TypeScript

I followed a tutorial on setting up an Angular2 / Electron app, which you can watch here: https://www.youtube.com/watch?v=pLPCuFFeKOU. The code base for my project is based on this repository: https://github.com/rajayogan/angular2-desktop Currently, I&ap ...

Having trouble with Vue 3 Component State not updating following an asynchronous operation?

Encountering challenges in my Vue 3 app when trying to update a component's state post an asynchronous operation. Here's what's happening: Within a component, there is a method called containerMoveHere that utilizes Socket.io for an async o ...

An obstacle with VueUse's useVirtualList

Whenever I attempt to initialize the virtual list, a perplexing error message appears. Here is the message that has me puzzled: Argument of type 'AppItem[] | undefined' is not assignable to parameter of type 'MaybeRef<AppItem[]>'. ...

"Using Mongoose to push objects into an array in a Node.js environment

I am faced with a peculiar issue involving an array of images in my code. Here is the structure: var newImageParams = [ { image_string: "hello", _type: "NORMAL" }, { image_string: "hello", _type: "NORMAL" } ] M ...

The strict-origin-when-cross-origin policy is enforced when submitting a POST request through a form to a specific route

Currently, I am diving into the world of Node.js, express, and MongoDB by reading Greg Lims's book. However, I've hit a roadblock when trying to utilize a form to submit data to a route that should then output the body.title of the form in the co ...

Having trouble getting dayjs to work in the browser with Vue.js?

Trying to display date differences in a human-readable format using the guide found here: I'm attempting to incorporate DayJS as a component in my VueJS application like this: <script src="{{ asset('/vendor/vuejs/vue.js') }}" t ...

Is there a method to hide an HTML form completely?

Is there a way to quickly hide an HTML form from a webpage once the submit button is clicked and replace it with the result of a .php file in the most efficient manner possible, with minimal code? ...

Executing function with ng-click in AngularJS only on the second click of the button

Currently, I am studying AngularJS and working on an exercise that involves using the ng-click function. Strangely, I am only able to view the result after clicking upload for the second time. I am trying to display my json content but it's not workin ...

Is it feasible to verify for vacant dates with a single click?

Is there a way to determine if a date value is empty, and if it is, display a popup indicating so? After some research, I stumbled upon a similar issue where the date value was always filled with a default "mm/dd/yyyy" value. The solution provided involv ...

A single pre-task to handle multiple tasks within the package.json file

Currently, I am implementing Terraform for a specific project and I have been assigned two tasks within my package.json file. These tasks involve executing the commands terraform plan and terraform apply. "scripts": { "tf:apply": "terraform apply", ...

Is it possible that ngChange does not trigger when the model is updated through code?

According to the documentation, the ngChange directive will not trigger if the model is updated programmatically rather than through a change in the input value. Does this imply that once you programmatically modify the model, you are unable to utilize ng ...

How can I add header and footer elements in dot.js template engine?

My understanding was that all I needed to do (as per the documentation on GitHub) was to insert {{#def.loadfile('/snippet.txt')}} into my template like this: <!DOCTYPE html> <html> <head> <meta charset=&a ...

Instant Pay Now Option for Your WordPress Website with PayFast Integration

I have encountered an interesting challenge that I would like some guidance on. My goal is to integrate a PayFast "Pay Now" button into a Wordpress.com blog, specifically within a sidebar text widget. The tricky part is that I need the customer to input th ...

Next.js Refresh Screen

Is there a way to refresh my client's web page from the server at a specific time? I need the client's page to be refreshed at 12pm, and although I'm using a scheduler, it doesn't seem to be automatically refreshing the web page on the ...