I define tabs as prop, then I want to use in function inside setup() as tabs.value..., but it does not recognize property. It is throwing error:
Cannot find name 'tabs', tabs is not defined
Code:
<script lang="ts">
import {
defineComponent,
ref,
computed,
PropType,
toRefs,
} from '#vue/composition-api'
import i18n from '#/setup/i18n'
export default defineComponent({
name: 'ProgramModal',
props: {
tabs: Array as PropType<Array<any>>,
},
setup() {
const changeTab = (selectedTab: { id: number }) => {
tabs.value.map((t) => {
t.id === selectedTab.id ? (t.current = true) : (t.current = false)
})
}
return {
tabs,
changeTab,
ariaLabel,
}
},
})
</script>
How can I
You need to pass props to setup function:
setup(props) {...
Related
I have a vue 3 script setup component (composition api), and another file (for printing) that has some logic I want to inject into my main component.
My main file looks like
<template>
<v-app>
<v-main>
<v-table-mobile :headers="headers" :rows="rows" :items-per-page="itemsPerPage" no-data-text="No data to display" pagination-text="{0} - {1} of {2}" :sort-by="sortBy" :group-by="groupBy">
<template #item.a="{ item }">
{{ item.a }}
</template>
</v-table-mobile>
</v-main>
</v-app>
</template>
<script lang="ts">
import * as Print from './utils/print';
export Print;
</script>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import VTableMobile, { TableHeader, TableColumnSort, TableColumnGroup } from './components/MobileTable.vue';
const sortBy = ref({key:'a', order:'asc'} as TableColumnSort);
//const sortBy = ref({} as TableColumnSort);
const groupBy = ref({key:'a', expanded:[1] as any[]} as TableColumnGroup);
//const groupBy = ref({} as TableColumnGroup);
const itemsPerPage = ref(3);
const headers = ref([
{ key: "a", title: "a" },
{ key: "b", title: "b" }
] as TableHeader[]);
const rows = ref([
{ a:1, b:2 },
{ a:3, b:4 },
{ a:1, b:6 },
{ a:7, b:8 },
{ a:9, b:10 },
{ a:11, b:12 },
{ a:13, b:14 },
{ a:15, b:16 },
{ a:17, b:18 },
{ a:19, b:20 }
]);
onMounted(() => {
setTimeout(() => {
rows.value = rows.value.concat([ { a:21, b:22 } ]);
itemsPerPage.value = 4;
}, 3000);
})
</script>
The print file looks like
import { Ref, nextTick } from 'vue';
export default {
mounted() {
alert(1);
},
beforeUnmount() {
alert(2);
},
methods: {
async Print(print_mode:Ref<boolean>) {
print_mode.value = true;
await nextTick();
window.print();
},
DisablePrintMode(print_mode:Ref<boolean>) {
print_mode.value = false;
}
}
}
I want to add the mounted, beforeMount and the 2 methods to be injected into my main component. But this is not working. How can I fix it?
Right now I get an error
Declaration or statement expected.ts(1128) where the export Print is.
Thanks
Vue composables are designed for this.
Composables | VueJS
<!-- print file -->
<script setup>
import { onMounted, onUnmounted, nextTick } from 'vue'
/*
Other logic code
*/
async function Print(print_mode) {
print_mode.value = true;
await nextTick();
window.print();
},
function DisablePrintMode(print_mode) {
print_mode.value = false;
}
onMounted(() => {
// content of this hook
})
onUnmounted(() => {
// content of this hook
})
</script>
<!-- main file -->
<script setup>
import { usePrint } from './print.js'
const printFunc = usePrint()
</script>
I have written a sample code, you can take this reference to your code
I am trying to get $refs in Vue 3 using Composition API. This is my template that has two child components and I need to get reference to one child component instance:
<template>
<comp-foo />
<comp-bar ref="table"/>
</template>
In my code I use Template Refs: ref is a special attribute, that allows us to obtain a direct reference to a specific DOM element or child component instance after it's mounted.
If I use Options API then I don't have any problems:
mounted() {
console.log("Mounted - ok");
console.log(this.$refs.table.temp());
}
However, using Composition API I get error:
setup() {
const that: any = getCurrentInstance();
onMounted(() => {
console.log("Mounted - ok");
console.log(that.$refs.table.temp());//ERROR that.$refs is undefined
});
return {};
}
Could anyone say how to do it using Composition API?
You need to create the ref const inside the setup then return it so it can be used in the html.
<template>
<div ref="table"/>
</template>
import { ref, onMounted } from 'vue';
setup() {
const table = ref(null);
onMounted(() => {
console.log(table.value);
});
return { table };
}
On Laravel Inertia:
<script setup>
import { ref, onMounted } from "vue";
// a list for testing
let items = [
{ id: 1, name: "item name 1" },
{ id: 2, name: "item name 2" },
{ id: 3, name: "item name 3" },
];
// this also works with a list of elements
let elements = ref(null);
// testing
onMounted(() => {
let all = elements.value;
let item1 = all[0];
let item2 = all[1];
let item3 = all[2];
console.log([all, item1, item2, item3]);
});
</script>
<template>
<div>
<!-- elements -->
<div v-for="(item, i) in items" ref="elements" :key="item.id">
<!-- element's content -->
<div>ID: {{ item.id }}</div>
<div>Name: {{ item.name }}</div>
</div>
</div>
</template>
<template>
<your-table ref="table"/>
...
</template>
<script>
import { ref, onMounted } from 'vue';
setup() {
const table = ref(null);
onMounted(() => {
table.value.addEventListener('click', () => console.log("Event happened"))
});
return { table };
}
</script>
Inside your other component you can interact with events you already registered on onMounted life cycle hook as with my example i've registered only one evnet
If you want, you can use getCurrentInstance() in the parent component like this code:
<template>
<MyCompo ref="table"></MyCompo>
</template>
<script>
import MyCompo from "#/components/MyCompo.vue"
import { ref, onMounted, getCurrentInstance } from 'vue'
export default {
components : {
MyCompo
},
setup(props, ctx) {
onMounted(() => {
getCurrentInstance().ctx.$refs.table.tempMethod()
});
}
}
</script>
And this is the code of child component (here I called it MyCompo):
<template>
<h1>this is MyCompo component</h1>
</template>
<script>
export default {
setup(props, ctx) {
const tempMethod = () => {
console.log("temporary method");
}
return {
tempMethod
}
},
}
</script>
Original question:
vuex shared state in chrome extension
I have the following setup in a chrome extension;
A content script that needs to write to a vuex store
A background script that initializes that store
And a popup script that renders stuff from the store (received from the content script)
store.js
import Vue from "vue";
import Vuex from "vuex";
import "es6-promise/auto";
import createMutationsSharer from "vuex-shared-mutations";
import dummyData from "./dummyData";
Vue.use(Vuex);
export default new Vuex.Store({
state: {
chromePagesState: {
allSections: [],
},
},
mutations: {
setChromePagesState(state, value) {
state.chromePagesState = value;
},
addWhiteListedItem(state, item) {
state.chromePagesState.allSections[0].itemSectionCategory[0].tasks.splice(
0,
0,
item
);
},
},
actions: {
// init from local storage
async loadChromePagesState({ commit }) {
const json = await getStorageValue("inventoryData");
commit(
"setChromePagesState",
Object.keys(json).length === 0 && json.constructor === Object
? dummyData
: JSON.parse(JSON.stringify(json))
);
},
// send message to background script to call init (shortened)
async loadChromePagesStateBrowser({ commit }) {
browser.runtime
.sendMessage({ type: "storeinit", key: "chromePagesState" })
.then(async (chromePagesState) => {
const json = await getStorageValue("inventoryData");
commit(
"setChromePagesState",
Object.keys(json).length === 0 && json.constructor === Object
? dummyData
: JSON.parse(JSON.stringify(json))
);
});
},
},
// stuff from vuex-shared-mutations
plugins: [
createMutationsSharer({
predicate: [
"addWhiteListedItem",
"loadChromePagesState",
"loadChromePagesStateBrowser",
],
}),
],
});
The content script calls store from a vue component:
index.js
import store from "../popup/firstpage/store";
new Vue({
el: overlayContainer,
store,
render: (h) => h(Overlay, { props: { isPopUp: isPopUp } }),
});
Overlay.vue
<script>
import { mapState, mapMutations } from "vuex";
export default {
props: ["isPopUp"],
data() {
return {
};
},
computed: mapState(["chromePagesState"]),
methods: {
...mapMutations(["addWhiteListedItem"]),
// this gets called in the template
addToWhiteList() {
let newItem = initNewItemWithWebPageData();
this.addWhiteListedItem(newItem);
},
},
}
</script>
The background script receives a message and calls a mutation on the store:
background.js
import store from "../content/popup/firstpage/store";
browser.runtime.onMessage.addListener((message, sender) => {
if (message.type === "storeinit") {
store.dispatch("loadChromePagesState");
return Promise.resolve(store.state[message.key]);
}
});
Upon opening popup.js, a store mutation is called that sends a message to background.js that calls another mutation in the store:
popup.js
import store from "./firstpage/store";
export function showPopup() {
const popupContainer = document.createElement("div");
new Vue({
el: popupContainer,
store,
render: (h) => h(App),
created() {
console.log("Calling store dispatch from popup");
this.$store.dispatch("loadChromePagesStateBrowser");
},
});
}
Where App.vue is
<template>
<div id="app">
<OtherComponent />
</div>
</template>
<script>
import { mapActions } from "vuex";
import OtherComponent from "./components/ChromePage.vue";
export default {
name: "App",
OtherComponent: {
VueTabsChrome,
},
methods: {
...mapActions(["loadChromePagesState"]),
},
mounted() {
// once fully mounted we load data
// this is important for a watcher in ChromePage component
console.log("App.vue mounted");
// this.loadChromePagesState();
},
};
</script>
Intuitively export default new creates a new instance on every import hence the not being in sync across scripts (since the stores are different objects).
How can the same store be initialized once and used across multiple entry points?
popup.js is opened when the user clicks the extension icon:
(in this case clicks "new tab").
I am new to Typescript with vuex. I simply want to fetch user list from the backend. Put in the store. I declared custom user type
export interface User {
id: number;
firstName: string;
lastName: string;
email: string;
}
in my vuex.d.ts file, I declare store module like:
import { Store } from "vuex";
import { User } from "./customTypes/user";
declare module "#vue/runtime-core" {
interface State {
loading: boolean;
users: Array<User>;
}
interface ComponentCustomProperties {
$store: Store<State>;
}
}
in my store I fetch the users successfully and commit the state:
import { createStore } from "vuex";
import axios from "axios";
import { User, Response } from "./customTypes/user";
export default createStore({
state: {
users: [] as User[], // Type Assertion
loading: false,
},
mutations: {
SET_LOADING(state, status) {
state.loading = status;
},
SET_USERS(state, users) {
state.users = users;
},
},
actions: {
async fetchUsers({ commit }) {
commit("SET_LOADING", true);
const users: Response = await axios.get(
"http://localhost:8000/api/get-friends"
);
commit("SET_LOADING", false);
commit("SET_USERS", users.data);
},
},
getters: {
userList: (state) => {
return state.users;
},
loadingStatus: (state) => {
return state.loading;
},
},
});
I set the getters, I sense that I don't need to set getter for just returning state however this is the only way I could reach the data in my component. Please advise if there is a better way to do it. In my component I accessed the data like:
<div class="friends">
<h1 class="header">Friends</h1>
<loading v-if="loadingStatus" />
<div v-else>
<user-card v-for="user in userList" :user="user" :key="user.id" />
<pagination />
</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from "vue";
import { mapGetters } from "vuex";
import { User } from "../store/customTypes/user";
=import UserCard from "../components/UserCard.vue";
import Loading from "../components/Loading.vue";
import Pagination from "../components/Pagination.vue";
export default defineComponent({
name: "Friends",
components: {
UserCard,
Loading,
Pagination,
},
static: {
visibleUsersPerPageCount: 10,
},
data() {
return {
users: [] as User[],
currentPage: 1,
pageCount: 0,
};
},
computed: {
...mapGetters(["loadingStatus", "userList"]),
},
mounted() {
this.$store.dispatch("fetchUsers");
this.paginate()
},
methods: {
paginate () {
// this.users = this.$store.state.users
console.log(this.$store.state.users)
console.log(this.userList)
}
}
});
</script>
Now when I get userList with getters, I successfully get the data and display in the template. However When I want to use it in the method, I can't access it when component is mounted. I need to paginate it in the methods. So I guess I need to wait until promise is resolved however I couldn't figure out how. I tried
this.$store.dispatch("fetchUsers").then((res) => console.log(res)) didn't work.
What I am doing wrong here?
An action is supposed to return a promise of undefined, it's incorrectly to use it like this.$store.dispatch("fetchUsers").then(res => ...).
The store needs to be accessed after dispatching an action:
this.$store.dispatch("fetchUsers").then(() => {
this.paginate();
});
I've tried to override VueJS component method from another component.
Here I want to override checkForm method from another VueJS file.
inside ./src/components/auth_form.vue:
<script lang="ts">
import Vue from 'vue'
export default Vue.extend({
props: {...},
methods: {
checkForm: function(e: any) {
console.log('Please override this method!')
}
}
})
</script>
I want to override this method from ./src/views/signup.vue:
<script lang="ts">
import Vue from 'vue'
import authForm from '../components/auth_form.vue'
export default Vue.extend({
name: 'signup',
components: {
authForm
},
data: function() {...},
methods: {
// This does not override the method above!
checkForm: function(e: any) {
console.log("success")
// Reset the form
this.signupForm = {} as SignupForm
}
}
})
</script>
Yes they will not override because the 2 functions are in 2 different scopes. They can have same name and works independently. In order to do what you want, you have to put the checkForm function of auth_form inside its props.
So in auth_form:
props: {
checkForm: {
type: Function,
default(e) {
console.log('Please override this method!')
}
}
}
then when calling it in signup.vue
<auth-form :check-form="checkForm" /> //pass parent's method into children props
methods: {
checkForm() {
console.log("success") // this will override the default one
}
}