I have a Map component which initializes leaflet on the DOM like so:
Map.vue
<template>
<div ref="map"/>
<template>
<script>
import * as L from 'leaflet';
import mapEventBus from '../event-buses/map.vue';
export default {
mounted(){
const map = L.map(this.$refs.map);
mapEventBus.$on('add-marker',(newMarker) => {
newMarker.addTo(map);
});
}
}
</script>
And then I have another component which needs to add a marker that is built on the components creation.
OtherComponent.vue
<template>
<div/>
</template>
<script>
import mapEventBus from '../event-buses/map.vue';
export default {
created(){
mapEventBus.$emit('add-marker',L.marker([51.5, -0.09]));
}
}
</script>
Because the map is initialized after the OtherComponent has already tried emitting to the event bus, the event is never fired. What would be the best way to "await" for the map to be initialized and then add the marker to the map. I though about having a "cache" of pending markers that is added on the map creation but that seems clunky.
Example:
https://codesandbox.io/s/2ov71xnz3r
OK, so you've got a little chicken and egg problem there. You have an element you need to update via refs (some way to hack data into a 3rd party plugin), but you get the data BEFORE you mount the HTML.
What you need to do is separate out the immediate catch into a data variable, then on mount, check to see if it exists and if so update the HTML element.
I'm not answering your question above, because the problem is simplified in the codesandbox example you provided.
Here is the solution based on that:
https://codesandbox.io/s/3rnyp31n4p
<script>
import { EventBus } from '../eventBus.js'
export default {
data: () => ({
immediateMessage: null
}),
beforeCreate() {
EventBus.$on("immediate-message", message => {
this.immediateMessage = message;
});
},
mounted() {
if (this.immediateMessage) {
this.$refs.immediateMessageEl.innerHTML += this.immediateMessage;
}
EventBus.$on("delayed-message", message => {
this.$refs.delayedMessageEl.innerHTML += message;
});
}
};
</script>
Note, the beforeCreate() binds to the event and sets a variable, then we use that variable once the DOM is mounted.
Check out lifecycle hooks page for more info https://v2.vuejs.org/v2/guide/instance.html#Lifecycle-Diagram
This is definitely not the most elegant solution, but will definitely get you going.
Related
i have a react component thats keep re-rendering idk why but i think the reason is the data fetching
data code :
export function KPI_Stock_Utilisation() {
const [kpi_stock_utilisation, setKpi_stock_utilisation] = useState([{}]);
useEffect(() => {
axios.get("http://localhost:5137/KPI_Stock_Utilisation").then((response) => {
setKpi_stock_utilisation((existingData) => {
return response.data;
});
});
}, []);
console.log('data get')
return kpi_stock_utilisation;
}
this log displayed many times , and the log in the component too
component code :
import React from "react";
import { KPI_Stock_Utilisation } from "../../Data/data";
import { useEffect } from "react";
export default function WarehouseUtilisChart(props) {
let kpi_stock_utilisations =KPI_Stock_Utilisation();
let Stock_utilisation = (kpi_stock_utilisations.length / 402) * 100;
console.log('component render')
return (
<div>
<p>{kpi_stock_utilisations}</p>
</div>
);
}
im new with react i tried useEffect inside the componenets but its not working
Calling the react custom hook KPI_Stock_Utilisation several times will for sure render more than once.
in your case I suggest you use useEffect in the same component as I will show you.
import React,{useEffect,useRef} from "react";
import { KPI_Stock_Utilisation } from "../../Data/data";
import axios from 'axios';
export default function WarehouseUtilisChart(props) {
const [kpi_stock_utilisation, setKpi_stock_utilisation] = useState([{}]);
const stock_utilisation= useRef(0);
useEffect(() => {
axios.get("http://localhost:5137/KPI_Stock_Utilisation").then((response) => {
stock_utilisation.current = (response.data.length / 402) * 100;
setKpi_stock_utilisation(response.data);
});
//this will guarantee that the api will be called only once
}, []);
//you should see this twice, one with the value 0, and another one, the calculated data
console.log('component render',stock_utilisation.current)
return (
<div>
<p>{kpi_stock_utilisations}</p>
</div>
);
}
To note, if you call this component from more than one location, for sure it will render several times - keep that in mind.
On the other hand, all your variables should always start with a lower case and try to name your variables like this: instead of kpi_stock_utilisation change it to kpiStockUtilisation for a better coding practice
You got into infinite loop.
Its hard to explain why it doesn't work as expected, but I can try.
First of all, useEffect with empty array of dependencies works like componentDidMount and fires only after (!) first render.
So you have some value returned from your let kpi_stock_utilisations =KPI_Stock_Utilisation(); then it rendered, after this your useEffect fires a request and set state, setting of state trigger re-render and new value to return, this new value trigger your parent component to return let kpi_stock_utilisations =KPI_Stock_Utilisation(); might run again.
If you are trying to create a custom hook for fetching some info, follow rules of hooks
I hope it helped you
Out of the blue, emitter stopped working:
event-bus.js
import Vue from 'vue';
export const EventBus = new Vue();
import { EventBus } from '../event-bus';
...
mounted() {
this.getCart();
}
...
methods: {
getCart() {
axios.get(`${app.api_erp_url}/cart/${this.cartId}`).then((response) => {
this.cart = response.data;
EventBus.$emit('cartLoaded', this.cart); // this not working
});
}
},
another-component.vue
mounted() {
// MiniCart.vue
EventBus.$on('cartLoaded', (payload) => {
...
});
},
No matter how I try to emit the event inside mounted/created, it will not work. No problems when firing events on click or something.
Created sandbox: https://codesandbox.io/s/gracious-kilby-m43ih?fontsize=14&hidenavigation=1&theme=dark
Child components mount before their parent component.
This is the sequence occurring in your example:
HelloWorld (parent) is created
Test (child) is created
Test (child) is mounted, which emits an event
HelloWorld (parent) is mounted, which subscribes to the event that was already emitted
If you want HelloWorld to catch the event from its children, subscribe to the event in the created hook.
demo
According to this You should use kebab-case format to name your custom events :
EventBus.$emit('cartLoaded', this.cart);//not correct
EventBus.$emit('cart-loaded', this.cart); //correct
May be the event emitted before MiniCart.vue component registered the event.
Meaning in code.
EventBus.$emit('cartLoaded', this.cart);
this run first time before the event has been registered
EventBus.$on('cartLoaded', (payload) => {
...
});
I have migrated my application to Vue 3.
Now my linter shows a deprecation error, documented here: https://eslint.vuejs.org/rules/no-deprecated-events-api.html.
The documentation shows how to replace this.$emit with the mitt library, but it doesn't show how to replace this.$parent.$emit.
In your child component:
setup(props, { emit }) {
...
emit('yourEvent', yourDataIfYouHaveAny);
}
Your parent component:
<your-child #yourEvent="onYourEvent" />
...
onYourEvent(yourDataIfYouHaveAny) {
...
}
With script setup syntax, you can do:
<script setup>
const emit = defineEmits(['close', 'test'])
const handleClose = () => {
emit('close')
emit('test', { anything: 'yes' })
}
</script>
No need to import anything from 'vue'. defineEmits is included.
Read more here: https://learnvue.co/2020/01/4-vue3-composition-api-tips-you-should-know/
Due to the composition api, it allows you to use the $attrs inherited in each component to now fulfill this need.
I assume that you are using this.$parent.emit because you know the the child will always be part of the same parent. How do I simulate the above behavior with $attrs?
Lets say I have a table containing row components. However I wish to respond to row clicks in table's parent.
Table Definition
<template>
<row v-bind="$attrs" ></row>
</template>
Row Definition
<template name="row" :item="row" #click=onClick(row)>
Your Row
</template>
export default {
emits: {
row_clicked: () =>{
return true
}
},
onClick(rowData){
this.$emit('row_clicked',rowData)
}
}
Finally, a component containing your table definition, where you have a method to handle the click.
<table
#row_clicked=clicked()
>
</table
Your table component should effectively apply #row_clicked to the row component thus triggering when row emits the event.
There is similar way of doing it by using the context argument that is passed in second argument inside the child component (the one that will emit the event)
setup(props, context){
context.emit('myEventName')
}
...then emit it by calling the context.emit method within the setup method.
In your parent component you can listen to it using the handler like so:
<MyParentComponent #myEventName="handleMyEventName" />
Of course, in the setup method of the MyParentComponent component you can declare the handler like this
//within <script> tag of MyParentComponent
setup(props){
const handleMyEventName() => {
...
}
return { handleMyEventName }
}
Listen for custom events for the bus in component b. However, after dispatching events in component a, it accesses component b. the listening function of component b is executed, but msg of data function is not updated
Please don't say Vuex.
The relevant code is based on Vue CLi3
Here code:
Component A:
<template>
<div>
Component A
<button #click="sendMsg">pushB</button>
</div>
</template>
<script>
import bus from './bus'
export default {
methods: {
sendMsg() {
bus.$emit('send', 'hello Component B')
this.$router.push('/bbb')
}
}
}
</script>
component B:
<template>
<div>
<p>component B:{{ msg }}</p>
</div>
</template>
<script type="text/javascript">
import bus from './bus'
export default {
data () {
return {
msg: 'bbb'
}
},
mounted () {
bus.$on('send', data => {
console.log(data)
console.log(this)
this.msg = data
})
}
}
</script>
bus.js
import Vue from 'vue';
export default new Vue()
router:
const aaa = () => import('#/components/demo/bus/a')
const bbb = () => import('#/components/demo/bus/b')
export default new Router({
routes: [{
path: '/aaa',
component: aaa
},
{
path: '/bbb',
component: bbb
}]
})
I tried using 'watch' to observe 'msg', but it didn't work.
Can you help me?
If possible, I would like to deeply understand 'bus'
This will work only if both component A and component B are present in the page at the time you are emitting. From the code it seems that you are emitting the value from component A and then navigating to component B and expecting the value there.
What you are doing is something like kicking a ball and then running after it and then picking it only to find that the ball has disappeared. What you need is another person already present at that location who picks up the ball.
A solution in this case can be to set the value in localstorage, navigate to the other route and then read the value from localstorage.
If the value you need to pass is a simple value, you can just pass it in query string and then read from $router params in component B.
Your code will not work as expected as your are changing route after emitting event from Component A. So it can't be catch by Component B.
You can save the changed value in mixing look here for mixins or use localstorage. And you can also use query string as stated in previous answer
I have a parent and child component, in child component I have a <transition> defined like this:
<template lang="pug">
div
transition(name="fade-transition" mode="out-in" v-on:after-enter="fnAfterEnter")
h1(v-if"someCondition") lorem ipsum
</template>
<script>
export default {
methods: {
fnAfterEnter () {
do something here...
}
}
}
</script>
The problem is, in parent component I have some functions that will mount and destroy the child component with simple v-if condition. Things will work just fine for the first time child component is mounted but once destroyed and mounted back again <transition>'s all the hooks (not just v-on:after-enter) doesn't trigger methods fnAfterEnter.
Thanks in advance :)
I found that the issue was: the child component's transition was not completed and I was running some function in parent component to make child component's transition item condition true without using $nextTick but now I did like below code and the issue got fixed.
<script>
export default {
methods: {
someFnInParent () {
this.$nextTick(() => {
this.$refs.childComp.someCondition = true
})
}
}
}
</script>
So, this.$nextTick(() => {}) helped me :)