API call occurs duplicate contents - api

I use VueJS for a project and Axios for API call. I don't know how to execute this code once. When I go in my home page this code is executed, I go to another page, I go back and this code is executed again. How to cancel execution ?
mounted () {
axios
.get( config.API.projects )
.then(response => {
this.SET_PROJECTS( response.data );
const projects = this.$store.getters["projects/projects"];
projects.forEach( project => {
this.SET_SKILLS( project.skills )
});
this.SET_SHOW_LOADER( false );
})
.catch( ( error ) => {
console.log( error );
alert( "Une erreur est survenue, merci de contacter par mail l'administrateur de ce site" )
});
},

Assuming Your VUEX getter "projects/projects" is an array, and is not populated before the call, you can just do a check to see if it has any data. If it is empty (length equal to zero) you want to get the data and populate it with it. Next time you enter this view, the check will fail, since the store is already populated with the results. (length of projects is greater than 0)
mounted () {
const projects = this.$store.getters["projects/projects"];
if (projects.length === 0) {
axios.get( config.API.projects ).then(response => {
this.SET_PROJECTS( response.data );
projects.forEach( project => {
this.SET_SKILLS( project.skills )
});
this.SET_SHOW_LOADER( false );
})
.catch( ( error ) => {
console.log( error );
alert( "Une erreur est survenue, merci de contacter par mail l'administrateur de ce site" )
});
}
},
Bonus tip: Be carefull with using .catch. I assume you want to "catch" only if there is any errors with getting the data with axios. What you are also doing, it actually catching/silencing every error from the line axios.get(... to the line .catch. This means that if let's say you get an error inside the .then function, eg: "SET_PROJECTS" is not an function and your app breaks, you will not get it in the console
In your case, you will. But only because you console.log the error parameter from the .catch. If you did not do this, you would never know there was any errors.
To fix this, simply change .catch to the 2nd parameter of the .then function:
if (projects.length === 0) {
axios.get( config.API.projects ).then(response => {
this.SET_PROJECTS( response.data );
projects.forEach( project => {
this.SET_SKILLS( project.skills )
});
this.SET_SHOW_LOADER( false );
}, error => {
console.log( error );
alert( "Une erreur est survenue, merci de contacter par mail l'administrateur de ce site" )
})
}

Related

How to insert data from other tables in a third table in frontend project with vue

I have 4 tables, one for users, one for students, one for family of students, and the other one for the people that make registrations called "Auxiliar" in the code. I need to create the relation between familiar and students into a third table. There, i'd put through an input the students's ID, familiar's ID and the auxiliar's ID because she/he is the person with the authorization or token to make the registration. In the backend everything goes perfect but I have mistakes with the code in frontend. I got the error 500. Here's the code:
this is part where is the form where I send the input
enter code here
<form v-on:submit.prevent="processEstudianteFamiliar">
<label>Ingrese el Id del estudiante</label>
<input type="number" v-model="asignacion.estudiante"/>
<label>Ingrese el Id del familiar</label>
<input type="number" v-model="asignacion.familiar"/>
<label>Ingrese el Id del auxiliar</label>
<input type="number" v-model="asignacion.registra"/>
<button type="submit">Asignar</button>
</form>
Then this is the script
import axios from 'axios';
export default {
name: "EstudianteFamiliar",
data: function ()
{
return {
asignacion:{
estudiante: "",
familiar: "",
registra: ""
},
loaded: false,
}
},
methods:{
verifyToken : async function() {
return axios.post("http://127.0.0.1:8000/refresh/",
{refresh: localStorage.getItem("token_refresh")},
{headers:{}}
)
.then((result) => {
console.log("New access token");
localStorage.setItem("token_access", result.data.access);
})
.catch((error) => {
this.$emit("logOut");
})
},
processAsignarFamiliar: async function() {
if (localStorage.getItem("token_access") === null ||
localStorage.getItem("token_refresh") === null){
this.$emit('logOut');
return;
}
await this.verifyToken();
let token = localStorage.getItem("token_access");
console.log(this.asignacion,2)
console.log(token)
axios.post(
"http://127.0.0.1:8000/estudianteFamiliar/",
this.asignacion,
{headers: {'Authorization': `Bearer ${token}`} }
)
.then((result)=>{
let dataEstudianteFamiliar={
token_access: result.data.access,
token_refresh: result.data.refresh
}
console.log(result.data)
console.log(this.asignacion),
this.$emit('completedEstudianteFamiliar', dataEstudianteFamiliar);
})
.catch((error)=>{
console.log(error);
alert("Error: Falló la asignación del familiar del estudiante");
});
},
created: async function () {
this.processEstudianteFamiliar();
}
}
}
I'm doing something wrong in the frontend because when i test in postman the backend runs ok and create the relation in the database. I haven't found out what it is. The error send me to this in the backend and I guess i'm making the relation wrong in the code.
token = request.META.get('HTTP_AUTHORIZATION')[7:]
auxValido = validateAuxiliar(token, request.data['asignacion']['registra'])
the key error is the ['asignacion']
Thank you so much I'd apreciate your helph.

How in store object to get access to parent vue page?

In my vue 2.6/cli 4/vuex 3.1 app I update some data in store vuex, like in src/store/index.js :
userPropStore(context, userProp ) {
bus.$emit( 'beforeUserPropStore', userProp );
let apiUrl = process.env.VUE_APP_API_URL
Vue.http.post(apiUrl + '/personal/user-props', userProp).then(({data}) => {
let userProps= this.getters.userProps
userProps.push({
"id": data.user_props.id,
"name": data.user_props.name,
"value": data.user_props.value,
"created_at": data.user_props.created_at,
})
this.commit('refreshUserProps', userProps);
bus.$emit( 'onUserPropStoreSuccess', data );
}, error => {
console.log('context::')
console.log(context)
console.error(error)
self.$refs.userObserverForm.setErrors(error.body.errors)
});
}, // userPropStore(context, userProp ) {
and using ValidationProvider of vee-validate 3.2 I want to catch server errors (like not unique item)
but I got error :
index.js?4360:847 Uncaught (in promise) TypeError: Cannot read property 'userObserverForm' of undefined
on line
self.$refs.userObserverForm.setErrors(error.body.errors)
If there is a way in store object to get access to parent page, maybe with context, which has : https://imgur.com/a/P4St8Ri
?
Thanks!
This is a very strange implementation.
Obviously self.$refs.userObserverForm.setErrors(error.body.errors) fails because you are NOT in a component, which is where $refs is available.
What you have to do in your catch block is setting the errors in Vuex and then make your component read from there.
Pseudocode follows:
I don't understand what your bus is doing... I guess you use it to send data to your component, but why use Vuex then?
userPropStore(context, userProp ) {
bus.$emit( 'beforeUserPropStore', userProp );
let apiUrl = process.env.VUE_APP_API_URL
Vue.http.post(apiUrl + '/personal/user-props', userProp).then(({data}) => {
let userProps= this.getters.userProps
userProps.push({
"id": data.user_props.id,
"name": data.user_props.name,
"value": data.user_props.value,
"created_at": data.user_props.created_at,
})
this.commit('refreshUserProps', userProps);
bus.$emit( 'onUserPropStoreSuccess', data );
commit('SET_USER_PROPS, data)
}, error => {
console.log('context::')
console.log(context)
console.error(error)
commit('SET_USER_PROPS_ERRORS', error.body.errors)
});
}
The way I've done this in the past is to return the promise that Vue.http.post produces and then do what you want in your component with the failure information:
userPropStore(context, userProp ) {
bus.$emit( 'beforeUserPropStore', userProp );
let apiUrl = process.env.VUE_APP_API_URL
//add return to the line below
return Vue.http.post(apiUrl + '/personal/user-props', userProp).then(({data}) => {
Then over in your component:
loadData() {
this.$store.dispatch('userPropStore').catch((error) => {
this.$refs.userObserverForm.setErrors(error.body.errors)
//any other things you want to do "in component" goes here
});
}

Vue VeeValidate - How to handle exception is custom validation

I have a custom validation in VeeValidate for EU Vat Numbers. It connects to our API, which routes it to the VIES webservice. This webservice is very unstable though, and a lot of errors occur, which results in a 500 response. Right now, I return false when an error has occured, but I was wondering if there was a way to warn the user that something went wrong instead of saying the value is invalid?
Validator.extend('vat', {
getMessage: field => 'The ' + field + ' is invalid.',
validate: async (value) => {
let countryCode = value.substr(0, 2)
let number = value.substr(2, value.length - 2)
try {
const {status, data} = await axios.post('/api/euvat', {countryCode: countryCode, vatNumber: number})
return status === 200 ? data.success : false
} catch (e) {
return false
}
},
}, {immediate: false})
EDIT: Changed code with try-catch.
You can use:
try {
your logic
}
catch(error) {
warn user if API brokes (and maybe inform them to try again)
}
finally {
this is optional (you can for example turn of your loader here)
}
In your case try catch finally block would go into validate method
OK, first of all I don't think that informing user about broken API in a form validation error message is a good idea :-| (I'd use snackbar or something like that ;) )
any way, maybe this will help you out:
I imagine you are extending your form validation in created hook so maybe getting message conditionaly to variable would work. Try this:
created() {
+ let errorOccured = false;
Validator.extend('vat', {
- getMessage: field => 'The ' + field + ' is invalid.',
+ getMessage: field => errorOccured ? `Trouble with API` : `The ${field} is invalid.`,
validate: async (value) => {
let countryCode = value.substr(0, 2)
let number = value.substr(2, value.length - 2)
const {status, data} = await axios.post('/api/euvat', {countryCode: countryCode, vatNumber: number})
+ errorOccured = status !== 200;
return status === 200 ? data.success : false;
},
}, {immediate: false})
}
After searching a lot, I found the best approach to do this. You just have to return an object instead of a boolean with these values:
{
valid: false,
data: { message: 'Some error occured.' }
}
It will override the default message. If you want to return an object with the default message, you can just set the data value to undefined.
Here is a veeValidate v3 version for this:
import { extend } from 'vee-validate';
extend('vat', async function(value) {
const {status, data} = await axios.post('/api/validate-vat', {vat: value})
if (status === 200 && data.valid) {
return true;
}
return 'The {_field_} field must be a valid vat number';
});
This assumes your API Endpoint is returning json: { valid: true } or { valid: false }

Cypress hangs in loop when running custom Chai assertion

I have been trying to create my own custom chai assertion (based on the Cypress recipe template: https://github.com/cypress-io/cypress-example-recipes/blob/master/examples/extending-cypress__chai-assertions/cypress/support/index.js).
What I have found with the code below is that when it is run I end up with a constant loop of WRAP, if I swap this.obj with element it then results in a constant stream of GET. I do not seem to ever progress further than getRect(first).then((actual)
If anyone could help me out I'd be very grateful.
cypress/integration/test.js
describe('testing custom chai', () => {
it('uses a custom chai helper', () => {
cy.visit('https://www.bbc.co.uk/news');
cy.get('#orb-modules > header').should('be.leftAligned', '#orb-header');
});
});
cypress/support/index.js
function getRect(selector) {
if (selector === '&document') {
return cy.document().then(doc => doc.documentElement.getBoundingClientRect());
} if (typeof selector === 'string') {
return cy.get(selector).then($elem => $elem[0].getBoundingClientRect());
}
return cy.wrap(selector).then(elem => Cypress.$(elem)[0].getBoundingClientRect());
}
function getRects(first, second) {
return getRect(first).then((actual) => {
getRect(second).then(expected => [actual, expected]);
});
}
const aligned = (_chai, utils) => {
function leftAligned(element) {
getRects(element,this.obj).then((rects) => {
this.assert(
rects[0].left === rects[1].left,
'expected #{this} to be equal',
'expected #{this} to not be equal',
this._obj,
);
});
}
_chai.Assertion.addMethod('leftAligned', leftAligned);
};
chai.use(aligned);
The basic problem is that the async commands cy.get(), cy.wrap(), cy.document() can't be used in the custom assertion. My best guess is that the auto-retry mechanism is going bananas and giving you the constant loop.
Instead, you can use Cypress.$() which is the synchronous version (essentially jquery exposed on the Cypress object).
The following seems to work ok. (I renamed getRects() param to subject, as sometimes it's a selector and sometimes it's the object passed in to .should()).
Note also this._obj instead of this.obj.
function getRect(subject) {
if (subject === '&document') {
return Cypress.$(document).context.documentElement.getBoundingClientRect();
}
if (typeof subject === 'string') { // the selector passed in to assertion
return Cypress.$(subject)[0].getBoundingClientRect();
}
if (typeof subject === 'object') { // the element from cy.get() i.e this._obj
return subject[0].getBoundingClientRect();
}
return null; // something unkown
}
function getRects(first, second) {
const actual = getRect(first)
const expected = getRect(second)
return [actual, expected];
}
const aligned = (_chai, utils) => {
function leftAligned(element) {
const rects = getRects(element, this._obj)
this.assert(
rects[0].left === rects[1].left,
'expected #{this} to be equal',
'expected #{this} to not be equal',
this._obj,
);
}
_chai.Assertion.addMethod('leftAligned', leftAligned);
};
chai.use(aligned);
I was unable to test your BBC page directly, as there's a cross-origin problem occurring
Refused to display 'https://www.bbc.com/news' in a frame because it set 'X-Frame-Options' to 'sameorigin'
but it does work with a mockup page
cypress/app/bbc-sim.html
<div id="orb-modules">
<header>
<h1>Brexit: Boris Johnson's second attempt to trigger election fails</h1>
</header>
</div>
and testing like so
it('uses a custom chai helper', () => {
cy.visit('app/bbc-sim.html')
cy.get('#orb-modules > header').should('be.leftAligned', '#orb-modules');
});

cypress custom find command

I have a custom command that gets me my elements with the data-cy attribute.
Cypress.Commands.add("getById", (id) => {
cy.get(`[data-cy=${id}]`)
})
everything's working fine.
Now it would be nice if I had the same with find. It would be looking like this:
Cypress.Commands.add("findById", { prevSubject: true }, (subject, id) => {
cy.wrap(subject).find(`[data-cy=${id}]`)
})
The problem there is that cypress throws an error with this code:
cy.root().then((root) => {
if(root.findById("...").length) {
...
}
})
The error is "root.findById" is not a function.
Can you help me write that custom command correctly?
The basic problem is that subject passed in to the command is already wrapped, so just chain the find() from it. Also you need to return the result to use it in the test.
Custom command
Cypress.Commands.add("findById", { prevSubject: true }, (subject, id) => {
return subject.find(`[data-cy=${id}]`)
})
The next problem is you can't mix 'ordinary' js code with Cypress commands, so the returned value must be accessed from a .then().
Spec
describe('...', () => {
it('...', () => {
cy.visit('app/find-by-id.html')
cy.root().findById('2').then(el => {
console.log('found', el, el.length)
expect(el.length).to.eq(2)
})
})
})
Html used to test the test (app/find-by-id.html)
<div>
<div data-cy="1"></div>
<div data-cy="2"></div>
<div data-cy="2"></div>
<div data-cy="3"></div>
</div>
Adding to #Richard Matsen's answer, you might want to add some log into your command, so that it appears well in your cypress log, just as if you had used .find(...) directly:
Cypress.Commands.add(
"findByTestId",
{
prevSubject: ["element"],
},
(
subject: Cypress.Chainable<HTMLElement>,
testId: string,
options?: Partial<
Cypress.Loggable &
Cypress.Timeoutable &
Cypress.Withinable &
Cypress.Shadow
>
) => {
const $el = subject.find(`[data-testid=${testId}]`, options);
Cypress.log({
$el: $el as any,
name: "findByTestId",
message: testId,
});
return $el;
}
);