0

So I am building a search page in Vue 3 using the composition API. I have a component which takes a set of data from the parent and shows a snippet of the data including where the keyword is, and so it needs to make a working copy of this data to make a displayable value.

I had a lot of weird errors because I was initially using the following code, thinking it would just get the value:

setup(props) {

          const displayEntry = ref(props.entry)

...

but this ended up being reactive and changing the original data. I don't need the reactivity because I create the component dynamically from the parent. I also don't want to keep a working copy of the data in the parent because that will increase code complexity. I then tried a myriad of different solutions to try to break the reactivity until I got to simply:

displayEntry.value = props.entry

At which point my linter goes bananas...

error    Getting a value from the `props` in root scope of `setup()` will cause the value to lose reactivity  vue/no-setup-props-destructure

So what is the correct way to just get the value from a prop?

Juckix
  • 35
  • 1
  • 2
  • 7
  • What do you mean by “changing the original data”? This feels like an XY problem: if you’re receiving props in a child component, the child component should not mutate the prop. You should still use `ref()` so that whenever the parent data mutates the child component’s prop will be updated accordingly. – Terry Mar 27 '21 at 17:11
  • Oh I guess I’m passing reactive data into the prop, but I’m not really sure why it’s reactive since I do entries.value.filter() – Juckix Mar 27 '21 at 17:20
  • Hello, I believe you need to use the toRefs helper function like so: `const { entry } = toRefs(props)` https://v3.vuejs.org/guide/composition-api-setup.html#props – user1870482 Jul 01 '21 at 20:19

2 Answers2

0

Turns out that I was somehow passing a reference in the parent. Following is my code:

setup(props) {
  watchEffect(() => {
      if (searchTerm.value == "") {
        filteredEntries.value = []
      } else {
        filteredEntries.value = entries.value.filter(searchFilter)
      }
    })

  return {
      searchTerm, filteredEntries, echo, showPreview
    }
}

And in the template:

<SearchPreview
    v-for="( entry, index ) in filteredEntries"
    :key="index"
    :entry="entry"
    :search-term="searchTerm"
  />

No clue why it passes a ref and I'm not sure how to just pass the value, but I fixed it in the component with the following hack:

const displayEntry = ref(JSON.parse(JSON.stringify(props.entry)))

(this leaves some nested properties undefined but I pass those separately to make it work)

Juckix
  • 35
  • 1
  • 2
  • 7
0

You never initialized fileredEntries as a ref in the code example you showed. How are you assigning any values there? If you're using a data field on your component then you should move that to the setup function. Don't mix composition and options api, it doesn't work well together.

Also you used a (for ... in) loop to iterate over your filteredEntries. For an Array you should be using a (for ... of) loop. (see What is the difference between ( for... in ) and ( for... of ) statements?)

Here's an example of what it should like without any hacks.

<template>
  <div>
    <SearchPreview
      v-for="( entry, index ) of filteredEntries"
      :key="index"
      :entry="entry"
      :search-term="searchTerm"
    />
  </div>
</template>
<script>

const MyComponent = defineComponent({
setup(props) {
  const entries = ref(['John', 'Jane']);
  const searchTerm = ref('');
  const filteredEntries = ref([]);
  const searchFilter = (item) => {
    // do some filtering
  };

  watchEffect(() => {
      if (searchTerm.value == "") {
        filteredEntries.value = []
      } else {
        filteredEntries.value = entries.value.filter(searchFilter)
      }
    });

  return {
      searchTerm, filteredEntries
    };
  }
})
Braks
  • 561
  • 2
  • 9