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.
Related
I tried to set the interval for the function to delete it when the component will be destroyed but get this error. And can't find any solution for this.
My interval function:
<script>
export default {
data: () => ({
ordersInterval: null,
}),
async fetch() {
const data = await this.$axios.post(`${this.apiURL}orders`, {
per_page: this.$store.state.pagination.per_page,
page: this.$store.state.pagination.current_page,
})
this.orders = data.data.data
this.$store.dispatch('changePaginationData', {
paginationData: data.data.meta,
})
this.ordersInterval = setInterval(() => {
this.filterOrders()
}, 10000)
},
}
</script>
How can I fix this error?
ESlint is complaining and marking it as an error.
It's probably because fetch() needs to know when the fetching is done at some point, for helpers like $fetchState.pending especially.
I've used a setInterval in some of my code but it's called on an event. You could eventually have a watcher and call the setInterval when it's true (toggling it in your fetch() hook).
If you can, try to use websockets or a system a bit more flexible than polling.
Polling can be tricky to write also (with this), here is a good answer on how to to write it: https://stackoverflow.com/a/43335772/8816585
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 have the following code with Pusher:
Echo.private("channel").listen(".foobar", (e) => {
this.fetchData();
});
When there is an incoming request, I want data to be fetched again. But not on every Pusher event. I want the data to be fetched only once in 5 seconds.
I was thinking about a interval that is reset when there is a new incoming event, but how to achieve this?
Ok, first of all it's a Laravel Echo code. It may be using pusher behind the scenes, but saying it's a pusher code is slightly incorrect.
Then, if I understand you right, you need some kind of debounce function. For example lodash offers one.
const debouncedFunction = _.debounce(() => {
this.fetchData()
}, 5000);
Echo.private('channel').listen('.foobar', e => {
debounced()
})
debounce will create debounced version of function which you can then call, or even cancel, because it comes with cancel method.
debouncedFunction.cancel()
I am building an online boardgame usin create-react-app, react hooks, and am using sockets.io to transmit data (player location, active player, etc.) between connected users. The flow of the logic is that a user makes a choice, that choice gets added to an array in state, and then the updated state is pushed via sockets to all connected users. The problem is that the useEffect listener that is in charge of receiving the socket data from the back end and updating the user data on each connected user is firing too many times instead of just once.
Code:
Send call to the back end:
try {
console.log(currentCard, typeof(currentCard.title), tech)
setUser1Data({
...user1Data,
userTech: [...user1Data.userTech, currentCard.title]
});
} finally {
console.log(user1Data)
socket.emit("p1state", user1Data);
pass();
}
The back end receiver/emitter:
socket.on("p1state", function(state) {
console.log(state)
io.emit("p1state", state)
})
The client listener:
useEffect(() => {
socket.on("p1state", state => {
console.log("1")
setUser1Data({...user1Data, state});
});
}, [user1Data]);
Some "interesting" things I noticed: this useEffect is being fired too many times. The first time it fires it sets everything the way it should, but then each subsequent time it overwrites the previous setting, reverting to the original user1Data state object.
Also, on the back end, I have a console.log firing when a client connects. Even though I am testing only locally with one browser tab at the moment, it is still logging several user connected events.
The useEffect is currently using the state in the dependency array and is setting the same state in the updater function. As you can see, this leads to an infinite loop.
useEffect(() => {
socket.on("p1state", state => {
console.log("1")
setUser1Data(userData => ({...userData, state}));
});
}, []);
Instead you can use the function version of state setter so that it gives you the accurate prevState instead of relying on state representation in closure.
I had a similar problem. I solved it making the useEffect close the socket every time it gets unmounted (and open/reopen after every mount/update. This was my code:
useEffect(()=>{
const socket = io("http://localhost:3000")
socket.on(userId, (arg) => {
//stuff
});
return () => socket.emit('end'); //close socket on unmount
})
Is there any event to listen data on every update whether it is exactly the same data or not?
Let me explain what I want to achieve.
If I set the data as shown below on every 1 second interval
Write Data
setInterval(() => {
firebaseRef.child('liveUser').set({ userId: 1});
}, 1000);
How can I listen liveUser on every update? Right now with my current implementation firebase stop .on('value') event if the value is duplicate.
Read Data
firebaseRef.child('liveUser').on('value', (snapshot) => {
console.log(snapshot.val());
})
I am aware of alternative solution of using timestamp with liveUser object, but it would be helpful if firebase already provides solution for duplicate data.
By design, a listener is only called when data is changed. Writing identical data to the same location does not change the data and thus doesn't trigger attached listeners.
To trigger attached listeners, you need to make a change to the node:
setInterval(() => {
firebaseRef.child('liveUser').set({
userId: 1,
timestamp: Math.floor(Date.now())
});
}, 1000);