How to refer to Vue when using provide - javascript

I've been trying to pass down a value via a parent component to a child component without using props. I'm using provide for this on a parent component (not the highest parent). The value im passing down will be dynamically updated, so after reading the vue docs I have to do something like: Vue.computed(() => this.todos.length) but it throws an error because Vue is undefined. I seem to be unable to import Vue like this import Vue from 'vue' (or something similar). How can I make this work? To be honest, even when I try to pass down a static variable I get undefined in the (direct) child component, even when I use the exact same code as in the vue docs.
So I have 2 questions:
how to refer/import the Vue instance?
Is it possible to use provide on a direct parent component (which is not the root)?
I'm using Vue3

You can get the instance using getCurrentInstance but it's not needed for what you want to do:
import { getCurrentInstance } from 'vue';
setup() {
const internalInstance = getCurrentInstance();
}
You can provide a computed from any component. Import provide and computed in the parent and use them like:
Parent
import { provide, computed } from 'vue';
setup() {
...
const total = computed(() => x.value + y.value); // Some computed
provide('total', total);
}
Import inject in the child:
Child
import { inject } from 'vue';
setup() {
...
const total = inject('total');
}
Here's a demo (with just slightly different import syntax because the CDN doesn't use actual imports):
const { createApp, ref, computed, provide, inject } = Vue;
const app = createApp({});
// PARENT
app.component('parent', {
template: `
<div class="parent">
<child></child>
<button #click="x++">Increment</button> (from Parent)
</div>
`,
setup() {
const x = ref(5);
const y = ref(10);
const total = computed(() => x.value + y.value);
provide('total', total);
return {
x
}
}
});
// CHILD
app.component('child', {
template: `
<div class="child">
Total: {{ total }}
</div>
`,
setup() {
const total = inject('total');
return {
total
}
}
});
app.mount("#app");
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
}
.parent, .child { padding: 24px; }
.parent {
background: #dddddd;
}
.child {
margin: 6px 0;
background: #ddeeff;
}
<div id="app">
<parent></parent>
</div>
<script src="https://unpkg.com/vue#next"></script>

Related

How to get _value properties in Vue 3 ref

Hi I'm trying to get values from the _value property in Vue to no avail. How am I able to grab these values like accessKey, align, ariaAtomic etc. I'm trying to get the clientWidth.
<div id="hello" ref="theRange" class="border-red-200 border-2 w-[80px]">
<h1>Test</h1>
</div>
const theRange = ref(null);
console.log(theRange.value.clientWidth); // Throws error
This is how you can get it
<script setup>
import { ref, onMounted } from 'vue'
const cube = ref(null)
onMounted(() => {
console.log(cube.value.clientWidth)
})
</script>
<template>
<div ref="cube">nice</div>
</template>
<style scoped>
div {
width: 200px;
height: 300px;
background-color: orange;
}
</style>
Here is an example.
Regarding Vue's lifecycle hooks, setup() doesn't have the element attached to the DOM yet, hence why you may not have anything regarding window-related properties of your object.

Vue3- [Provide and Inject] when data changes ,not do visual update ,I use filter( ) to del an item in array

I am test provide and inject method. I put datas, del-function in parent to provide, I put dynamic render in child using v-for='data' in datas...
The goal I want to implement is: when I press the "delete button"=>del-function in child, then datas in parent get an item deleted , and datas in parent provide get updated.
Then child get new datas to do visual update. v-for re-render. [!!!]
But when I press the "delete button" , datas updated ,but visually ,no one get deleted.
v-for rendered cards
// parent vue file
<template>
<Reslist/>
</template>
<script>
import Reslist from './components/ResList.vue'
export default {
name: "App",
components: {
Reslist
},
provide() {
return {
datas: this.datas,
delData: this.delData,
};
},
data() {
return {
datas: [
{
id: 1,
name: "wawa",
age: "18",
},
{
id: 2,
name: "wmmmfwa",
age: "1128",
},
],
};
},
methods: {
delData(id) {
console.log('delete-id ='+ id);
const newDatas = this.datas.filter( element => element.id !== id);
this.datas = newDatas;
console.log( this.datas);
},
},
};
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
// child vue file
<template>
<div v-for='data in datas' :key="data.name">
<h2>{{data.name}}</h2>
<p>{{data.age}}</p>
<button #click='delData(data.id)'>delete</button>
</div>
</template>
<script>
export default {
inject:['datas','delData']
}
</script>
<style scoped>
div{
width: 18.75rem;
margin: 1.25rem auto;
border: solid 1px grey;
padding: 1.25rem;
}
</style>
I know how to use prop to pass data to child. I just want to know why [provide and inject] don't work?? In [provide],I already [datas = this.datas] , does my logic have mistakes?
Good night, Bro!
I found a solution using computed props...
Hope its helpful!
Parent Vue File
<template>
<Reslist/>
</template>
<script>
import Reslist from './ResList.vue'
import { computed } from '#vue/reactivity'
export default {
name: "App",
components: {
Reslist
},
provide() {
return {
datas: computed(() => this.datas),
delData: this.delData,
};
},
data() {
return {
datas: [
{
id: 1,
name: "wawa",
age: "18",
},
{
id: 2,
name: "wmmmfwa",
age: "1128",
},
],
};
},
methods: {
delData(id) {
console.log('delete-id ='+ id);
const newDatas = this.datas.filter( element => element.id !== id);
this.datas = newDatas;
console.log(this.datas);
},
},
};
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
Child File
<template>
<div v-for='data in datas' :key="data.name">
<h2>{{data.name}}</h2>
<p>{{data.age}}</p>
<button #click='delData(data.id)'>delete</button>
</div>
</template>
<script>
export default {
inject:['datas','delData']
}
</script>
<style scoped>
div{
width: 18.75rem;
margin: 1.25rem auto;
border: solid 1px grey;
padding: 1.25rem;
}
</style>
Configuring Main.js To Accept Computed prop.
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
app.config.unwrapInjectedRef = true
app.mount('#app')
The information for this config : https://vuejs.org/guide/components/provide-inject.html#working-with-reactivity
Your injected data is not working in a reactive way, and per the Vue.js Documentation, in order for injected data to do this, you must provide it as a computed property by wrapping it in a computed() function:
Which states:
Working with Reactivity
In order to make injections reactively linked to the provider, we need to provide a computed property using the computed() function
In your case, it might look like this:
provide() {
return {
datas: computed(() => this.datas),
delData: this.delData,
};
},
Having said this, Vue is always undergoing updates, enhancements and fixes, and in order for this to work fully, temporarily, you must add an additional config to your application:
Which states:
Temporary Config Required
The above usage requires setting app.config.unwrapInjectedRef = true to make injections automatically unwrap computed refs. This will become the default behavior in Vue 3.3 and this config is introduced temporarily to avoid breakage. It will no longer be required after 3.3.
In code, this could look like so:
import { createApp } from 'vue'
import App from './App.vue'
import './index.css'
const app = createApp(App);
app.config.unwrapInjectedRef = true;
app.mount('#app')

TypeScript VueJS: Using watcher with computed property

I'm building a custom component that will allow me to use arbitrary <div>s as radio buttons. Currently my code looks like so:
<template>
<div
class="radio-div"
:class="selected ? 'selected' : ''"
#click="handleClick"
>
<slot></slot>
</div>
</template>
<style lang="scss" scoped>
.radio-div {
display: inline-block;
border-radius: 5px;
border: 1px solid #000;
padding: 5px;
margin: 5px;
}
.radio-div.selected {
box-shadow: 0 0 10px rgba(255, 255, 0, 0.5);
border: 2px solid #000;
}
</style>
<script lang="ts">
import { Vue, Component, Prop, Watch } from "vue-property-decorator";
#Component
export default class RadioDiv extends Vue {
#Prop()
val!: string;
#Prop({
required: true
})
value!: string;
selected = false;
mounted() {
this.selected = this.value === this.val;
}
#Watch("value")
onChange() {
this.selected = this.value === this.val;
}
handleClick(e: MouseEvent) {
this.$emit("input", this.val);
}
}
</script>
To utilize this I can put it in a template like so:
<template>
<div class="q-pa-md">
<q-card>
<q-card-section class="bg-secondary">
<div class="text-h6">Political Affiliation</div>
</q-card-section>
<q-separator />
<q-card-section>
<radio-div v-model="politicalParty" val="Republican">
<div class="text-h6">Republican</div>
<p>Wrong answer</p>
</radio-div>
<radio-div v-model="politicalParty" val="Democrat">
<div class="text-h6">Democrat</div>
<p>Wrong answer</p>
</radio-div>
<radio-div v-model="politicalParty" val="Independent">
<div class="text-h6">Independent</div>
<p>These people clearly know what's up</p>
</radio-div>
</q-card-section>
</q-card>
</div>
</template>
<script lang="ts">
import { Vue, Component, Watch } from "vue-property-decorator";
import RadioDiv from "../components/RadioDiv.vue";
#Component({
components: { RadioDiv }
})
export default class Profile extends Vue {
politicalParty = "Independent";
}
</script>
This works as expected. I can click on the <div>s and it switches which one is selected and updates the variable appropriately.
But now I want to tie this into a global state manager. So instead of a local politicalParty variable, I have a computed property like so:
<script lang="ts">
import { Vue, Component, Watch } from "vue-property-decorator";
import RadioDiv from "../components/RadioDiv.vue";
import globalState from "../globalState";
#Component({
components: { RadioDiv }
})
export default class Profile extends Vue {
get politicalParty() {
return globalState.politicalParty;
}
set politicalParty(val) {
globalState.politicalParty = val;
}
}
</script>
Putting a console.log statement in the setter I can see that it is getting called, and the variable is being updated. But putting a console.log statement in my value watcher (in the RadioDiv component) shows it's no longer being called now that I'm using computed properties.
What's the secret to get my RadioDiv reactive again, now that I'm using global state?
Update
The issue doesn't seem to be specific to my custom components, or to watchers. I decided to ignore this and move on while waiting for an answer from StackOverflow and ran into the issue again with Quasar's components:
<template>
...
<q-card-section>
<div class="row">
<div class="col">
<q-slider v-model="age" :min="0" :max="100" />
</div>
<div class="col">
{{ ageText }}
</div>
</div>
</q-card-section>
...
</template>
<script lang="ts">
...
get age() {
return globalState.age;
}
set age(val) {
globalState.age = val;
this.ageText = "You are " + val + " years old";
}
...
</script>
This led me to try using no custom components whatsoever:
<template>
...
<input type="text" v-model="test" />
<p>Text: {{ test }}</p>
...
</template>
<script lang="ts">
let testVal = "";
...
get test() { return testVal; }
set test(val) { testVal = val; }
...
</script>
Once again: No reactivity. When I use a computed property with v-model nothing seems to change after the call to set
If globalState were just an Object, then it would not be reactive, so computed is only going to read its value once. Same for testVal, which is just a String (also not reactive itself).
To make the test computed prop reactive to testVal, create testVal with Vue.observable():
const testVal = Vue.observable({ x: '' })
#Component
export default class Profile extends Vue {
get test() { return testVal.x }
set test(val) { testVal.x = val }
}
Similarly for globalState, exporting a Vue.observable() would allow your computed props to be reactive:
// globalState.js
export default Vue.observable({
politicalParty: ''
})

Testing DOM in Enzyme

Let's say I have a tiny component like this:
Button.js
import React from 'react';
import './Button.css';
export default class Button extends React.Component {
render() {
return (
<a href={ this.props.url } className={`button button-${ this.props.type }`}>
{ this.props.content }
</a>
);
}
}
And there's some super basic styling like this:
Button.css
.button {
color: white;
padding: 1em;
border-radius: 5px;
text-decoration: none;
}
.button-primary {
background-color: red;
}
.button-primary:hover {
background-color: darkred
}
.button-secondary {
background-color: aqua;
color: black;
}
.button-secondary:hover {
background-color: darkcyan;
color: white;
}
And let's say I want to write some tests for this:
Button.test.js
import React from 'react';
import Enzyme, {shallow, mount} from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
Enzyme.configure({adapter: new Adapter()});
import Button from './Button';
import './Button.css';
// Render buttons
const primaryButton = mount(
<Button
content="Primary button"
url="http://www.amazon.co.uk"
type="primary"
/>
);
const secondaryButton = mount(
<Button
content="Secondary button"
url="http://www.ebay.co.uk"
type="secondary"
/>
);
it('should exist', () => {
expect(primaryButton).toBeDefined();
expect(secondaryButton).toBeDefined();
});
it('should display text in the button', () => {
expect(primaryButton.text()).toEqual('Primary button');
});
it('should have the correct CSS classes', () => {
expect(primaryButton.find('.button').hasClass('button-primary')).toEqual(true);
expect(secondaryButton.find('.button').hasClass('button-secondary')).toEqual(true);
});
I've set this up using react-create-app and all the above works perfectly.
My question is: how do I test that what is getting rendered looks correct? For example, in this case I would want to make sure that the buttons have the correct background colours defined in the CSS file and that they have the correct border radius. This will prevent other developers accidentally overriding critical styling for example.
I was under the impression that Enzyme did this out of the box, but I cannot understand how to interrogate the virtual DOM which I assume is happening in the background? I thought that JSDOM was automatically running and I'm executing this from the CLI which is a Node environment.
I've tried this so far:
it('should have the correct background colours', () => {
const domNode = primaryButton.find('.button').at(0).getDOMNode();
const background = getComputedStyle(domNode).getPropertyValue('background');
expect(background).toBe('red');
});
But background is returned blank, in fact if I do console.log(getComputedStyle(domNode)) I get this returned which seems to be missing the styles:
console.log src/modules/Button/Button.test.js:42
CSSStyleDeclaration {
_values: {},
_importants: {},
_length: 0,
_onChange: [Function] }
The getDOMNode of an enzyme wrapper gets you the corresponding DOM node.
You can then use getComputedStyle to get the style of that DOM:
const renderedComponent = mount(<MyComponent /);
const domNode = renderedComponent.find('div').at(0).getDOMNode();
const background = getComputedStyle(domNode).getPropertyValue('background');
expect(background).toBe('red');

Add dynamically components in vue.js not working

I try to add dynamically component. This component set into props.
I not register components into components:{} section, because I don't now how many components and their name will be send from props.
I use ES6 syntax, where import js modules not work into ready(){} section.
My code:
let Child = Vue.extend({
data() {
return {
msg: 'CHILDREN'
}
},
template: '<div class="c-child">component {{msg}}</div>'
})
let Parent = Vue.extend({
props: {
component: ''
},
data() {
return {
msg: 'PARENT'
}
},
template: '<div class="c-parent">from component- {{msg}}<br><component :is="component"/></div>',
})
// register
Vue.component('parent-component', Parent)
// create a root instance
let vue = new Vue({
el: '#root'
})
.c-parent {
border: 1px solid red;
padding: 10px;
}
.c-child {
border: 1px solid green;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.26/vue.common.js"></script>
<div id="root">
<parent-component :component="Child"></parent-component>
</div>
Dynamic component not render:
<component :is="component"/>
P.S. This code work on jsfiddle - open link
You need to parse the interpreted component name to :is="" component, defining them the following way should work:
import GroupsGrid from './../../components/groups'
import RolesGrid from './../../components/roles'
import MainTabs from './../../components/main-tabs.vue' // <-- this component will be render other components
data: {
tabs: {
'GroupsGrid': 'groups-grid',
'RolesGrid': 'roles-grid'
}
},
And still used like this
<component :is="tabs.GroupsGrid"></component>

Categories

Resources