Trying to get vue.js to render something conditionally based on a method in created() - vue.js

I have a call in my created method which has an await.
I want to know that the results of that call are loaded so that i can conditionally show/hide things in the DOM.
Right now it looks like the DOM is being rendered before that method has completed. But I though that methods in created were called before the DOM rendered?

You're correct in assuming that the created hook runs before the component mounts. However, the lifecycle hooks are not waiting for async calls to complete. If you want to wait for that call to be completed and data to load, you can do so by using a Boolean that you set to true when your data has loaded.
Your template:
<div v-if='dataLoaded'>Now you can see me.</div>
in your vue instace
export default {
data () {
return {
dataLoaded: false
}
},
created () {
loadMyData().then(data => {
// do awesome things with data
this.dataLoaded = true
})
}
}
This way you can keep your content hidden until that call has resolved. Take care with the context when you handle the ajax response. You will want to keep this as a reference to the original vue instance, so that you can set your data correctly. Arrow functions work well for that.

Related

Vue3 child component does not recreating, why?

I have made some sandbox code of my problem here:
https://codesandbox.io/s/clever-zeh-kdff1z
<template>
<div v-if="started">
<HelloWorld :msg="msg" #exit="exit" #remake="remake" />
</div>
<button v-if="!started" #click="started = !started">start</button>
</template>
<script>
import HelloWorldVue from "./components/HelloWorld.vue";
export default {
name: "App",
components: {
HelloWorld: HelloWorldVue,
},
data() {
return {
started: false,
msg: "Hello Vue 3 in CodeSandbox!",
};
},
methods: {
exit() {
this.started = false;
},
remake() {
this.msg = this.msg + 1;
//this code should recreate our child but...
this.exit();
this.started = true;
// setTimeout(() => {
// this.started = true;
// });
},
},
};
</script>
So! We have 2 components parent and child. The idea is simple - we have a flag variable in our parent. We have a v-if statement for this - hide / show an element depend on the flag value "false" or "true". After we toggle the flag - the child component should be recreated. This is the idea. Simple.
In our parent we have a button which will set the flag variable to "true" and our child will be created and will appear on our page.
Ok. Now we have 2 buttons inside our child.
One button is "exit" which is emit an event so the flag variable of parent will set to "false" and the elemint will disappear from our page(It will be destroyed btw). Works as charm. Ok.
The second button "remake". It emit event so the flag variable will be just toggled (off then on). Simple. We set to "false", we set to "true". So the current child should dissapear, and then imediatly will be created new one.
But here we are facing the problem! Ok, current child is still here, there is no any recreation, it just updates current one... So in child I have checked our lifecycle hooks - created and unmounted via console.log function. And the second button dont trigger them. Start->Exit->Start != Start->Remake.
So can anyone please explain me why this is happening? I cant figure it out.
Interesting thing, if you can see there is some asynchronous code commented in my demo. If we set our flag to "true" inside the async function the child will be recreated and we will see the created hook message but it seems like crutch. We also can add a :key to our component and update it to force rerender, but it also seems like a crutch.
Any explanations on this topic how things work would be nice.
Vue re-uses elements and components whenever it can. It will also only rerender once per tick. The length of a 'tick' is not something you should worry yourself about too much, other than that it exists. In your case the this.exit() and this.started = true statements are executed within the same tick. The data stored in this.started is both true in the last tick and the current tick as it does not end the tick in between the statements, and so nothing happens to your component.
In general you should think in states in Vue rather than in lifecycles. Or in other words: What are the different situations this component must be able to handle and how do you switch between those states. Rather than determining what to do in which point in time. Using :key="keyName" is indeed generally a crutch, as is using import { nextTick } from 'vue'; and using that to get some cadence of states to happen, as is using a setTimeout to get some code to execute after the current tick. The nasty part of setTimeout is also that it can execute code on a component that is already destroyed. It can sometimes help with animations though.
In my experience when people try to use lifecycle hooks they would rather have something happen when one of the props change. For example when a prop id on the child component changes you want to load data from the api to populate some fields. To get this to work use an immediate watcher instead:
watch: {
id: {
handler(newId, oldId) {
this.populateFromApi(newId);
},
immediate: true
}
}
Now it will call the watcher on component creation, and call it afterwards when you pass a different id. It will also help you gracefully handle cases where the component is created with a undefined or null value in one of the props you expect. Instead of throwing an error you just render nothing until the prop is valid.

Trigger a component function from a vuex action/mutation

Short version: (Im using options API in vue CLI 3)
How can I call a function or execute some code inside a component (in my case resetModal()), when something inside my vuex store is triggered (in my case when modal data changes, AKA a new modal is set).
So I want to call myFunction() in my component:
methods: {
myFunction() {
something();
},
}
When a mutation in my store is called:
mutations: {
changeValue() {
[something to call `myFunction` with];
},
}
Long version:
I have a modal component, which is used in my App.vue file, (<modal-bottom v-if="modalBottomInfo.show"></modal-bottom>) and as you can see it activates whenever the .show is true, which is stored in my store, therefore can be activated/deactivated from the entire project.
So far so good, but the problem is, I want to trigger some code whenever a new modal is called, originally I'd done with mounted() {...} function inside the modal component, but this way, if a new value for modal is set while its active (.show is true) the values do change (so changes happen, but the function inside mounted() is omitted.
I also tried updated() but since my modal has a counter on it, the dom keeps changing so updated() is unreliable.
Modal values can only be changed by a mutation in my store, so if there is a way to trigger my function (which is used in mounted() inside the same mutation, my problem is solved).
I have also thought of using watcher: or moving the entire logic of my modal component to vuex but I dont wanna commit to either without being sure no better way exists yet.
In your vuex store, you will use a boolean in your state.
When your action is called, you can modify this state boolean.
In your component, you set a computed property with a store getter for this boolean.
You then use it locally to show/hide your modal

Vue 3 composition API, undefined variable, lifecycle

I am quite new to Vue.js and while developing my first project i have stumbled upon a problem which I think is related to the component's lifecycle.
The context is the following:
I am using Vue 3 with composition API.
I have a "Map" component in which I use d3.js to show a chart.
In my Setup() method I have onBeforeMount() and onMounted().
In onBeforeMount() method, I want to use data from my Firestore database for the values in the chart. The console.log on line 47 shows the data correctly.
In onMounted(), is where I plan to put my d3 chart. But when I try to access the data as in console.log on line 55, I get undefined...
Ideally, from "Map" component, I want to fetch the data from my database and use it in order to create the chart and then have the component render the chart.
Then I would have another component called 'MapSettings" that will be able to alter the data in "Map" such as filters etc...and have the values automatically updated in the chart.
Finally, both components will be siblings and have same parent component. So "Map" and "MapSettings" are on same hierarchical level.
But first I need to understand why I am getting undefined.. Any help or suggestion would be greatly appreciated!
Your lifecycle hooks look nice. The problem that you're facing has to do with code been executed asynchronously and code been executed synchronously.
You're declaring a function that uses async-await feature. This function will be executed asynchronously. In this case, you're getting data from Firestore and storing it in a ref at onBeforeMount().
On the other hand, you're declaring a normal function at onMounted() trying to access a ref's value, which will result in undefined because the function that you define at onBeforeMount() didn't finish its execution (or it's in the event queue) when onMounted is called.
That's why you first see the console.log that comes from onMounted.
One solution would merge both functions in one lifecycle hooks and use async await:
setup() {
const {actorDocs, load} = getActorDocs()
const actorsData = red([])
// load actor data from db
onBeforeMount( async () => {
await load()
actorsData.value = actorDocs
console.log(actorsData.value)
// manipulate it here...
})
}
Keep in mind that you cannot pause with async-await a lifecycle hook. What you're pausing is the function that Vue will execute in that hook. Indeed, this is really nice because pausing a hook wouldn't be efficient at all.
i face that problem, in my case i want use imgsRef.value out of onBeforeMount scope. How to get imgsRef value out of beforeMount scope?
onBeforeMount( async () => {
await axios
.get("http://localhost:3000/ourMoment")
.then((response) => {
imgsRef.value = response.data
// imhsRef get the value from api
console.log(imgsRef.value.photo)
})
})
// i try to concole.log here but the value still empty
console.log(imgsRef.value)

How can a dynamically instantiated Vue Component be cached?

In my application I instantiate a dialog component programmatically. The dialog can (does) contain a child component to show content. I accomplish this thus:
// Create the containing dialog component.
// I don't care if this is cached or not, and it's easy
// to recreate.
//
var DialogComponent = Vue.extend(Dialog);
var instance = new DialogComponent({
parent: parent,
data: {...}
propsData: {...}
});
// Compile the subcomponent string. In practice this would
// be built dynamically before being passed to
// Vue.compile()
//
const template = '<ComponentName :binding="" ... />'
const x = Vue.compile(template);
const slotContent = {
data() { return data},
render: x.render,
staticRenderFns: x.staticRenderFns
}
// Create a vnode from the compiled render functions. This
// is the part I am less confident in, as it appears the
// render function returned will always instantiate a new
// child component, and it feels like involving `instance`,
// i.e. the dialog, is incorrect.
//
const vnode = instance.$createElement(slotContent);
instance.$slots.default = [ vnode ];
// Mount the dialog component to a DOM element.
//
var tmp = document.createElement('div');
document.body.appendChild(tmp);
instance.$mount(tmp);
// Hook the dialog close event to clean up the instance.
// In practice, when caching I would like to pull the
// child component out and only destroy the dialog instance.
// I can successfully set the child's vnode.data.keepAlive and
// persist the instance, but a new one gets created nonetheless.
//
instance.$on('close', (e) => {
instance.$el.parentNode.removeChild(instance.$el);
instance.$destroy();
})
I believe this is the accepted way of accomplishing this, and it works flawlessly. However, each time the dialog goes away it is of course $destroyed and the component resets when it's shown again.
I now need a method of maintaining the subcomponent's state when the dialog closes and reopens. So imagine a dialog that shows a new Date() on creation— I need that same date to show up after the dialog is closed and reopened. I have tried various sprinklings of <keep-alive> to no avail, but I don't think this is the appropriate use of that component.
Caching the vnode successfully avoids the compile call, but because (I think) the returned render function instantiates a component instance (ComponentName in the example) it does not reuse the original ComponentName, even if I successfully avoid destroying it (vm._isDestroyed == false).
Ultimately I think I'd like to hit line 73 in vue/create-component.js when inserting the child component, but a breakpoint there never gets hit (which may be an unrelated webpack/source-maps thing).
Is there a sane way of accomplishing the caching of a programmatically instantiated Vue component for later reuse similar to how <keep-alive> works?

vue2: can not find a proper way to initialize component data by ajax

I have a component whose data is initialized by ajax. I know vue.js has provide several lifecycle hooks: Lifecycle-Diagram. But for ajax to initialize the data, which hook(beforeCreate, create, mounted, etc) is the best place to do it:
hook_name: function() {
ajaxCall(function(data) {
me.data = data;
});
}
Currently, i do it in mounted, making it to re-render the component. But i think we should get the data before the first render. Can someone figure out the best way to do it?
If you want to initialize your component with data you receive from a request, created() would be the most appropriate hook to use but it is a request, it might not resolve by the end of created or even mounted() (when even your DOM is ready to show content!).
So do have your component initialized with empty data like:
data () {
return {
listOfItems: [],
someKindOfConfig: {},
orSomeSpecialValue: null
}
}
and assign the actual values when you receive them in your created hook as these empty data properties would be available at that point of time, like:
created () {
someAPICall()
.then(data => {
this.listOfItems = data.listOfItems
})
/**
* Notice the use of arrow functions, without those [this] would
* not have the context of the component.
*/
}
It seems like you aren't using (or aren't planning to use) vuex but I'd highly recommend you to use it for for managing your data in stores. If you use vuex you can have actions which can make these api calls and by using simple getters in your component you would have access to the values returned by the request.