2

I have these files

App.vue, Header.vue, search.js and Search.vue

App.vue is normal and just adding different views

Header.vue has an input box

<input type="text" v-model="searchPin" @keyup="searchResults" />
<div>{{searchPin}}</div>

and script:

import useSearch from "@/compositions/search";

export default {
  name: "Header",
  setup() {
    const { searchPin, searchResults } = useSearch();

    return {
      searchPin,
      searchResults
    };
  }
};

search.js has the reusable code

import { ref } from "vue";

export default function useSearch() {
  const searchPin = ref("");

  function searchResults() {
    return searchPin.value;
  }

  return {
    searchPin,
    searchResults
  };
}

Now, this is working well.. once you add something on the input box, it is showing in the div below.

The thing I have not understood is how to use this code to a third component like Search.vue.

I have this, but its not working.

<template>
  <div>
    <h1 class="mt-3">Search</h1>
    <div>{{ searchPin }}</div>
  </div>
</template>

<script>
  import useSearch from "@/compositions/search";

  export default {
    name: "Search",
    setup() {
      const { searchPin, searchResults } = useSearch();

      return {
        searchPin,
        searchResults
      };
    }
  };
</script>

What am I missing? Thanks.

4
  • What do you mean, "not working"? Can you elaborate on what you're expecting vs what you're seeing? Commented Sep 1, 2020 at 16:32
  • I am trying to show what the user inputs in the input field but in the Search.vue file. I have a fixed header with search box on all pages and whenever user inputs, it will search and show the results in the search.vue component. So, I am trying to get it to the basic of showing the user input. Currently, its coming blank. With Vue 2 we could use event bus but not sure what to do with composition api. Commented Sep 2, 2020 at 9:19
  • If I understand correctly, you want the data to be persistent across the entire app, correct? Commented Sep 2, 2020 at 16:13
  • Yes, I just want to search on the header and show the results on the search.vue component. Commented Sep 3, 2020 at 5:53

2 Answers 2

7

The fix for this is very simple

instead of

import { ref } from "vue";

export default function useSearch() {
  const searchPin = ref("");

  function searchResults() {
    return searchPin.value;
  }

  return {
    searchPin,
    searchResults
  };
}

use

import { ref } from "vue";

const searchPin = ref("");

export default function useSearch() {  

  function searchResults() {
    return searchPin.value;
  }

  return {
    searchPin,
    searchResults
  };
}

The problem is that the searchPin is scoped to the function, so every time you call the function, it gets a new ref. This is a desirable effect in some cases, but in your case, you'll need to take it out.

Here is an example that uses both, hope it clears it up.

const {
  defineComponent,
  createApp,
  ref
} = Vue


const searchPin = ref("");

function useSearch() {
    const searchPinLoc = ref("");

  function searchResults() {
    return searchPin.value + "|" + searchPinLoc.value;
  }

  return {
    searchPin,
    searchPinLoc,
    searchResults
  };
}

const HeaderComponent = defineComponent({
  template: document.getElementById("Header").innerHTML,
  setup() {
    return useSearch();
  },
})


const SearchComponent = defineComponent({
  template: document.getElementById("Search").innerHTML,
  setup() {
    return useSearch();
  }
})

createApp({
  el: '#app',
  components: {
    HeaderComponent, SearchComponent
  },
  setup() {}
}).mount('#app')
<script src="https://unpkg.com/[email protected]/dist/vue.global.js"></script>
<div id="app">
  <header-component></header-component>
  <search-component></search-component>
</div>

<template id="Header">
  searchPin : <input type="text" v-model="searchPin" @keyup="searchResults" />
  searchPinLoc : <input type="text" v-model="searchPinLoc" @keyup="searchResults" />
  <div>both: {{searchResults()}}</div>
</template>

<template id="Search">
  <div>
    <h1 class="mt-3">Search</h1>
    <div>both: {{searchResults()}}</div>
  </div>
</template>

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

Comments

1

Adding flavor to @Daniel 's answer. This is exactly what I'm struggling with regarding to best practices ATM and came to some conclusions:

Pulling the Ref outside of the composition fn would fix your problem but if you think about it, it's like sharing a single instance of a data property used in multiple places. You should be very careful with this, since ref is mutable for whoever pulls it, and will easily break unidirectional data flow. For e.g. sharing a single Ref instance between a parent component and a child components can be compared to passing it down from parent's data to child's props, and as I assume we all know we should avoid mutating props directly

So classical answer for your question would be, move it to Vuex state and read it from there.

But if you have a small application, don't want a state manager, or simply want to take full advantage of the composition API, then my suggestion would be to at least do something of this pattern

import { ref, computed } from "vue";

const _searchPin = ref(""); // Mutable persistant prop

const searchPin = computed(() => _searchPin.value); // Readonly computed prop to expose

export default function useSearch() {  

  function searchResults() {
    return searchPin.value;
  }

  return {
    searchPin,
    searchResults
  };
}

Not more than ONE component should mutate the persistent Ref while others could only listen to the computed one.

If you find that more than one component needs access to change the ref, then that's probably a sign you should find another way to implement this (Vuex, props and events, etc...)

As I said, I am still trying to make sense of this myself and am not sure this is a good enough pattern either, but it's definitely better then simply exposing the instance.

Another option for code arrangement would be to encapsulate in 2 different access hooks

import { ref, readonly } from "vue";

const searchPin = ref(""); // Mutable persistant prop

export const useSearchSharedLogic() {
  return readonly({
    searchPin
  })
}

const useSearchWriteLogic() {
  return {
    searchPin
  }
}

// ----------- In another file -----------

export default function useSearch() {  

  const { searchPin } = useSearchSharedLogic()

  function searchResults() {
    return searchPin.value;
  }

  return {
    searchPin,
    searchResults
  };
}

Or something of this sort (Not even sure this would work correctly as written). Point is, don't expose a single instance directly

Another point worth mentioning is that this answer takes measure to preserve unidirectional data flow pattern. Although this is a basic proven pattern for years, it's not carved in stone. As composition patterns get clearer in the close time, IMO we might see people trying to challenge this concept and returning in some sense to bidirectional pattern like in Angular 1, which at the time caused many problems and wasn't implemented well

1 Comment

Welcome ! Please use some JS code highlight tags for an easier lecture of your answer: stackoverflow.com/editing-help#syntax-highlighting

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.