How to get the last value emit from vue event bus? - vue.js

I'm using vue event bus, for example in resize window:
resize.ts:
import Vue from 'vue';
export const windowResize = new Vue();
when I emit value:
const innerWidth = …;
const innerHeight = …;
resize.$emit('resize', { innerWidth, innerHeight });
when I received value:
resize.$on('resize', ({ innerWidth, innerHeight }) => { … });
My question is it possible to get the get the last value has been dispatched?
for example say in lazy component I want to have the width and height that calculate in the parents components? I think something like subject behavior (not BehaviorSubject) in rxjs…
In this lazy component I want to write
resize.$on('resize', ({ innerWidth, innerHeight }) => { … });
but I don't want to trigger the resize event.

Related

At what point are props contents available (and are they reactive once they are)?

I pass data into a component via props:
<Comment :comment="currentCase.Comment" #comment="(c) => currentCase.Comment=c"></Comment>
currentCase is updated via a fetch call to an API during the setup of the component (the one that contains the line above)
The TS part of <Comment> is:
<script lang="ts" setup>
import { Comment } from 'components/helpers'
import { ref, watch } from 'vue'
const props = defineProps<{comment: Comment}>()
const emit = defineEmits(['comment'])
console.log(props)
const dateLastUpdated = ref<string>(props.comment?.DateLastUpdated as string)
const content = ref<string>(props.comment?.Content as string)
watch(content, () => emit('comment', {DateLastUpdated: dateLastUpdated, Content: content}))
</script>
... where Comment is defined in 'components/helpers' as
export class Comment {
DateLastUpdated?: string
Content?: string
public constructor(init?: Partial<Case>) {
Object.assign(this, init)
}
}
content is used in the template, but is empty when the component is rendered. I added a console.log() to check whether the props were known - and what is passed is undefined at that point:
▸ Proxy {comment: undefined}
When looking at the value of the props once the application is rendered, their content is correct:
{
"comment": {
"DateLastUpdated": "",
"Content": "comment 2 here"
}
}
My question: why is comment not updated when props are available (and when are their content available?)
I also tried to push the update later in the reactive cycle, but the result is the same:
const dateLastUpdated = ref<string>('')
const content = ref<string>('')
onMounted(() => {
console.log(props)
dateLastUpdated.value = props.comment?.DateLastUpdated as string
content.value = props.comment?.Content as string
watch(content, () => emit('comment', {DateLastUpdated: dateLastUpdated, Content: content}))
})
Vue lifecycle creates component instances from parent to child, then mounts them in the opposite order. Prop value is expected to be available in a child if it's available at this time in a parent. If currentCase is set asynchronously in a parent, the value it's set to isn't available on component creation, it's a mistake to access it early.
This disables the reactivity:
content.value = props.comment?.Content as string
props.comment?.Content === undefined at the time when this code is evaluated, it's the same as writing:
content.value = undefined;
Even if it weren't undefined, content wouldn't react to comment changes any way, unless props.comment is explicitly watched.
If content is supposed to always react to props.comment changes, it should be computed ref instead:
const content = computed(() => props.comment?.Content as string);
Otherwise it should be a ref and a watcher:
const content = ref();
const unwatch = watchEffect(() => {
if (props.comment?.Content) {
content.value = props.comment.Content;
unwatch();
...
}
});

Create computed property dependent on variables within onMounted() lifecycle hook

I have a component that needs to calculate some values using the DOM, and so I have the onMounted() lifecycle hook. In onMounted() I calculate some values (technically, they're computed properties) dependent on DOM elements.
I then use the values found in another computed property, leftOffset. I need to use leftOffset in template (it changes some CSS, not really relevant).
Below is my setup():
setup() {
let showContent = ref(false);
let windowWidth = ref(null);
let contentHider = ref(null);
let contentHiderXPos ;
let contentHiderWidth ;
let leftWidth;
onMounted(() => {
// The DOM element will be assigned to the ref after initial render
windowWidth = ref(window.innerWidth);
contentHiderXPos = computed(() => contentHider.value.getBoundingClientRect().left);
contentHiderWidth = computed(() => contentHider.value.getBoundingClientRect().width);
leftWidth = computed(() => contentHiderXPos.value + contentHiderWidth.value);
});
let leftOffset = computed(() => {
return -(windowWidth.value - leftWidth.value)
});
return {
contentHider,
leftOffset,
}
contentHider references a the DOM element of one of the divs defined in template.
My problem that leftOffest.value is undefined, because it tries to access windowWidth.value and leftWidth.value, which are also undefined. I've also tried putting leftOffset inside onMounted(), but then I can't access it from template (it's undefined).
How can I re-structure my code so that leftOffset can both be accessed from template and can also access the values found in onMounted()?
I've searched online, but couldn't find anything specific to the composition API.
Your usage of the ref is wrong. Follow the comments in the below snippet.
Also this is assuming that you do not want the window.innerwidth to be reactive.
// dont create another ref => you are trying to assign a new object here, thus breaking the reactivity
// windowWidth = ref(window.innerWidth);
// do this instead
windowWidth.value = window.innerWidth
If you want the innerWidth to be reactive, you have to use the native event listener like this;
const windowWidth = ref(window.innerWidth)
onMounted(() => {
window.addEventListener('resize', () => {windowWidth.value = window.innerWidth} )
})
onUnmounted(() => {
window.removeEventListener('resize', () => {windowWidth.value = window.innerWidth})
})

In Vue, what if I need to use state from getters while mounted life cycle hook?

I try to use data from vuex during mounted lifecycle hook.
However, it seems like mounted life cycle hook is excuted before I get the data from vuex.
How do I access the data from vuex and use it during mounted life cycle hook?
The code is as below.
I bring data by getters like this.
computed:{
targetCounty(){
return this.$store.getters['parkingModule/byCounty'][this.countyname]
}
Then I need to feed this data to my class constructur by init() method
init(){
scene =new THREE.Scene();
const canvas=document.querySelector('#myCanvas');
canvas.width=innerWidth;
canvas.height=innerHeight;
camera = new THREE.PerspectiveCamera( 75, window.innerWidth/window.innerHeight, 0.1,
1000 );
renderer=new THREE.WebGLRenderer({canvas:canvas})
renderer.setSize( window.innerWidth, window.innerHeight );
let texture = new THREE.TextureLoader().load('disc.png');
let rawRad = this.rawRadius
console.log(this.targetCounty)
const meshobject =new
ParkingSpot(rawRad,this.targetCounty.length,100,texture,this.targetCounty)
sphereMesh= meshobject.createMesh();
camera.position.z = 5
scene.add(sphereMesh);
console.log(sphereMesh.material.size)
},
this init() method is invoked during mounted life cycle hook like this.
mounted(){
this.init()
this.animate();
// window.addEventListener()
},
created(){
console.log(this.targetCounty)
// setTimeout(()=>{console.log(this.targetCounty[0])},3000)
},
However, when I log this.targetCounty, it returns empty array. So I got around it
by rendering computed property in DOM cause computed property runs only the element is rendered.
<template>
<div>
<canvas id="myCanvas"></canvas>
</div>
<p v-show='false'>{{targetCounty}}</p>
</template>
I created dummy DOM element only to get the computed property for my mounted life cycle(I think it's very bad approach)
What would be the solution for solving this problem?
You could use vm.$watch() in the mounted() hook to observe the store's getter for the initial value:
export default {
mounted() {
const unwatch = this.$watch(
() => this.$store.getters['parkingModule/byCounty'][this.countyname],
targetCounty => {
if (targetCounty) {
// handle initial value here...
this.targetCounty = targetCounty
this.init()
this.animate()
unwatch()
}
}
)
}
}
demo
Why don't you try making a function that explicitly returns the value and then invoke it in the mounted() lifecycle hook, saving it into a constant. Then pass that constant into your init function.
const targetCountry = this.$store.getters['parkingModule/byCounty'[this.countyname]
this.init(targetCountry)

change props value of a child component causing too many renders

child component takes a prop. Now from button click of the parent, i want to continue updating the prop so that the child component can update itself with new calculated result
const childComponent = ({value}) =>{
const [total, updateTotal] = useState('enter 0 or higher value and hit calculate button');
const calculateTotal = () =>{
updateTotal(value * 0.25);
}
useEffect(()=>{
if (value>0){
calculateTotal();
}
}, [value]);
return (<Text>{total}</Text>)
}
ParentComponent:
const [value, updateValue]= useState(0);
const generateValue = ()=>{
//logic that generates a number
const numberGenerated = generateValue();
updateValue(numberGenerated);
}
return (<View><ChildComponent value={value}/> <Button onPress={generateValue}>Calculate</Button></View>)
But when I click the button, it says :
Warning: Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn't have a dependency array, or one of the dependencies changes on every render.
Maybe it's because you missed the arrow function:
<Button onPress={()=>generateValue()}>Calculate</Button>

reactive object not updating on event emitted from watch

i'm building a complex form using this reactive obj
const formData = reactive({})
provide('formData', formData)
inside the form one of the components is rendered like this:
<ComboZone
v-model:municipality_p="formData.registry.municipality"
v-model:province_p="formData.registry.province"
v-model:region_p="formData.registry.region"
/>
this is the ComboZone render function:
setup(props: any, { emit }) {
const { t } = useI18n()
const { getters } = useStore()
const municipalities = getters['registry/municipalities']
const _provinces = getters['registry/provinces']
const _regions = getters['registry/regions']
const municipality = useModelWrapper(props, emit, 'municipality_p')
const province = useModelWrapper(props, emit, 'province_p')
const region = useModelWrapper(props, emit, 'region_p')
const updateConnectedField = (key: string, collection: ComputedRef<any>) => {
if (collection.value && collection.value.length === 1) {
console.log(`update:${key} => ${collection.value[0].id}`)
emit(`update:${key}`, collection.value[0].id)
} else {
console.log(`update:${key} =>undefined`)
emit(`update:${key}`, undefined)
}
}
const provinces = computed(() => (municipality.value ? _provinces[municipality.value] : []))
const regions = computed(() => (province.value ? _regions[province.value] : []))
watch(municipality, () => updateConnectedField('province_p', provinces))
watch(province, () => updateConnectedField('region_p', regions))
return { t, municipality, province, region, municipalities, provinces, regions }
}
useModelWrapper :
import { computed, WritableComputedRef } from 'vue'
export default function useModelWrapper(props: any, emit: any, name = 'modelValue'): WritableComputedRef<any> {
return computed({
get: () => props[name],
set: (value) => {
console.log(`useModelWrapper update:${name} => ${value}`)
emit(`update:${name}`, value)
}
})
}
problem is that the events emitted from useModelWrapper update the formData in the parent template correctly, the events emitted from inside the watch function are delayed by one render....
TL;DR;
Use watchEffect instead of watch
...with the caveat that I haven't tried to reproduce, my guess is that you're running into this issue because you're using watch which runs lazily.
The lazy nature is a result of deferring execution, which is likely why you're seeing it trigger on the next cycle.
watchEffect
Runs a function immediately while reactively tracking its dependencies and re-runs it whenever the dependencies are changed.
watch
Compared to watchEffect, watch allows us to:
Perform the side effect lazily;
Be more specific about what state should trigger the watcher to re-run;
Access both the previous and current value of the watched state.
found a solution, watchEffect wasn't the way.
Looks like was an issue with multiple events of update in the same tick, worked for me handling the flush of the watch with { flush: 'post' } as option of the watch function.
Try to use the key: prop in components. I think it will solve the issue.