Vuejs 3 props are Proxy - vue.js

I am passing array as a prop to another component, and when I want to read this on mounted in that component, I got Proxy {}. How to read data from this prop? You can see in example when I want to console log prop, result is Proxy {}. I can see all values in HTML structure, but not in the console on mounted.
<template>
<div class="custom-select-container">
<div class="selected-item" #click="openSelect">
<span class="selected-items-text">{{ selectedItem.name }}</span>
<span class="icon-arrow1_b selected-items-icon" :class="{ active: showOptions }" />
</div>
<transition name="fade">
<ul v-show="options.length && showOptions" class="custom-select-options">
<li v-for="(option, index) in options" :key="index" class="custom-select-item">{{ option.name }}</li>
</ul>
</transition>
</div>
</template>
<script>
import { ref, onMounted } from 'vue'
export default {
props: {
options: {
type: Array,
default: () => []
}
},
setup(props) {
let showOptions = ref(false);
let selectedItem = ref(props.options[0])
const openSelect = () => {
showOptions.value = !showOptions.value
}
onMounted(() => {
console.log('test', props.options)
})
return {
openSelect,
showOptions,
selectedItem
}
}
}
</script>
Parent component where I am passing data:
<template>
<div class="page-container">
<div>
<div class="items-title">
<h3>List of categories</h3>
<span>({{ allCategories.length }})</span>
</div>
<div class="items-container">
<div class="item" v-for="(category, index) in allCategories" :key="index">
<span class="item-cell size-xs">{{ index + 1 }}.</span>
<span class="item-cell size-l">{{ category.name }}</span>
</div>
</div>
</div>
<custom-select
:options="allCategories"
/>
</div>
</template>
<script>
import CustomSelect from '../components/Input/CustomSelect'
import { computed } from 'vue'
import { useStore } from 'vuex'
export default {
components: {
CustomSelect
},
computed: {
},
setup() {
const store = useStore()
const allCategories = computed(() => store.getters['categories/getAllCategories'])
return {
allCategories
}
}
}
</script>

That's how reactivity works in Vue3.
use
console.log(JSON.parse(JSON.stringify(data))
or
console.log(JSON.stringify(data, null, 2))
to show the content of proxies in console

Related

Vue.js Toggle vuecomponents

I have a very simple Vue application I am building. I am new with using Vue.js. I have 2 vue components and I am trying to toggle between them with a button click. Can I use script code on the main page to toggle between the 2 vue components?
Here is my code. I assume I have to use v-show or v-if on the components or a div/span around the components to show and hide them.
App.vue
<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js App"/>
<Display v-if="show" />
<button v-on:click="toggleDisplay()">toggle btn</button>
</div>
</template>
<script>
import HelloWorld from './components/HelloWorld.vue'
import Display from './components/Display.vue'
export default {
name: 'App',
components: {
HelloWorld,
Display
},
data: {
return {
selectedComp: "HelloWorld"
}
},
methods: {
toggleDisplay(comp: string) {
this.selectedComp = comp;
}
}
}
</script>
Display.vue
<template>
<div class="post">
<div v-if="loading" class="loading">
Loading...
</div>
<div class="titleBlock text-center">
<div class="row justify-content-center" style="width:100%;">
<div class="col-6">
Service Title for Page
</div>
</div>
</div>
<div v-if="byteArray" class="content">
<img :src="byteArray" />
<div>{{ createImage }}</div>
<p>{{ byteArray }}</p>
</div>
</div>
</template>
<script lang="js">
import Vue from 'vue';
export default Vue.extend({
data() {
return {
loading: false,
post: null,
byteArray: null
};
},
created() {
this.fetchByteArray();
// Hide DisplayVue after 5 seconds
//setTimeout(() => this.byteArray = false, 5000)
//.then(
// setTimeout(() => this.byteArray = true, 2000)
// );
setTimeout(() => this.img = false, 5000)
.then(
setTimeout(() => this.img = true, 2000)
);
},
//watch: {
//}
methods: {
fetchByteArray() {
this.post = true;
this.byteArray = true;
this.loading = null;
fetch('https://localhost:5001/api/Doc/image001.png')
.then(response => response.json())
.then(bytes => {
this.byteArray = "data:image/png;base64," + bytes;
this.loading = false;
return;
})
.catch(console.log("Error"));
}
},
computed: {
createImage() {
return `<img class="img-fluid" src="data:image/png;base64,` + this.byteArray + `" />`;
}
}
});
</script>
Give a try to that one: https://vuejs.org/guide/essentials/component-basics.html#dynamic-components
Mainly like here: https://stackoverflow.com/a/73029625/8816585
<script setup>
import { ref } from "vue"
import Hehe from "#/components/Hehe.vue"
import Nice from "#/components/Nice.vue"
const boolean = ref(true)
</script>
<template>
<component :is="boolean ? Hehe : Nice" />
<button #click="boolean = !boolean">toggle</button>
</template>

How do I use the mounted function in VueJs 3 composition api?

I am trying out the composition api in vueJs 3. Unfortunately I don't know how to include my mounted code in the setup(). When I put the code into the setup, the javascript tries to access the not yet rendered DOM. Can anyone tell me how I have to rewrite this?
Option api sytle (works)
<script>
export default {
name: "NavBar",
data() {
return {};
},
methods: {},
mounted() {
const bm = document.querySelector('#toggle');
const menu = document.querySelectorAll('nav ul li');
const overlay = document.querySelector('#overlay');
// ...
bm.addEventListener('click', () => {
bm.classList.toggle("active");
overlay.classList.toggle("open");
})
},
}
</script>
<template>
<header>
<div class="button_container" id="toggle">
<span class="top"></span>
<span class="middle"></span>
<span class="bottom"></span>
</div>
<div class="overlay" id="overlay">
<nav class="overlay-menu">
<ul>
<li>Home</li>
<!-- ... -->
</ul>
</nav>
</div>
</header>
</template>
First of all, this not a good practice to get a html element via document.querySelector(), see how to bind events in vue3
// bad
<div class="button_container" id="toggle">...</div>
mounted() {
const bm = document.querySelector('#toggle');
bm.addEventListener('click', () => {
bm.classList.toggle("active");
overlay.classList.toggle("open");
})
}
// good
<div class="button_container" id="toggle" #click="toggleContainer">...</div>
methods: {
toggleContainer() {
...
}
}
If you really want to document.querySelector() in setup(), you can use onMounted
import { onMounted } from 'vue';
export default {
setup() {
onMounted(() => {
const bm = document.querySelector('#toggle');
bm.addEventListener('click', () => {
bm.classList.toggle("active");
overlay.classList.toggle("open");
})
});
}
}
// or inside <script setup>
import { onMounted } from 'vue';
onMounted(() => {
....
});
But, just as #kissu commented, ref is a better way if you have to handle the html tag directly in vue
<div
ref="toggler">
...
</div>
<script setup>
import { ref, onMounted } from 'vue';
const toggler = ref(null);
onMounted(() => {
console.log(toggler.value) // <div></div>
});
</script>
But none of the above follow the concept of Vue, which is Vue is driven by data.
Since you seem to create a click event listener which will effect the class in html, here is the Vue way:
<template>
<header>
<div
class="button_container"
id="toggle"
:class="{
'active': isActiveBm
}"
#click="toggle">
<span class="top"></span>
<span class="middle"></span>
<span class="bottom"></span>
</div>
<div
class="overlay"
id="overlay"
:class="{
'open': isOpenOverlay
}">
<nav class="overlay-menu">
<ul>
<li>Home</li>
<!-- ... -->
</ul>
</nav>
</div>
</header>
</template>
<script setup>
import { ref } from 'vue';
const isActiveBm = ref(false);
const isOpenOverlay = ref(false);
const toggle = () => {
isActiveBm.value = !isActiveBm.value;
isOpenOverlay.value = !isOpenOverlay.value;
}
</script>

can't display vuejs data property inside vue template

I'm trying to use eventbus to send data from component A:
<template>
<div v-for="(user, index) in users" :key="index" class="col-lg-6">
<div class="card card-primary card-outline">
<div class="card-body d-flex">
<h1 class="mr-auto">{{ user.name }}</h1>
Afficher
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
users: {},
}
},
methods: {
envoyerDetails($data){
Fire.$emit('envoyer_details_projet', $data);
this.$router.push('details-projet');
},
loadUser() {
if(this.$gate.isAdmin()){
axios.get("api/user").then(({ data }) => (this.users = data.data));
}
}
},
mounted() {
this.loadUser()
}
}
</script>
In component B, i receive the data and i want to display it inside the template this way:
<template>
<div class="right_col text-center" role="main">
<h5><b>name: {{ user.name }}</b> </h5>
</div>
</template>
export default {
data() {
return {
user: {},
}
},
methods: {
afficherDetails (args) {
this.user = args;
console.log(this.user.name);
}
},
mounted() {
Fire.$on('envoyer_details_projet', this.afficheDetails);
}
}
The data is not displayed in the template but it is displayed in the console. What am i missing?
Maybe when you emit the event envoyer_details_projet in component A, but component B is not mounted yet so that it can't receive the data.

Vue JS Pass Data From Parent To Child Of Child Of Child

In Vue.js, how to correctly pass data from parent component to a chain of multi level child components?
You have a few options:
Props
Event Bus
Vuex
Provide/Inject
Find out more here: https://www.smashingmagazine.com/2020/01/data-components-vue-js/
#RoboKozo listed some really great options for you, it just depends on what you're doing with the data.
If it's static data (and you like to be modular), you can always make use of mixins.
From the Vue.js docs:
Mixins are a flexible way to distribute reusable functionalities for Vue components. A mixin object can contain any component options. When a component uses a mixin, all options in the mixin will be “mixed” into the component’s own options.
Example 1: Mixins
File/Folder Setup:
/src/mixins/defaultDataMixin.js // Call this whatever you want
/src/mixins/defaultDataMixin.js:
export const default defaultData {
data() {
return {
property1: "Foo",
property2: "Bar"
}
}
};
Now, on any component you want to have access to property1 or property2, you can just import your mixin.
/src/pages/MyComponent.vue
<template>
<div id="wrapper">
<div class="container">
<div class="row">
<div class="col">
{{ property1 }}
</div>
<div class="col">
{{ property2 }}
</div>
</div>
</div>
</div>
</template>
<script>
import { defaultData } from "#/mixins/defaultDataMixin"
export default {
mixins: [defaultData],
}
</script>
Example 2: Sharing data via Props & Emit
Props down, Emit up
Perhaps you return some data in a Parent component that you wish to pass down to be used in a child component. Props are a great way to do that!
File/Folder Setup
/src/pages/ParentComponent.vue
/src/components/ChildComponent.vue
ParentComponent.vue
<template>
<div id="wrapper">
<div class="container">
<div class="row">
<div class="col">
<child-component :defaultDataProp="defaultData" #emitted-text="helloWorld">
</child-component>
</div>
</div>
</div>
</div>
</template>
<script>
import { defaultData } from "#/mixins/defaultDataMixin"
import ChildComponent from "#/components/ChildComponent.vue"
export default {
mixins: [defaultData],
components: {
ChildComponent
},
data() {
return {
emittedText: "",
}
},
methods: {
helloWorld(e){
this.emittedText = e;
}
}
}
</script>
ChildComponent.vue
<template>
<div id="wrapper">
<div class="container">
<div class="row">
<div class="col">
{{ defaultDataProp }}
</div>
</div>
<div class="row">
<div class="col">
<button #click="emitData">Emit Data</button>
</col>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
defaultDataProp: {
type: String
}
},
data() {
return {
text: "Hello, world!"
};
},
methods: {
emitData() {
this.$emit('emitted-text', this.text);
}
}
}
</script>
Cool, right?
These are just a couple of ways you can share data and functions between components.
** Edit **
Example 3: Vuex state + mapGetters
I just noticed you tagged this state as well, so I'm assuming you have already installed and are using Vuex.
While it's not "passing" the data from parent to child directly, you could have the Parent component can mutate a state that a child component is watching.
Let's imagine you want to return the user information and then have access to that data across multiple components.
File/Folder setup:
/.env
/src/main.js
/src/store/store.js
/src/store/modules/user.js
/src/mixins/defaultDataMixin.js
/src/util/api.js
/src/pages/ParentComponent.vue
/src/components/ChildComponent.vue
main.js
import Vue from "vue";
import Vuex from "vuex";
import { store } from "#/store/store";
// ... add whatever else you need to import and use
Vue.use(vuex); // but make sure Vue uses vuex
// Vue can "inject" the store into all child components
new Vue({
el: "#app",
store: store, // <-- provide the store to the vue instance
// ..
});
.env:
VUE_APP_API_URL='http://127.0.0.1:8000'
api.js:
import axios from "axios";
import { store } from "#/store/store";
const apiClient = axios.create({
baseURL: process.env.VUE_APP_API_URL,
});
export default apiClient;
user.js:
import apiClient from "#/util/api"
import { store } from "../store"
const userModule = {
state: () => ({
user: {
firstName: "",
}
}),
mutations: {
setUser(state, payload) {
state.user = payload;
}
},
actions: {
async loadUser({ commit, dispatch }) {
try {
const user = (await apiClient.get("/user")).data;
commit("setUser", user); // mutate the vuex state
} catch (error) {
dispatch("logout");
}
},
logout({ commit }) {
commit("setUser", {});
}
},
getters: {
firstName: (state) => {
return state.user.firstName;
}
}
};
export default userModule;
store.js
import Vue from "vue";
import Vuex from "vuex";
import userModule from "#/store/modules/user"; // Grab our newly created module
Vue.use(Vuex);
export const store = new Vuex.Store({
modules: {
userState: userModule,
},
});
defaultDataMixin.js
import { mapGetters } from "vuex";
export const default defaultData {
data() {
return {
property1: "Foo",
property2: "Bar"
}
},
// We can make use of mapGetters here
computed: {
...mapGetters(["firstName"]),
}
};
Now, both Parent and Child have access to the centralized state and can mutate it. For example, let's call the loadUser() from the parent and watch for the state change on the child component.
ParentComponent.vue
<template>
<div id="wrapper">
<div class="container">
<div class="row">
<div class="col" v-if="firstName != ''">
{{ firstName }}
</div>
<div class="col">
<button #click="mutateState">Click Me</button>
</div>
</div>
<div class="row">
<div class="col">
<child-component #emitted-text="helloWorld">
</child-component>
</div>
</div>
</div>
</div>
</template>
<script>
import { defaultData } from "#/mixins/defaultDataMixin"
import ChildComponent from "#/components/ChildComponent.vue"
export default {
mixins: [defaultData],
components: {
ChildComponent
},
methods: {
// our new method to mutate the user state
mutateState() {
this.$store.dispatch("loadUser");
},
helloWorld(e){
this.emittedText = e;
}
}
}
</script>
ChildComponent.vue
<template>
<div id="wrapper">
<div class="container">
<div class="row">
<div class="col">
{{ defaultData }}
</div>
</div>
<div class="row">
<div class="col">
<button #click="emitData">Emit Data</button>
</col>
</div>
</div>
</div>
</template>
<script>
import { defaultData } from "#/mixins/defaultDataMixin"
export default {
mixins: [defaultData],
data() {
return {
text: "Hello, world!"
};
},
methods: {
emitData() {
this.$emit('emitted-text', this.text);
}
},
// Let's watch for the user state to change
watch: {
firstName(newValue, oldValue) {
console.log('[state] firstName state changed to: ', newValue);
// Do whatever you want with that information
}
}
}
</script>

Conditional rendering template on Vue?

I have a simple situation that I want to render cart-badge based on window width value but it always show the last one even though windowWidth does change the value. Please advise me what to do !
<template v-if="windowWidth>=1024">
<div class="nav-user-account"> {{windowWidth}}
<div class="nav-cart nav-cart-box">
<span class="text hidden-sm">Cart</span>
<span class="cart-number" id="nav-cart-num">{{cartItemCount}}</span>
</div>
</div>
</template>
<template v-if="windowWidth<1024">
<a href="#" style="color:#ff6a00" class="icon-cart">
{{windowWidth}}
<i class="fa fa-shopping-cart fa-2x" aria-hidden="true"></i>
<span class="cart-num">2</span>
</a>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
data() {
return {
windowWidth: window.innerWidth,
}
},
computed: {
...mapGetters('cart', {
cartItemCount: 'getShoppingCartItemsCount'
})
},
mounted() {
this.$nextTick(() => {
window.addEventListener('resize', () => {
this.windowWidth= window.innerWidth
});
})
},
}
</script>
UPDATED: as Tony19 pointed out, i need a master template outside of these 2 template.
<template>
<div>
<template v-if="windowWidth>=1024">...</template>
<template v-else></template>
</div>
</template>