Transition's hook creates issue once destroyed and called back - javascript

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 :)

Related

Vue - Access prop in child component, regular javascript

I am loading a Vue component from another Vue component and am passing a property to that component. I need to access this property in the regular javascript of that component, but cannot figure out how to do this.
The simplified parent component could look as follows:
<template>
<div>
<MenuEdit :menu-list="menuList"></MenuEdit>
</div>
</template>
<script>
import MenuEdit from '#/components/MenuEdit';
export default {
name: 'Admin',
data: function () {
return {
menuList: ["Item1","Item2","Item3","Item4"]
};
},
components: {
MenuEdit
}
}
</script>
<style scoped>
</style>
And the MenuEdit could look as follows:
<template>
<div>
{{ menuList }}
</div>
</template>
<script>
//console.log(this.menuList) // Does not work.
export default {
name: 'MenuEdit',
props: [
'menuList'
],
methods: {
testMenu: function() {
console.log(this.menuList) //This works fine
}
}
}
</script>
<style scoped>
</style>
EDIT
To add some context to the question, I am implementing sortablejs on Buefy using the following example: https://buefy.org/extensions/sortablejs
Instead of calling "vnode.context.$buefy.toast.open(Moved ${item} from row ${evt.oldIndex + 1} to ${evt.newIndex + 1})" at the end of the first const, I want to update the component (or better said, update the related Array).
In the example, the const are defined outside of the component, which is why I ended up with this question.
You cannot access the prop as that code (where your console.log is) runs before the component is mounted, before it's even declared really
If you want to access stuff when the component is first mounted, you can use the mounted lifecycle method

Mixin for destroyed Vue component is still listening for events

I have a parent component that conditionally renders one of two child components:
<template>
<div>
<!-- other code that changes conditional rendering -->
<folders v-if="isSearchingInFolders" :key="1234"></folders>
<snippets v-if="!isSearchingInFolders" :key="5678"></snippets>
</div>
</template>
Each of these components use the same mixin (searchMixin) locally like so:
<template>
<div>
<div>
<snippet
v-for="item in items"
:snippet="item"
:key="item.id">
</snippet>
<img v-if="busy" src="/icons/loader-grey.svg" width="50">
</div>
<button #click="getItems">Get More</button>
</div>
</template>
<script>
import searchMixin from './mixins/searchMixin';
import Snippet from './snippet';
export default {
components: { Snippet },
mixins: [searchMixin],
data() {
return {
resourceName: 'snippets'
}
},
}
</script>
Each of the components is functionally equivalent with some slightly different markup, so for the purposes of this example Folders can be substituted with Snippets and vice versa.
The mixin I am using looks like this (simplified):
import axios from 'axios'
import { EventBus } from '../event-bus';
export default {
data() {
return {
hasMoreItems: true,
busy: false,
items: []
}
},
created() {
EventBus.$on('search', this.getItems)
this.getItems();
},
destroyed() {
this.$store.commit('resetSearchParams')
},
computed: {
endpoint() {
return `/${this.resourceName}/search`
},
busyOrMaximum() {
return this.busy || !this.hasMoreItems;
}
},
methods: {
getItems(reset = false) {
<!-- get the items and add them to this.items -->
}
}
}
In the parent component when I toggle the rendering by changing the isSearchingInFolders variable the expected component is destroyed and removed from the DOM (I have checked this by logging from the destroyed() lifecycle hook. However the searchMixin that was included in that component does not appear to be destroyed and still appears to listen for events. This means that when the EventBus.$on('search', this.getItems) line is triggered after changing which component is actively rendered from the parent, this.getItems() is triggered twice. Once for folders and once for snippets!
I was expecting the mixins for components to be destroyed along with the components themselves. Have I misunderstood how component destruction works?
Yes, when you pass an event handler as you do EventBus keeps the reference to the function you passed into. That prevents the destruction of the component object. So you need clear the reference by calling EventBus.$off so that the component can be destructed. So your destroy event hook should look like this:
destroyed() {
this.$store.commit('resetSearchParams')
EventBus.$off('search', this.getItems)
},

How to replace this.$parent.$emit in Vue 3?

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 }
}

Vue event bus await mount

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.

Best practices for using React refs to call child function

I'm hoping for some clarity on the use of React refs for calling a child function. I have a Parent component that's a toolbar with a few buttons on it, and in the child component I have access to a library's export functionality. I'd like to call this export function on a button click in the parent component. Currently I'm using React refs to accomplish this:
Parent.js [ref]
class Parent extends React.Component {
onExportClick = () => {
this.childRef.export();
}
render() {
return (
<div>
<button onClick={this.onExportClick} />Export</button>
<Child ref={(node) => this.childRef = node;} />
</div>
)
}
}
Child.js [ref]
class Child extends React.Component {
export() {
this.api.exportData();
}
render() {
<ExternalLibComponent
api={(api) => this.api = api}
/>
}
}
This solution works fine, but I've seen a lot of disagreement on if this is the best practice. React's official doc on refs says that we should "avoid using refs for anything that can be done declaratively". In a discussion post for a similar question, Ben Alpert of the React Team says that "refs are designed for exactly this use case" but usually you should try to do it declaratively by passing a prop down.
Here's how I would do this declaratively without ref:
Parent.js [declarative]
class Parent extends React.Component {
onExportClick = () => {
// Set to trigger props change in child
this.setState({
shouldExport: true,
});
// Toggle back to false to ensure child doesn't keep
// calling export on subsequent props changes
// ?? this doesn't seem right
this.setState({
shouldExport: false,
});
}
render() {
return (
<div>
<button onClick={this.onExportClick} />Export</button>
<Child shouldExport={this.state.shouldExport}/>
</div>
)
}
}
Child.js [declarative]
class Child extends React.Component {
componentWillReceiveProps(nextProps) {
if (nextProps.shouldExport) {
this.export();
}
}
export() {
this.api.exportData();
}
render() {
<ExternalLibComponent
api={(api) => this.api = api}
/>
}
}
Although refs are seen as an "escape hatch" for this problem, this declarative solution seems a little hacky, and not any better than using refs. Should I continue to use refs to solve this problem? Or should I go with the somewhat hacky declarative approach?
You don't need to set the shouldExport back to false, you could instead detect the change:
componentWillReceiveProps(nextProps) {
if (nextProps.shouldExport !== this.props.shouldExport) {
this.export();
}
}
Then every toggle of the shouldExport would cause exactly one export. This however looks weird, I'd use a number that I'd increment:
componentWillReceiveProps(nextProps) {
if (nextProps.exportCount > this.props.exportCount) {
this.export();
}
}
I ran into the same problem in many occasions now, and since the React team doesn't encourage it i'll be using the props method for later development, but the problem is sometimes you want to return a value to the parent component, sometimes you need to check the child's state to decide whether to trigger an event or not, therefore refs method will always be my last haven, i suggest you do the same

Categories

Resources