3

I'm struggling to figure out how to make properties of an array reactive. Is this possible? In the example below, the filteredResults array itself is reactive, and working, but neither the resultCountRef (wrapped in reactive()) nor the resultCount fields are reactive. In otherwords, if I click the Filter for Apples button, the filteredResults changes to just the one item, but the two count fields remain at 3. Note that using {{filteredResults.length}} in the template does work as expected. Here is a working sample.

And here is the code (a Search.vue composition API component, and a useFilter composition function):

Search.vue:

<template>
  <div>resultCountRef: {{resultCountRef}}</div>
  <div>resultCount: {{resultCount}}</div>
  <div>filteredResults.length: {{filteredResults.length}}</div>
  <div>filteredResults: {{filteredResults}}</div>
  <div>filters: {{filters}}</div>
  <div><button @click="search()">Search</button></div>
  <div><button @click="applyFilter('apples')">Filter for Apples</button></div>
</template>

<script>
import { reactive } from 'vue';
import useFilters from './useFilters.js';

export default {
  setup(props) {
    const products = reactive(['apples', 'oranges', 'grapes']);
    const { filters, applyFilter, filteredResults } = useFilters(products);
    const resultCountRef = reactive(filteredResults.value.length);
    const resultCount = filteredResults.value.length;

    return {
      resultCountRef,
      resultCount,
      filteredResults,
      filters,
      applyFilter,
    };
  },
};
</script>

useFilters.js:

import { ref, onMounted } from 'vue';

function filterResults(products, filters) {
  return products.filter((product) => filters.every(
    (filter) => {
      return product.includes(filter);
    },
  ));
}

export default function useFilters(products) {
  const filters = ref([]);
  const filteredResults = ref([...products]);
  const applyFilter = (filter) => {
    filters.value.push(filter);
    filteredResults.value.splice(0);
    filteredResults.value.splice(0, 0, ...filterResults(products, filters.value));
  };

  return { filters, applyFilter, filteredResults };
}

1 Answer 1

3

UPDATED

const resultCountRef = reactive(filteredResults.value.length);

The reason why reactive is not working is because filteredResults.value.length returned a simple number and not referencing to anything.

From @JimCopper 's comment:

The returned object is simply providing an observable object that wraps the object/value/array passed in (in my case the filteredResults.length value). That returned object resultCountRef is the object that is tracked and can be then modified, but that's useless in my case and it's why reactive didn't work. )


As the resultCount depends on filteredResults, you can use computed to monitor the change

Here is the modified playground

setup(props) {
    const products = reactive(['apples', 'oranges', 'grapes']);
    const { filters, applyFilter, filteredResults } = useFilters(products);
    // const resultCountRef = reactive(filteredResults.length); 
    const resultCount = computed(() => filteredResults.length) // use computed to react to filteredResults changes

    return {
      // resultCountRef,
      resultCount,
      filteredResults,
      filters,
      applyFilter,
    };
},


The new doc does not have a very nice explanation on computed so i just quote it from the old doc explanation

A computed property is used to declaratively describe a value that depends on other values. When you data-bind to a computed property inside the template, Vue knows when to update the DOM when any of the values depended upon by the computed property has changed.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.