VueJS 3: update value and avoid watch - javascript

I am coding a table with pagination component, and I use multiple v-model and watch on these variables to fetch the data.
When perPage is updated, I want to reset page to 1. So I did it in my watch method but of course, watch is triggered twice (for perPage and then page).
Is it possible to update the variable and disable watch at this moment ?
Here is my current code:
<script setup lang="ts">
const sort = ref(route.query.sort || 'created_at')
const filters = ref(route.query.filters || {})
const page = ref(route.query.page ? parseInt(route.query.page.toString()) : 1)
const perPage = ref(route.query.per_page ? parseInt(route.query.per_page.toString()) : 10)
watch([sort, filters, page, perPage], ([oldSort, oldFilters, oldPage, oldPerPage], [newSort, newFilters, newPage, newPerPage]) => {
if (oldPerPage !== newPerPage)
page.value = 1
fetchItems()
router.push({
query: {
...route.query,
sort: sort.value,
// filters: filters.value,
page: page.value,
per_page: perPage.value,
},
})
})
async function fetchItems() {
items.value = await userApi.list({
filters: toRaw(filters.value),
sort: sort.value,
page: page.value,
perPage: perPage.value,
})
}
</script>
<template>
<CTable
:pagination-enabled="true"
v-model:sort="sort"
v-model:page="page"
v-model:per-page="perPage"
:total-items="items.meta.total"
:total-pages="items.meta.last_page"
/>
</template>
The only workaround I found is to return when I reset page:
watch(..., () => {
if (oldPerPage !== newPerPage) {
page.value = 1
return
}
fetchItems()
...
})
It is working in my case but for some another cases I would like to update without trigger the watch method.
Thanks!

Create another watcher for perPage :
watch([sort, filters, page, perPage], ([oldSort, oldFilters, oldPage, oldPerPage], [newSort, newFilters, newPage, newPerPage]) => {
fetchItems()
router.push({
query: {
...route.query,
sort: sort.value,
// filters: filters.value,
page: page.value,
per_page: perPage.value,
},
})
})
watch(perPage, (newPerPage,oldPerPage ) => {
if (oldPerPage !== newPerPage)
page.value = 1
})
It's recommended to create watch for a single property separately to avoid unnecessarily updates and conflicts. For the first watch try to replace it with watchEffect like since you're not using the old/new value:
watchEffect(() => {
fetchItems()
router.push({
query: {
...route.query,
sort: sort.value,
// filters: filters.value,
page: page.value,
per_page: perPage.value,
},
})
})

Considering that the state is changed through v-model, it needs to be observed with a watcher.
In order to avoid a watcher to be triggered multiple times, it should implement additional conditions like shown in the question, but it also needs to not skip an update when page is already 1, this won't result in additional update:
if (oldPerPage !== newPerPage) {
page.value = 1
if (newPage !== 1)
return
}
Otherwise there need to be multiple watchers, like another answer suggests. In case a watcher that causes asynchronous side effects (fetchItems) shouldn't be triggered more than it needs, and there are other causes for this to happen besides perPage, it can be debounced, e.g.:
watch(perPage, () => {
page.value = 1
});
watch([sort, filters, page, perPage], debounce(() => {
fetchItems()
...
}, 100));

Thank you for all your answers, I finally found what I was looking for using VueUse - watchIgnorable:
const { stop, ignoreUpdates } = watchIgnorable(page, (value) => {
fetchItems()
})
watch(perPage, (newPerPage, oldPerPage) => {
if (newPerPage !== oldPerPage) {
ignoreUpdates(() => {
page.value = 1
})
}
fetchItems()
})

Related

How do I properly get the state data set with nuxtServerInit in Nuxt

I have a situation where I get data from api in nuxtServerInit and write it to the store's state variable like so
let categoriesService = new Categories(this.$axios);
categoriesService.current().then((resp) => {
if (resp.data.success) {
const navCategoryRoutes = resp.data.items
.filter((item) => {
return item.label !== "" && item.label;
})
.map((item) => {
item.label = item.label.toLowerCase();
item.isSelected = false;
item.url = "/category/" + item.name + "/";
return item;
});
commit("nav/setNavCategoryRoutes", navCategoryRoutes);
}
});
I log it in the setter to the console and I can see that below the assignment declaration and I can see it's there
state: {
categoryRoutes: [
[Object],
[Object],
[Object],
[Object],
[Object]
]
},
In the getter, however, it's always an empty array ;/
export const getters = {
getNavCategoryRoutes(state) {
console.log({ getter: state });
return state.categoryRoutes;
},
};
getter: {
categoryRoutes: []
}
and in the file where I try to use it obviously it is empty as well
categories() {
console.log({
header: this.$store.getters["nav/getNavCategoryRoutes"],
});
return this.$store.getters["nav/getNavCategoryRoutes"];
},
I have tried to fetch it in computed property:
computed: {
categories() {
return this.$store.getters["nav/getNavCategorysRoutes"];
},
},
and in the mounted lifecycle method:
mounted() {
this.categories = this.$store.getters["nav/getNavCategoryRoutes"];
},
But the problem is earlier in the getter I think, I just don't know what am I doing wrong ;/ this getter is no different from the working ones. Would you please point me in the proper direction, I am out of ideas about what to do with that, thanks a lot.
To get data in nuxtServerInit, add nuxtServerInit to actions:
nuxtServerInit({ commit }) {
// get data then commit mutation
})

VueJs do a reload on a simple table from Vuetify

So I have a vuetify simple table that displays available times to book appointments. However, this times are pull from a database and that information get changes every 5 minutes (based on people that booked or cancel). The user will need to refresh the table to get the latest changes. Im trying to introduce some sort of auto refresh in VueJs that reloads the data every 5 minuts. this is my method that is been called right now
created(){
this.fetchAvailableTimeSlotsData75();
},
method:{
fetchAvailableTimeSlotsData75() {
this.$axios.get('appointments75', {
params: {
date: this.isCurrentMonth(this.strSelectedDate) ? '' : this.strSelectedDate,
week: this.intPageNumber
}
})
.then((objResponse) => {
if(objResponse.status == 200){
// console.log(objResponse.data)
this.total = objResponse.data.total;
this.arrAvailableDates = objResponse.data.dates;
this.arrAppointmentsData = objResponse.data.data;
this.getAppointments();
}
})
.catch((objError) => {
})
.finally(() => {
this.blnLoading = false;
this.snackbar = false
});}
}
Whats the best way to approach this in VueJs? Any Ideas?
To put it simply, use setInterval:
var _timerId;
export default {
data: () => ({
pollingInterval: 1000 * 60 * 5
}),
created() {
this.startPolling(true);
},
methods: {
startPolling(init = false) {
if (init) {
// Call it immediately
this.fetchAvailableTimeSlotsData75();
this.startPolling();
return;
}
_timerId = setInterval(this.fetchAvailableTimeSlotsData75, this.pollingInterval);
}
},
// Optional
destroyed() {
clearInterval(_timerId);
}
}

JavaScript, Redux: array not taking value

I am trying to get some statistics and problems for a user using a Redux action and pass it to a React component. The problem is, I have the array of objects curPageExercisesMarked, which I use for the pagination of the page, but it does not take the values I assign it to.
The stranger thing is that the other fields in the Redux store get updated, but not this one. I tried consoling the object in the action, but it just prints this:
It is important to mention that I am doing something similar in another action, using the exact same assignment and it works there. I've lost already an hour trying to figure this thing out so any help is welcomed.
The Redux action:
export const setStatistics = (
problems,
problemsSolved,
filter = ''
) => dispatch => {
let payload = {
subject1: 0,
subject2: 0,
subject3: 0,
total: 0,
exercisesMarked: [],
curPageExercisesMarked: []
};
for (let i = 0; i < problems.length; i++) {
if (problems[i].S === '1' && problemsSolved.includes(problems[i]._id)) {
payload.subject1++;
payload.total++;
payload.exercisesMarked.push(problems[i]);
} else if (
problems[i].S === '2' &&
problemsSolved.includes(problems[i]._id)
) {
payload.subject2++;
payload.total++;
payload.exercisesMarked.push(problems[i]);
} else if (
problems[i].S === '3' &&
problemsSolved.includes(problems[i]._id)
) {
payload.subject3++;
payload.total++;
payload.exercisesMarked.push(problems[i]);
}
}
payload.curPageExercisesMarked = payload.exercisesMarked.slice(0, 10);
dispatch({
type: SET_USER_STATISTICS,
payload
});
};
The redux reducer:
export default function(state = initialState, action) {
const { type, payload } = action;
switch (type) {
case SET_USER_STATISTICS:
return {
...state,
exercisesMarked: payload.exercisesMarked,
curPageExercisesMarked: payload.curPageExercisesMarked,
subject1: payload.subject1,
subject2: payload.subject2,
subject3: payload.subject3,
total: payload.total
};
case CHANGE_PAGE_MARKED:
return {
...state,
page: payload,
curPageExercisesMarked: state.exercisesMarked.slice(
(payload - 1) * state.pages_count,
payload * state.pages_count
)
};
default:
return state;
}
}
This is the part that does not function:
payload.curPageExercisesMarked = payload.exercisesMarked.slice(0, 10);
EDIT
I've discovered that if I go a component which loads all the problems and come back to this component, it actually gets the correct value.
Now, the interesting is that I do get the same problems here as well. Is it the way I use React Hook?
This is the part where I call the redux action in the react component:
const Dashboard = ({
problems: { problems },
auth: { user },
getProblems,
dashboard: {
curPageExercisesMarked,
page,
exercisesMarked,
pages_count,
subject1,
subject2,
subject3,
total
},
setStatistics
}) => {
useEffect(() => {
if (problems === null) {
getProblems();
} else if (user !== null) {
setStatistics(problems, user.problemsSolved);
}
}, [problems, user]);
// rest of the code
}
You can first simplify code as below. Update/Print console.log(JSON.stringify(payload)). I think if(problemsSolved.includes(problems[i]._id)) not working as expected
export const setStatistics = (
problems,
problemsSolved,
filter = ""
) => dispatch => {
let payload = {
subject1: 0,
subject2: 0,
subject3: 0,
total: 0,
exercisesMarked: [],
curPageExercisesMarked: []
};
for (let i = 0; i < problems.length; i++) {
if(problemsSolved.includes(problems[i]._id)) {
payload["subject"+ problems[i].S]++
payload.total++;
payload.exercisesMarked.push(problems[i]);
}
}
payload.curPageExercisesMarked = payload.exercisesMarked.slice(0, 10);
dispatch({
type: SET_USER_STATISTICS,
payload
});
};
// Also
case SET_USER_STATISTICS:
return {
...state,
...payload
};

Render same component with different details each time

I want the same message with different URL, based on a prop. I basically have the below render method, which I call inside my main one.
renderNoBasicMode = () => {
const { securityMode } = this.props;
// Need this, while isFetching securityMode === '',
// Unless, we don't this is rendering on multipel renders.
if (securityMode !== SecurityMode.BASIC && securityMode !== SecurityMode.EMPTY) {
return (
<div className="badge badge-light" data-test="non-basic-mode">
<NoResource
icon="user-o"
title="Non Basic Security Mode"
primaryBtn="New User"
primaryCallback={this.openCreateUserModalPromise}
moreUrl={NonBasicSecurityMode.url}
>
No users available when Lenses is running on {securityMode} security mode.
</NoResource>
</div>
);
}
return null;
};
And I want to display a different url based on the value of the NonBasicSecurityMode, which I have here:
const NonBasicSecurityMode = [
{ securityMode: 'mode1', url: 'https://...' },
{ securityMode: 'mode2', url: 'https://...' },
{ securityMode: 'mode3', url: 'https://...' }
];
The securityMode, is deternment by an API request.
export const securityModeSelector = createSelector(
lensesConfigSelector,
config => (config.&& config['security.mode']) || ''
);
function mapStateToProps(state) {
return {
securityMode: securityModeSelector(state),
};
}
Basically, I tried mapping through them, and a forEach, but I was apparently wrong. Can you help me figure this out? Thanks!!

How to write state changing routine, similar to Redux?

How can I write a really super, simple state changing routine? I need something like Redux, but way simpler, don't need all the bells & whistles.
I was thinking of a global object i.e. myState = {}, that is changed via setMyState() / getMyState().
I'm using JavaScript, and wondering if this would be done via a timer that polls say every 10ms, or so.
So in my JavaScript client app (I'm using ReactJS), a call to my getMyState("show-menu") inside a render() would update the Component's state just like using this.state..
The reason I want this is:
1) Wanna know how to write it for learning purposes.
2) Need something simpler that Redux, simple like Meteor's Session vars, so don't have to pass this.Refs. down to child compnents which setState on parent components.
3) Redux is a mouthful, there is still lots to digest and learn to use Redux.
Seems like you could do this pretty simply with a constructor.
function State () {
this._state = {};
...
}
State.prototype.get = function () {
return this._state;
};
State.prototype.set = function (state) {
return this._state = state;
};
var STATE = new State();
But then you have to do the polling you mentioned in your post. Alternatively, you can look at eventEmitter libraries for javascript, for example https://github.com/facebook/emitter, and turn the State object into an event emitter.
Update
Not sure if this is what you're looking for, at all, but it's simpler.
function makeStore () {
var state = { };
return {
set (key, value) { state[key] = value; },
get (key) { return state[key]; }
};
}
const store = makeStore();
store.set("counter", 1);
store.get("counter"); // 1
Believe it or not, there's really not a lot to Redux.
There's, perhaps, a lot to think about, and it's extra work to keep everything untied from your store...
But have a quick look:
function reducer (state, action) {
state = state || { count: 0 };
const direction = (action.type === "INCREASE") ? 1 : (action.type === "DECREASE") ? -1 : 0;
return {
count: (state.count + direction)
};
}
function announceState () {
console.log(store.getState());
}
function updateView () {
const count = store.getState().count;
document.querySelector("#Output").value = count || 0;
}
function increase () {
store.dispatch({ type: "INCREASE" });
}
function decrease () {
store.dispatch({ type: "DECREASE" });
}
const store = createStore(reducer, { count: 0 });
store.subscribe(announceState)
.subscribe(updateView);
document.querySelector("#Increment").onclick = increase;
document.querySelector("#Decrement").onclick = decrease;
updateView();
This is the code I intend to use.
Looking at it, I'm pretty much just creating a store (with a function to run every time there's an event), there's the subscription to have a listener run, after the store has updated, there's a line where I fire an action, and... ...well, that's it.
function createStore (reduce, initialState) {
var state = initialState;
var listeners = [];
function notifyAll () {
listeners.forEach(update => update());
}
function dispatch (event) {
const newState = reduce(state, event);
state = newState;
notifyAll();
return store;
}
function subscribe (listener) {
listeners.push(listener);
return store;
}
function getState () {
return state;
}
const store = {
getState, subscribe, dispatch
};
return store;
}
// THIS IS MY APPLICATION CODE
function reducer (state, action) {
state = state || { count: 0 };
const direction = (action.type === "INCREASE") ? 1 : (action.type === "DECREASE") ? -1 : 0;
return {
count: (state.count + direction)
};
}
function announceState () {
console.log(store.getState());
}
function updateView () {
const count = store.getState().count;
document.querySelector("#Output").value = count || 0;
}
function increase () {
store.dispatch({ type: "INCREASE" });
}
function decrease () {
store.dispatch({ type: "DECREASE" });
}
const store = createStore(reducer, { count: 0 });
store.subscribe(announceState)
.subscribe(updateView);
document.querySelector("#Increment").onclick = increase;
document.querySelector("#Decrement").onclick = decrease;
updateView();
<button id="Decrement">-</button>
<output id="Output"></output>
<button id="Increment">+</button>
The very tiny, very easy implementation of a store (note that the real thing is more complex) is above. dispatch and subscribe are very useful, here.

Categories