I know that a watch runs before the beforeUpdate lifecycle event occurs. However, I have things in the watch which must complete before the virtual-DOM is re-rendered/updated.
My watchis like this:
setup () {
watch(() => $route.params.ProductID, async () => {
// Update Vuex here with ProductID data and then do a whole bunch of stuff here that must complete before updating the DOM
},
{
deep: true,
immediate: true
})
onBeforeUpdate(() => {
// Use stuff that was created in watch above
})
}
This code is not only run by the client, but also the server for SSR.
I have tried to test if watch will complete all tasks first before onBeforeUpdate() runs like this:
setup() {
async function testWatch(){
setTimeout(function(){ return console.log("I am watcher timeout"); }, 3000);}
setup () {
watch(() => $route.params.ProductID, async () => {
await testWatch();
},
{
deep: true,
immediate: true
})
onBeforeUpdate(() => {
console.log("I am onBeforeUpdate hook");
})
}
}
If I run the above I get the output of:
I am onBeforeUpdate hook
then 3 seconds later..
I am watcher timeout
How can I ensure that the code within the watch will execute before anything else in the component? I need this to happen so that the correct data is available to the component before it renders.
How can I ensure that the code within the watch will execute before anything else in the component?
You can't - it is not possible.
Your best bet is to move the async code to route guards as described in the docs - Fetching Data Before Navigation
Problem with the approach is (or can be - sometimes) that the fetching code (getPost function in the example) cannot be a method on the component and (logically) has no access to this
I solve this sort of thing by creating a data property 'loading' that gets set to false when loading is done.
Then use a v-if to only render stuff when 'loading' is false.
Related
I have recently done a few API tests for a new job. Just receiving data and passing it through. Although I have completed the tasks and it works functionally, the people I walk through it with are not huge fans of componentDidMount.
They do not suggest an alternative? Anyone know why this could be? Is it due to it being async?
The new modern way to do it is: useEffect
First some code (from the docs):
// Similar to componentDidMount and componentDidUpdate:
useEffect(() => {
// Update the document title using the browser API
document.title = `You clicked ${count} times`;
});
At the end-of-the-day, the componentDidMount purpose is to execute something(the side effect) because the component was mounted(the reason or event).
So you can specify array of dependencies (or causes) for re-running like so:
useEffect(() => {
// ....
}, [someVar]);
so if someVar changed, the function will re-run.
Special use cases are; omitting this argument, will cause it to run once, on-mount event. and specify empty array will cause it to run on each re-render.
For the componentWillUnmount:
Just return a function from the inner function like so:
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
// Specify how to clean up after this effect:
return function cleanup() {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
I have a timer in my component, which is running with setInterval that starts in mounted() component.
Suppose this component is at http://localhost:3000/some-route.
Now how do I do clearInterval() whenever I go to another route like http://localhost:3000/ as I want to stop the timer.
I've used unmounted() but when you go to different route, the component doesn't unmounts but if I go to same route (/some-route), setInterval runs again as the component is mounted again.
So, how do I clear the interval every time I go to different route?
I had to do it once, it's a bit tricky because of the scope and the way it interacts with this (arrow functions basically). At the end, I also needed it to be available globally so I used it in pair with Vuex but you can totally use it in a component with some upper scope for the variable.
Here is a sample of code I've used
actionSetPolling({ state, dispatch }) {
try {
const myPolling = setInterval(async function () {
if (someVariable !== 'COMPLETED') {
// do stuff
} else if (conditionToStop) {
// this is facultative, but can be done here so far too
window.clearInterval(myPolling)
}
}, 2000)
dispatch('setPollingId', myPolling)
} catch (error) {
console.warn('error during polling', error.response)
window.clearInterval(state.pollingId)
}
}
setPollingId is an action that set's a pollingId mutation, that way I can track it all along globally.
You could use the same while unmounting your component with this
beforeDestroy() {
window.clearInterval(state.pollingId)
},
Not sure if it's the best way of doing things but setInterval is by it's nature, clunky and hacky out of the box IMO, especially in an SPA.
I have a screen with some choices on. If you select the choice it sets state of the data. I then have a confirm button. if the user hits confirm I make an async call to get some extra data. I want to wait for this to happen before opening the modal as I need to present that extra data in my modal.
before hooks I would use setState and do something like:
this.setState({data: myData}, () => this.openModal()) as this would reliably set the state then open the modal. all the answers online seem to suggest using useEffect but it seems dodgy to do this:
useEffect(() => {
if (data) {
setModalOpen(true)
}
}, [data, setData])
I don't want my modal potentially randomly opening at different points. plus it seems better to have the code living in the same place I set state. it makes sense to be there. not some random useEffect
any suggestions how this can be achieved?
(one other solution I can think of is making the API call on every choice select, rather than before confirm) however, this could lead to a lot of unnecessary API calls so I'd rather not go down that route.
Using useEffect() is correct, I also encountered this issue when trying to do a callback on setState with hooks.
Like you said: this.setState({data: myData}, () => this.openModal()) was possible before, but now when trying this with hooks the console displays the error:
Warning: State updates from the useState() and useReducer() Hooks don't support the second callback argument. To execute a side effect after rendering, declare it in the component body with useEffect().
So useEffect() seems the way to go.
You should use useEffect() as a callback after the state is correctly setted if you would like to do something with the state like validation.
useEffect(() => {
// fetch on load
axios.get("https://randomuser.me/api/").then((response) => {
setPerson(response.data.results[0]);
});
}, []);
useEffect(() => {
// do some validation perhaps
if (person !== null) {
if (person.name.first && person.name.last) {
setModal(true);
} else {
setModal(false);
}
}
}, [person]); // add person in dependency list
As suggested in the comments, you could also do setModal() when the async data has arrived (using .then() or await).
Some example code using random user generator API and axios for fetching.
useEffect(() => {
// fetch on load
axios.get("https://randomuser.me/api/").then((response) => {
setPerson(response.data.results[0]);
setModal(true); // set modal visibility
});
}, []);
I would like to query an API service every 15 seconds, so I can get data from a database and check whether something was changed. If there was a change, then my front end would update automatically because of how vue works.
while (true) {
setTimeout(function() {
QueryService.orders().then(response =>
this.orders = response.data
)
}, 15000)
}
My questions are:
Is this a good approach to solve such a problem at all?
What would be the best position in the code to place such a loop?
EDIT:
Using setInterval() seems to be the right way, but using a polling function with setInterval in the created() hook doesn't affect the data-table at all. It shows me "No data available":
data () {
return {
headers [
{ ... },
{ ... }
],
orders: []
}
created () {
setInterval(function() {
QueryService.orders().then(response => this.orders = response.data)
}, 15000)
}
Using the polling function without setInterval works and fills my data-table with data as usual:
created () {
QueryService.orders().then(response => this.orders = response.data)
}
For a simple and quick solution, I'd go with I'mOnlyVueman's answer. Here some example code I found from Vue.js polling using setINterval(). This example includes
pollData method initiated on created that dispatches a store action (which would call the API)
Canceling the poll as you navigate to another page using beforeDestroy
Code
data () {
return {
polling: null
}
},
methods: {
pollData () {
this.polling = setInterval(() => {
this.$store.dispatch('RETRIEVE_DATA_FROM_BACKEND')
}, 3000)
}
},
beforeDestroy () {
clearInterval(this.polling)
},
created () {
this.pollData()
}
But polling an API isn't very elegant and it doesn't scale well. You'll likely need to do something with Websockets, setting up your app to listen for events pushed from your API.
Here's info on Subscriptions in Vue-Apollo & GraphQL that Denis Tsoi mentioned.
Subscriptions are a GraphQL feature that allows the server to send
data to the clients when a specific event happens on the backend.
Subscriptions are usually implemented with WebSockets, where the
server holds a steady connection to the client. That is, the
Request-Response-Cycle that we used for all previous interactions with
the API is not used for subscriptions. Instead, the client initially
opens up a steady connection to the server by specifying which event
it is interested in. Every time this particular event happens, the
server uses the connection to push the data that’s related to the
event to the client.
A loop like this would go in the component's script within a mounted () lifecycle hook.
That would mean once the component loads your loop would trigger. For detailed guidance on this technique the Vue docs are a good first stop, as well as this article.
I was going through an Angular code in one of my existing projects and found below snippet.
We are using Angular material datatable to render the view on the page
export class Component implements OnInit,AfterViewInit{
private dataSource: MatTableDataSource<Product> = null;
#ViewChild(MatPaginator) paginator: MatPaginator;
columnsToDisplay = ['productId','productname'];
constructor(private _service : DataService) { }
ngOnInit() {
this._service.getProducts().subscribe(
((data : Product[]) => this.dataSource = new MatTableDataSource(data)),
() => console.log('THIS IS ERROR')
);
setTimeout(() => this.dataSource.paginator = this.paginator);
//this.dataSource.paginator = this.paginator;
}
ngAfterViewInit() {
this.dataSource.paginator = this.paginator;
}
}
My question is :
1) Since this.service.getData() returns an Observable and subscribe will be called asynchronously whenever the HttpResponse is available ,
does operation inside setTimeout function will be called ONLY AFTER the subscribe method is called ?
2) I have seen that ngAfterViewInit method also contains exactly the same code as in setTimeout method in ngOnInit method
3) But when this method is called (ngAfterViewInit) , this.products is still NULL indicating that subscribe is not yet called '
4) Is that the reason setTimeout is called inside ngOnInit method ?
5)If this is the case , what is the use of ngAfterViewInit method ?
1) It depends. the subscription execute the code only when the action is done. So, when the this.service.getData() has finished its job. The setTimeout do the job after a delay. If the subscription need less time than the setTimeout, it will be executed first.
2) Maybe you were trying to notice when the function is executed?
3) the AfterViewInit is fired multiple times. You can check like this if(!!something) and then execute some code.
4) you should ALWAYS avoid to use settimeout (just use it for debug purposes).
EDIT:
ngOnInit() {
this._service.getProducts().subscribe(
((data : Product[]) => this.dataSource = new MatTableDataSource(data)),
() => console.log('THIS IS ERROR')
);
setTimeout(() => this.dataSource.paginator = this.paginator);
//this.dataSource.paginator = this.paginator;
}
`
Let's simply this code a bit:
ngOnInit() {
this.service.doStuff()
.subscribe(result => {
this.functionA();
},
err => {
//Do other stuff in case of an error
});
this.functionB();
}
functionA(){
console.log("Hello,");
}
functionB(){
console.log("world!");
}
The output of this code will be:
world!Hello,
But why?
That's because of the observable pattern.
You can imagine that as you walking with two people: one that know english, one that doesn't. So even if you say "How are you?" first to the guy who doesn't know english, he will need time to understand what did you say and answer you. At the same time, the other guy (that know english very well) answer you instantly.
The example of functionA and functionB is the same. FunctionA is executed only when the subscription has catch something. That's why it isn't fired first. You can see that putting a debug point here:
ngOnInit() {
this.service.doStuff()
.subscribe(result => {
---> this.functionA();
},
err => {
//Do other stuff in case of an error
});
---> this.functionB();
}
hope to have explained well this.
Now let's move on, let's use the timeout:
ngOnInit() {
this.service.doStuff()
.subscribe(result => {
this.functionA();
},
err => {
//Do other stuff in case of an error
});
settimeout(() => {
this.functionB();
}, 500);
}
Which function will be executed first?
Spoiler: You can't know that.
If you are wondering why, it's easy: You know exactly that the functionB will be called after 500ms, but you can't know how much time will use the subscription to be ready. So if you are lucky, and your subscription usually need about 500ms to complete, you can try to reload the page several time, sometimes you will see Hello, world!, sometimes you will see world!Hello,.
To answer in a better way at your questions: I don't really know why did you put the code like this, literally no idea.
The ngAfterViewInit is a life-cycle called after the ngOnInit, and execute the logic after Angular has fully initialized a component's view.
I will try to simplify the description:
setTimeout puts the inside function in the end of the javascript queue so while javascript process is running it will pop up from the stack and call the operation. anything in the queue will get called only if the stack is empty. so setTimeout tells javascript to hold the this code till you finish your work.
subscribe and observable: observable is async data structure so once you subscribe to it you can never now how much time it will take to call the subscribe method. in other words, subscribe will get called only if as example http response returns.
Back to your question: you can not know when your setTimeout code get called but theoretically it will get called before subscribe (javascript engine faster than http response).
If you need to initialize some data table structure only after you get the data from the http request you should put it inside the subscribe method and no need for setTimeout.
ngAfterViewInit is used by angular to tell the developer that in this stage your view is ready and you can as an example use elementRef.
ngOnInit is used by angular to tell the developer that all the inputs and directives ...etc.
1. no , setTimeout will be called only once and before subscribe as its outside of its context.
2. because of Asynchronous update , if we update properties asynchronously the values will not be updated when the verification loop is running and we get no error.
3. the ViewChild is available only after ngAfterViewInit. it populates the children when creating a view and so they are available earlier.
4. ngOnInit lifecycle hook is triggered before the DOM update operation and will give no error. ngOnInit lifecycle hook is triggered after the bindings have been processed . ngAfterViewInit is invoked when the view is initially rendered i.e. it's called after a component's view, and its children's views, are created.
5. ngAfterViewInit() should be called after a component's view, and its children's views, are created, most importantly children's ngAfterViewInit()s are called before the parent's ngAfterViewInit().