Accessing Vue Component Data with $ref.
This article assumes you have basic knowledge of Vue & Vue’s reactive system.
Emergencies are a given, and sometimes we get one of those when building frontend applications, thankfully most of these frontend frameworks provide us with multiple ways to handle emergencies. With Vue, one of the many emergency hatches is the $ref
attribute.
Generally, it’s common to have a ref attribute on pure HTML elements (like the <input />
element) in Vue, in the same way, you can have a reference on a custom component as well (<my-custom-componet />
), and have access to its computed values, methods, data properties, and so on. This, however, should only be used in emergency situations or as a last resort.
Accessing Data — The General Approach
Say we have two child components (Component A & Component B) within a parent component and we would for some reason need to display some data from Component A in B and vice-versa. see rough sketch below:
The general and recommended approach would be to emit the data from A, add a listener/handler in the parent component, then pass the value into Component B via props. This would look something like this:
In some cases, we might need to hack our way around and eliminate some steps from this flow to make the data passing trip slightly shorter. Say ComponentA
wouldn’t need to emit its data value to the parent component, then we can remove the emit and listen steps and directly access the values in ComponentA
from the Parent Component.
Using $ref
Say we have two identical components — Component A & Component B.
Component A has two methods; setThought
that sets the value of a thought
data property to any value passed from the editable div, and another method — readMind
that does nothing for now.
<template>
<div>
<div contenteditable @input="setThought($event.target.innerText)">
{{ thought }}
</div>
<div>
<button @click="readMind">What is B thinking?</button> {{ otherThought }}
</div>
</div>
</template>
<script>
export default {
name: "ComponentA",
data() {
return {
thought: 'I should steal a cookie',
otherThought: '',
};
},
methods: {
setThought(value) {
this.thought = value;
},
readMind() { }
},
};
</script>
Component B is similar, with just a slight difference in content:
<template>
<div>
<div contenteditable @input="setThought($event.target.innerText)">
{{ thought }}
</div>
<div class="">
<button @click="readMind">What is A thinking?</button> {{ otherThought }}
</div>
</div>
</template>
<script>
export default {
name: "ComponentB",
data() {
return {
thought: 'I like school a tiny bit',
otherThought: '',
};
},
methods: {
setThought(value) {
this.thought = value
},
readMind() { }
},
};
</script>
You may or may not have already figured out what we want to do here. We need Component A to be able to read Component B’s thoughts, without Component B emitting its thought.
For this, both components need to have something in common - their parent. To share datjsa between two components using $ref
, they need to be children of the same parent component. So in a parent component, we’ll import Component A and B into a parent component and assign ref attributes to both of them.
<template>
<div id="app">
<ComponentA ref="componentA" />
<ComponentB ref="componentB" />
</div>
</template>
<script>
import ComponentA from './components/ComponentA.vue'
import ComponentB from './components/ComponentB.vue'
export default {
name: 'App',
components: {
ComponentA,
ComponentB
},
}
</script>
With this structure, we can easily access each component by reference from its parent like so:
this.$parent.$refs.componentA
OR
this.$parent.$refs.componentB
Now we can update the readMind
method for Component A
so that on button-click Component A
would know exactly what Component B
is thinking:
<script>
export default {
name: "ComponentA",
methods: {
...
readMind() {
this.otherThought = this.$parent.$refs.componentB.thought
}
...
}
}
</script>
Notice we can access the thought
data property in ComponentB by setting a ref attribute on it and accessing it from its parent.
We can make a similar update to the readMind
method in Component B to do the same thing - find out what Component A is thinking.
<script>
export default {
name: "ComponentB",
methods: {
...
readMind() {
this.otherThought = this.$parent.$refs.componentA.thought
}
...
}
}
</script>
What does this look like?
Can we set component values too?
Sure, just like the 2010 movie — Inception, let’s force Component B’s thoughts to be exactly what Component A thinks about. We can set the value of the data properties in the same way:
<script>
export default {
name: "ComponentA",
methods: {
...
setThought(value) {
this.thought = value;
this.$parent.$refs.componentB.thought = value;
},
...
}
}
</script>
Alternatively, you can call the setThought
method of ComponentB from ComponentA:
<script>
export default {
name: "ComponentA",
methods: {
...
setThought(value) {
this.thought = value;
this.$parent.$refs.componentB.setThought(value);
},
...
}
}
</script>
What would this look like?
Oh, refs are reactive?
No, they’re not. What you see is only a result of calling the setThought
method every time there’s an input in Component A and in turn, setting the value of this.$parent.$refs.componentB.thought
to the same value as the thought
data property in ComponentA
.
Refs are one of Vue’s quirky parts and can get troublesome if you do not properly understand their usage. The safest way to use them would be outside your lifecycle hooks and only within methods. Also, avoid direct usage within components' template or computed properties.
If you’d like to fiddle around with the source code, you’ll find it here:
https://github.com/MartinsOnuoha/vue-ref-example
Also, Here’s a detailed guide on how and where to safely access refs, and of course, the official documentation.
Cheers ☕️