React provides stale state in setState() - javascript

I have a scenario where I have a set of data where a filename maps to a set of graph line data. On a checkbox change,
it will either delete from or add to the map (not a JS map - just a JS object). The add operation works fine, at first.
The delete operation also appears to work, and after deleting a filename from the map the React state appears to have
updated correctly. However, when adding another item react seems to "resurrect" a very old state that represents the
collection of all files ever added to the map. This behavior is caused by a specific setState() call, which I highlight
below. Been going round in circles debugging this and have hit a dead end. The reason state is cloned in the console.log()
call is that I found out that the Chrome console.log calls are asynchronous.
I was being a bit more choosey about what I was deep cloning but to remove any uncertainty I just deep cloned everything.
What exactly break is.
Select file1.
1.1 file 1 data displayed on graph
Select file2.
2.1 file 2 data displayed on graph
Delete file 2
3.1 Only file 1 data not displayed in graph
Delete file 1
4.1 Nothing displayed on graph. this.state appears to reflect this both in the console.log output and the
react development tools inspection of the element state.
Select any file, but lets say file 3
5.1 Graph displays data for file1, file2, and file3.
5.2 This is wrong - in 4.1 saw that the state showed no graph data was mapped. The error is introduced
by a setState() call that only updates one, unrelated flag, that is used to show a modal dialog.
If I remove the call this.setState({fetching_data: true},... from getFIleData() then everything works. For some reason
if that call is there, it receives as older state.
If anyone can shed light on this, I would be most grateful, as I have run out of ideas ;)
class ResultsList extends React.Component
{
state = {
fetching_data: false,
// Data on all available files, for which server can provide data
//
// [ {
// date: (5) [mm, dd, hh, mm, ss]
// pm: "pmX"
// fullname: "mm_dd_hh_mm_ss_pmX_TAG"
// tag: "TAG"
// serno: "1234"
// },
// ...
// ]
data : [],
selected_col: "",
// Contents of data files that have been requested from server. If item is in this
// dictionary then is is "selected", i.e. displayed on the graph. When "deselected"
// should be removed from this dictionary.
//
// { filename1 : {
// data: {dataset1: Array(45), dataset2: Array(45), xvalues: Array(45)}
// path: "blah/blah"
// status: 200
// status_str: "ok"
// >>>> These bits are augmented, the above is from server
// FAM_colour: string,
// HEX_color: string,
// <<<<
// },
// filename2 : {
// ...
// }
// }
file_data: {},
file_data_size: 0,
graph: null,
};
createGraphDataSetsFromFileData = (srcFileData) => {
const newGraphDatasets = [];
let idx_prop = 0;
for (var prop in srcFileData) {
if (Object.prototype.hasOwnProperty.call(srcFileData, prop)) {
newGraphDatasets.push(
{
label: 'dataset1_' + prop,
fill: false,
lineTension: 0.5,
backgroundColor: 'rgba(75,192,192,1)',
borderColor: srcFileData[prop].FAM_colour,
borderWidth: 2,
data: srcFileData[prop].data['dataset1'],
}
);
newGraphDatasets.push(
{
label: 'dataset2_' + prop,
fill: false,
lineTension: 0.5,
backgroundColor: 'rgba(75,192,192,1)',
borderColor: srcFileData[prop].HEX_colour,
borderWidth: 2,
data: srcFileData[prop].data['dataset2'],
}
);
idx_prop = idx_prop + 1;
}
}
return newGraphDatasets;
};
getFIleData = (filename) => {
console.log("GETTING OPTICS");
console.log(cloneDeep(this.state));
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// If I remove this setState call then everything works
//
// Display the "fetching data" modal dialog
this.setState({fetching_data: true},
() => {
console.log("££££££ DIALOG SHOW COMPLETED ££££££"); console.log(cloneDeep(this.state));
}
);
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
fetch(`http://${this.SERVER_IP_PORT}/api/v1/get_result/${filename}`)
.then(response => response.json())
.then(json => {
console.log("JSON REPLY RECEIVED")
if (json.status !== 200) {
alert("Failed to fetch results list")
}
else {
const EXPECTED_NO_CYCLES = 45 // Oh so terribly hacky!!!
if (json.data['dataset1'].length === EXPECTED_NO_CYCLES) {
this.setState( (prevState) => {
console.log("JSON SETSTATE PREV STATE AS"); console.log(prevState);
// Clone the file_data map and add the new data to it
let newFileData = cloneDeep(prevState.file_data);
newFileData[filename] = cloneDeep(json); // TODO - FIXME - BAD this is a shallow copy
newFileData[filename].FAM_colour = COLD_COLOURS[prevState.file_data_size % COLD_COLOURS.length];
newFileData[filename].HEX_colour = WARM_COLOURS[prevState.file_data_size % WARM_COLOURS.length];
// Create new graph data from the file_data map
let newGraph = null;
if (newGraph === null) {
newGraph = {
labels : cloneDeep(json.data['xvalues']),
datasets: this.createGraphDataSetsFromFileData(newFileData)
}
}
else {
newGraph = cloneDeep(prevState.graph)
newGraph.labels = cloneDeep(json.data['xvalues']);
newGraph.datasets = this.createGraphDataSetsFromFileData(newFileData)
}
const retval = {
file_data: newFileData,
file_data_size: prevState.file_data_size + 1,
graph : newGraph
};
console.log("------- returning:"); console.log(retval);
return retval;
}, () => {console.log("££££££ OPTICS STAT EUPDATE APPLIED ££££££"); console.log(cloneDeep(this.state)); });
}
else {
alert("Assay test run contains imcomplete data set");
}
}
})
.catch( error => {
alert("Failed to fetch results list: " + error);
})
.finally( () => {
console.log("##################################################################")
this.setState({fetching_data:false},
() => {console.log("££££££ OPTICS STAT FINALLY NOT FETCH APPLIED ££££££"); console.log(cloneDeep(this.state)); });
});
};
//
// THIS FUNCTION IS THE onChange FOR A CHECKBOX and is passed the filename of the item clicked
handleRowSelectionChange = (fullname) => {
if (this.state.file_data.hasOwnProperty(fullname)) {
console.log("CHECKBOX NOT TICKED")
this.setState(
(prevState) => {
// Delete the file from the map
let newFileData = cloneDeep(prevState.file_data);
delete newFileData[fullname];
let rv = {
file_data: newFileData,
file_data_size: prevState.file_data_size - 1,
graph : {
datasets: this.createGraphDataSetsFromFileData(newFileData),
labels: cloneDeep(prevState.graph.labels)
}
}
console.log("______"); console.log(rv);
return rv;
},
() => {
console.log("======== DELETE UPDATE APPLIED =======");
console.log(cloneDeep(this.state));
}
);
}
else {
console.log("CHECKBOX IS TICKED");
this.getFIleData(fullname);
}
}
If I select two files, I expect 4 datasets in the graph and the state reflects this:
If I then delete these lines, I expect to see no graph data, and the state appears to reflect this:
But! If I then click on a third file...
The old state is introduced, specifically by
this.setState({fetching_data: true},
() => {
console.log("££££££ DIALOG SHOW COMPLETED ££££££"); console.log(cloneDeep(this.state));
}
);
in the getFileData function. If this is removed, then no stale state is introduced.

Related

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

Vuex Getters duplicating array values

I have a simple lesson creator where I allow teachers to make selections for various categories, those selections ids are collected and grouped together and I want to return them all at the end as a lesson plan.
However, I am having a strange problem that I can't figure out. My Vuex store shows the selections correctly, however my getter duplicates all of my arrays.
After selections are made, my Vuex store shows something like this through the Vue.js dev-tools plugin:
lesson_store:Object
lesson:Object
selected_event:1
selected_exploration:Array[1]
0:1
selected_extensions:Array[1]
0:1
selected_goals:Array[1]
0:54
selected_lexis:Array[1]
0:2
store.js state and getter:
const state = {
lesson: {
selected_event: '',
selected_exploration: [],
selected_extensions: [],
selected_goals: [],
selected_lexis: [],
}
};
getSelections(state) {
console.log('GETTER SELECTIONS', state.lesson);
return state.lesson
}
My call to getSelections from lesson.vue file:
<template><button #click="saveLesson">Save</button></template>
methods: {
saveLesson () {
console.log('GET RETURN OF SELECTIONS',this.$store.getters["getSelections"]);
},
}
Now my console output is:
lesson_store:Object
lesson:Object
selected_event:1
selected_exploration:Array[2]
0:1
0:1
selected_extensions:Array[2]
0:1
0:1
selected_goals:Array[2]
0:54
0:54
selected_lexis:Array[2]
0:2
0:2
The thing is, none of my other getters behave this way. This getter is super basic.
When I check out store and getSelections getter in the Vue.js dev-tools the values are correct and there are no duplicates.
Any advice or direction you can provide would be much appreciated.
UPDATE::::::
Actions and Mutations for Lesson_Store
// create mutations
const mutations = {
setSelectedEvent(state, payload) {
// state.lesson.selected_event = payload
if (state.lesson.selected_event === payload) {
state.lesson.selected_event = '';
} else {
state.lesson.selected_event = payload
}
},
setSelectedReading(state, payload) {
if (state.lesson.selected_reading === payload) {
state.lesson.selected_reading = '';
} else {
state.lesson.selected_reading = payload
}
},
setSelectedLexis(state, payload) {
// if event is already in array, then remove it with filter
// otherwise push it to the array
if (state.lesson.selected_lexis.includes(payload)) {
state.lesson.selected_lexis = state.lesson.selected_lexis.filter(function (item) {
return item !== payload;
});
} else {
state.lesson.selected_lexis.push(payload);
}
// state.lesson.selected_lexis = payload
},
setSelectedExplorations(state, payload) {
// if event is already in array, then remove it with filter
// otherwise push it to the array
if (state.lesson.selected_exploration.includes(payload)) {
state.lesson.selected_exploration = state.lesson.selected_exploration.filter(function (item) {
return item !== payload;
});
} else {
state.lesson.selected_exploration.push(payload);
}
// state.lesson.selected_lexis = payload
},
setSelectedQuestions(state, payload) {
// if event is already in array, then remove it with filter
// otherwise push it to the array
if (state.lesson.selected_questions.includes(payload)) {
state.lesson.selected_questions = state.lesson.selected_questions.filter(function (item) {
return item !== payload;
});
} else {
state.lesson.selected_questions.push(payload);
}
// state.lesson.selected_lexis = payload
},
setSelectedPerformances(state, payload) {
// if event is already in array, then remove it with filter
// otherwise push it to the array
if (state.lesson.selected_performances.includes(payload)) {
state.lesson.selected_performances = state.lesson.selected_performances.filter(function (item) {
return item !== payload;
});
} else {
state.lesson.selected_performances.push(payload);
}
},
setSelectedExtensions(state, payload) {
// if event is already in array, then remove it with filter
// otherwise push it to the array
if (state.lesson.selected_extensions.includes(payload)) {
state.lesson.selected_extensions = state.lesson.selected_extensions.filter(function (item) {
return item !== payload;
});
} else {
state.lesson.selected_extensions.push(payload);
}
},
setSelectedGoals(state, payload) {
// if event is already in array, then remove it with filter
// otherwise push it to the array
if (state.lesson.selected_goals.includes(payload)) {
state.lesson.selected_goals = state.lesson.selected_goals.filter(function (item) {
return item !== payload;
});
} else {
state.lesson.selected_goals.push(payload);
}
},
};
// create actions
const actions = {
setSelectedEvent({commit}, payload) {
commit('setSelectedEvent', payload);
},
setSelectedReading({commit}, payload) {
commit('setSelectedReading', payload);
},
setSelectedLexis({commit}, payload) {
commit('setSelectedLexis', payload);
},
setSelectedExplorations({commit}, payload) {
commit('setSelectedExplorations', payload);
},
setSelectedQuestions({commit}, payload) {
commit('setSelectedQuestions', payload);
},
setSelectedPerformances({commit}, payload) {
commit('setSelectedPerformances', payload);
},
setSelectedExtensions({commit}, payload) {
commit('setSelectedExtensions', payload);
},
setSelectedGoals({commit}, payload) {
commit('setSelectedGoals', payload);
},
};
All of these appear to be working correctly because my vuejs dev tools display all of the selection id's properly.
To anyone having similar issues where your dev tools store does not match your actual store values output, it is probably due to your code not updating the store values formally through the actions and mutations approach.
if this store value is ever updated directly without actions and mutations the value in the store will change, however, those updated values will not be detected by vuejs dev tools and your actual store data and dev tools data values will not match.

How do I use axios response in different components without using export?

As the tittle says, I would like to be able to use the same axios response for differents components.
I have some restrictions like, I'm onlyl able to use react by adding scripts tags to my html so things like exports or jsx are impossible for me.
This is my react code:
class User extends React.Component {
state = {
user: {}
}
componentWillMount() {
console.log(localStorage.getItem("user"))
axios.get('http://localhost:8080/dashboard?user=' + localStorage.getItem("user"))
.then(res => {
const userResponse = res.data
setTimeout(() =>
this.setState({user: userResponse.user}), 1000);
})
}
render () {
const {user} = this.state
if (user.fullName === undefined)
return React.createElement("div", null, 'loading..');
return React.createElement("span", {className: "mr-2 d-none d-lg-inline text-gray-600 small" }, user.fullName);
}
}
ReactDOM.render( React.createElement(User, {}, null), document.getElementById('userDropdown') );
class Roles extends React.Component{
state = {
user: {}
}
componentWillMount() {
console.log(localStorage.getItem("user"))
axios.get('http://localhost:8080/dashboard?user=' + localStorage.getItem("user"))
.then(res => {
const userResponse = res.data
setTimeout(() =>
this.setState({user: userResponse.user}), 1000);
})
}
render () {
const {user} = this.state
const roles = user.user.roles.map((rol) => rol.roleName)
if (user.fullName === undefined)
return React.createElement("div", null, 'loading..');
return React.createElement("a", {className: "dropdown-item" }, user.fullName);
}
}
ReactDOM.render( React.createElement(Roles, {}, null), document.getElementById('dropdownRol') );
I would like to be able to manage different components(rendering each one) with data of the same axios response.
Is this possible considering my limitations?
Thanks in advance
Here's a working example of how you might do it. I've tried to annotate everything with comments, but I'm happy to try to clarify if you have questions.
// Fake response object for the store's "load" request
const fakeResponse = {
user: {
fullName: "Carolina Ponce",
roles: [
{ roleName: "administrator" },
{ roleName: "editor" },
{ roleName: "moderator" },
{ roleName: "generally awesome person" }
]
}
};
// this class is responsible for loading the data
// and making it available to other components.
// we'll create a singleton for this example, but
// it might make sense to have more than one instance
// for other use cases.
class UserStore {
constructor() {
// kick off the data load upon instantiation
this.load();
}
// statically available singleton instance.
// not accessed outside the UserStore class itself
static instance = new this();
// UserStore.connect creates a higher-order component
// that provides a 'store' prop and automatically updates
// the connected component when the store changes. in this
// example the only change occurs when the data loads, but
// it could be extended for other uses.
static connect = function(Component) {
// get the UserStore instance to pass as a prop
const store = this.instance;
// return a new higher-order component that wraps the connected one.
return class Connected extends React.Component {
// when the store changes just force a re-render of the component
onStoreChange = () => this.forceUpdate();
// listen for store changes on mount
componentWillMount = () => store.listen(this.onStoreChange);
// stop listening for store changes when we unmount
componentWillUnmount = () => store.unlisten(this.onStoreChange);
render() {
// render the connected component with an additional 'store' prop
return React.createElement(Component, { store });
}
};
};
// The following listen, unlisten, and onChange methods would
// normally be achieved by having UserStore extend EventEmitter
// instead of re-inventing it, but I wasn't sure whether EventEmitter
// would be available to you given your build restrictions.
// Adds a listener function to be invoked when the store changes.
// Called by componentWillMount for connected components so they
// get updated when data loads, etc.
// The store just keeps a simple array of listener functions. This
// method creates the array if it doesn't already exist, and
// adds the new function (fn) to the array.
listen = fn => (this.listeners = [...(this.listeners || []), fn]);
// Remove a listener; the inverse of listen.
// Invoked by componentWillUnmount to disconnect from the store and
// stop receiving change notifications. We don't want to attempt to
// update unmounted components.
unlisten = fn => {
// get this.listeners
const { listeners = [] } = this;
// delete the specified function from the array.
// array.splice modifies the original array so we don't
// need to reassign it to this.listeners or anything.
listeners.splice(listeners.indexOf(fn), 1);
};
// Invoke all the listener functions when the store changes.
// (onChange is invoked by the load method below)
onChange = () => (this.listeners || []).forEach(fn => fn());
// do whatever data loading you need to do here, then
// invoke this.onChange to update connected components.
async load() {
// the loading and loaded fields aren't used by the connected
// components in this example. just including them as food
// for thought. components could rely on these explicit fields
// for store status instead of pivoting on the presence of the
// data.user object, which is what the User and Role components
// are doing (below) in this example.
this.loaded = false;
this.loading = true;
try {
// faking the data request. wait two seconds and return our
// hard-coded data from above.
// (Replace this with your network fetch.)
this.data = await new Promise(fulfill =>
setTimeout(() => fulfill(fakeResponse), 2000)
);
// update the loading/loaded status fields
this.loaded = true;
this.loading = false;
// call onChange to trigger component updates.
this.onChange();
} catch (e) {
// If something blows up during the network request,
// make the error available to connected components
// as store.error so they can display an error message
// or a retry button or whatever.
this.error = e;
}
}
}
// With all the loading logic in the store, we can
// use a much simpler function component to render
// the user's name.
// (This component gets connected to the store in the
// React.createElement call below.)
function User({ store }) {
const { data: { user } = {} } = store || {};
return React.createElement(
"span",
{ className: "mr-2 d-none d-lg-inline text-gray-600 small" },
user ? user.fullName : "loading (User)…"
);
}
ReactDOM.render(
// Connect the User component to the store via UserStore.connect(User)
React.createElement(UserStore.connect(User), {}, null),
document.getElementById("userDropdown")
);
// Again, with all the data loading in the store, we can
// use a much simpler functional component to render the
// roles. (You may still need a class if you need it to do
// other stuff, but this is all we need for this example.)
function Roles({ store }) {
// get the info from the store prop
const { data: { user } = {}, loaded, loading, error } = store || {};
// handle store errors
if (error) {
return React.createElement("div", null, "oh noes!");
}
// store not loaded yet?
if (!loaded || loading) {
return React.createElement("div", null, "loading (Roles)…");
}
// if we made it this far, we have user data. do your thing.
const roles = user.roles.map(rol => rol.roleName);
return React.createElement(
"a",
{ className: "dropdown-item" },
roles.join(", ")
);
}
ReactDOM.render(
// connect the Roles component to the store like before
React.createElement(UserStore.connect(Roles), {}, null),
document.getElementById("dropdownRol")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="userDropdown"></div>
<div id="dropdownRol"></div>

How to update values in table with this.state?

I make a component, which show information from database in table. But this information with filters.
Filtering can be by event type and by participant (id: integer type).
When I click the button, I call handleShowClick(). In this function I check: if value of type event isn't null, I get from database events with this type. if value of type event is null, I get all events.
After this I check a participant value. If value isn't null, I call function, which search which events are include this participant. Data from this.state.event show in table in another component.
I haven't problems with event type. But I have problem with participant. When I choose one of participant, table shows correct data for a split second. After this return to prev state (without filter by participants).
How can I fix this issue? I set state to event only in this component
class TestPage extends Component {
constructor(props) {
super(props);
this.state = {
event: [],
searchByType: null,
searchByParticipant: null,
participantToEvent: []
};
this.handleShowClick = this.handleShowClick.bind(this);
this.onHandleEventByTypeFetch = this.onHandleEventByTypeFetch.bind(this);
this.handleParticipantSearch = this.handleParticipantSearch.bind(this);
this.onHandleEventFetch = this.onHandleEventFetch.bind(this);
}
handleShowClick() { // onClick
if (this.state.searchByType !== null) {
this.onHandleEventByTypeFetch(); // select * from ... where type=...
} else {
this.onHandleEventFetch(); // select * from ...
}
if (this.state.searchByParticipant !== null) {
this.handleParticipantSearch();
}
}
handleParticipantSearch() {
const list = [];
this.state.participantToEvent.map(itemP => { // participantToEvent is binding table
if (itemP.parid === this.state.searchByParticipant) {
this.state.event.map(itemEvent => {
if (itemEvent.id === itemP.eventid) {
list.push(itemEvent);
}
});
}
});
console.log(list); // here I see array with correct result
this.setState({ event: list });
}
onHandleEventFetch() {
fetch( ... , {
method: 'GET'
})
.then((response) => {
if (response.status >= 400) {
throw new Error('Bad response from server');
}
return response.json();
})
.then(data => {
if (data.length === 0) {
alert('nothing');
} else {
this.setState({
event: data
});
}
});
}
onHandleEventByTypeFetch() {
fetch( ... , {
method: 'GET'
})
.then((response) => {
if (response.status >= 400) {
throw new Error('Bad response from server');
}
return response.json();
})
.then(data => {
if (data.length === 0) {
alert('nothing');
} else {
this.setState({
event: data
});
}
});
...
}
}
Structure of this.state.event:
[{id: 1, name: 'New event', participant: 5, type: 10}, ...]
Structure of this.state.participantToEvent:
[{id: 1, idparticipant: 5, idevent: 1}, ...]
this.setState(...this.state,{ event: list });
I think this would solve your problem. Because you clear every item except for {event:list} by not copying the previous state.
Edit:
You should put
...this.state
to onHandleEventByeTypeFetch and onHandleEventFetch. Without them when you click handleShowClick one of those two functions always work and clears searchByParticipant data from the state by not copying the previous state.
The reason for you see the correct data for a short time is all about async nature of the state.

Asynchronously updating Zabuto Calendar

I have Zabuto calendar showing booking dates, but some tours have many dates for the period, and loading them is slow.
I have changed the API to paginate the data, and send a next url if there is more data to load, but I can't see how to get zabuto calendar to update its data once it is displayed, and with Javascript being the ultimate asynchronous programming language, I figured there must be a way the calendar can display and upload data at the same time.
Posts such as this
How to load data from ajax to zabuto calendar plugin?
shows how to load the calendar data by ajax call, but not how to continuously upload more data asynchronously while the current calender data is displayed. Other posts indicate that the only way is to reload the entire calendar
reloading AJAX data for Zabuto Calendar after modal dismissal. I would prefer an asynchronous way.
The previous developer started to use the Vue framework, So what I have is a Zabuto calendar Vue module
<template>
<div id="my-calendar-a"></div>
</template>
<script>
import Vue from 'vue'
import moment from 'moment'
import { mapGetters } from 'vuex'
export default {
name: 'ZabutoCalendar',
methods: {
initialise: function () {
$('.calendar-month-navigation .glyphicon').click(function () {
Vue.bus.$emit('calendar-change-month')
})
}
},
props: ['tour'],
computed: {
...mapGetters('cart', [
'cartItems'
]),
...mapGetters('calendar', [
'tourDates'
])
},
mounted: function () {
var self = this
var currentDate = new Date()
/*
Use of the thrid party plugin zabuto calendar to
set up the calendar and check if dates are being clicked
https://github.com/zabuto/calendar
*/
var nextUrl = '/api/check-dates?year=' + moment().format('YYYY') +
'&month=' + moment().format('M') + '&tour=' + self.tour;
this.$store.dispatch('calendar/getTourDates', nextUrl).then(response => {
// I tried putting a while nextUrl loop here, but the calender wont display till dispatch returns
nextUrl = self.tourDates[1].next_url;
$(self.$el).zabuto_calendar({
data: self.tourDates[0].tourdates,
weekstartson: 0,
show_previous: false,
year: currentDate.getFullYear(),
month: currentDate.getMonth() + 1,
action: function () {
if ($(this).find('> div').hasClass('start_spots')) {
// reconstruct data
var selectedTour = {}
var id = this.id
var elem = $('#' + id)
$('.calendar-dow .selected').removeClass('selected')
$(this).find('> div').addClass('selected')
selectedTour = _.find(self.tourDates[0].tourdates, { 'tour_date_id': elem.data('tour_date_id') })
selectedTour['styled_date'] = moment(elem.data('date')).format('Do MMMM YYYY')
if ($(this).find('> div').hasClass('start_future')) {
selectedTour['available'] = 1
for (var index in self.cartItems) {
if (self.cartItems[index].date === elem.data('date')) {
selectedTour['available'] = 3
break
}
}
} else {
selectedTour['available'] = 2
}
self.$store.commit('calendar/setSelectedTour', selectedTour)
Vue.bus.$emit('date-click')
}
}
})
// while loop could surround above code
})
}
}
</script>
And a javascript module to do the ajax call to get all the data in one go
import axios from 'axios'
import moment from 'moment'
export const calendar_module = {
namespaced: true,
state: {
tourDates: [],
selectedTour: {}
},
getters: {
tourDates: (state) => {
return state.tourDates
},
selectedTour: (state) => {
return state.selectedTour
}
},
mutations: {
setSelectedTour (state, selectedTour) {
state.selectedTour = selectedTour
},
setTourDates (state, tourDates) {
state.tourDates = tourDates
}
},
actions: {
getTourDates ({ commit }, datesUrl) {
var response_data = axios.get(datesUrl).then((response) => {
commit('setTourDates', response.data)
});
return response_data;
}
}
}
The API response data is returned in the form
{ 'tourdates': array_data_object, 'next_url', url_string }
with next_url (within the response) set to an empty string if there is no more data. getTourDates actually returns the API response. I tried putting a while nextUrl loop around the code where commented, but zabuto calendar does not display till the dispatch function returns.
Does Zabuto Calendar have a built in way to asynchronously update its data while displaying? Otherwise how else can I get it to asynchronously display and load future dates?
Another way would be to get the ajax call to run several concurrently, and just return null in any that are redundant, but I would prefer to query the database first to see how many pages are needed, and would prefer not to waste an ajax call just to find out how many asynchronous hits are needed to get all data.
I could not put in a while nextUrl loop, so tried axios.all() instead, which allows for asynchronous calling of multiple gets at the same time. This made no improvement of load time, which was probably just as well, because it forced me to look at my REST API which had several inefficiencies that I would not have otherwise cleaned up.
This is my Async solution (which I no longer needed to use once the API was nice and quick) for posterity
import axios from 'axios'
import moment from 'moment'
export const calendar_module = {
namespaced: true,
state: {
tourDates: [],
selectedTour: {}
},
getters: {
tourDates: (state) => {
return state.tourDates
},
selectedTour: (state) => {
return state.selectedTour
}
},
mutations: {
setSelectedTour (state, selectedTour) {
state.selectedTour = selectedTour
},
setTourDates (state, tourDates) {
if (state.tourDates.length == 0) {
state.tourDates = tourDates[0].tourdates;
} else {
state.tourDates = state.tourDates.concat(tourDates[0].tourdates);
}
}
},
actions: {
getTourDates ({ commit }, datesUrl) {
var response_data = axios.all([
axios.get(datesUrl + '&page=1'),
axios.get(datesUrl + '&page=2'),
axios.get(datesUrl + '&page=3'),
axios.get(datesUrl + '&page=4'),
axios.get(datesUrl + '&page=5'),
axios.get(datesUrl + '&page=6')
]).then(axios.spread(function (response1, response2, response3, response4, response5, response6) {
commit('setTourDates', response1.data);
commit('setTourDates', response2.data);
commit('setTourDates', response3.data);
commit('setTourDates', response4.data);
commit('setTourDates', response5.data);
commit('setTourDates', response6.data)
}));
return response_data;
}
}
}

Categories