I am currently working on a stenciljs App and would like to get some info via a REST service. This REST call is implemented as a function which returns a Promise. Now, if I use this rest call in componentwillload Lifecycle Method, it works fine:
async componentWillLoad() {
return getUserData().then( userData => {
console.log("got userdata", userData);
});
}
Now, I would like to get this info when the user clicks a button. For that, I call this method in the click handler for this button:
<button id="mybutton" onClick={()=>this._handleButtonClick()></button>
_handleButtonClick() {
return getUserData().then( userData => {
console.log("got userdata", userData);
});
}
But this does not work, I see in the Browser console Network Tab that a network call is made but it returns no data. How can I make this thing work
Looks like you are missing closing brace (})?
onClick={()=>this._handleButtonClick() <- here
but assuming that is just a typo, maybe you can just store the result of getUserData in state object (that way, it will trigger render() function as your data change)?
Here is how I'm doing the fetch (just as an example)
fetch(`REST endpoint`, {
method: 'GET'
headers: { // Content-Type and security token }
}).then ((response: Response) => response.json())
.then(data => {
this.dataList = data;
});
(so here, I'm storing result as "dataList", which is defined with #State() dataList)
I noticed your button doesn't have an attribute type="button".
You could be running into an edge case scenario where if a button is the first button within a form, and isn't marked as type="button", the browser defaults it to <button type="submit">. At that point, clicking on the button might trigger the API call because of the registered listener, but it'll also trigger a form submit.
Since API calls take some time to complete, by the time the response is returned your page may have reloaded because of a form submit.
Again, this is an edge case scenario but looks like a reasonable explanation for what you're observing.
Related
I have a component that I want to cover with some e2e tests. This component takes the URL provided by the user in the input, calls the API after the button click and then returns the shortened version of that URL. After that, shortened url is added to the list below the input on the UI and makes some localStorage assertion. I want Cypress to wait for the API response and only then check the UI if the list item was added. I made this working but I hardcoded the wait time in the wait() method. How Can I achieve that programatically ?
describe("Shortener component", () => {
it("Should add the list item and data to localStorage", () => {
cy.visit("http://127.0.0.1:5500"); //Live server extension address
cy.get("#url-input").type("https://facebook.com");
cy.get("#form-submit-button").click();
// wait for the api response and make sure that the value has been added to the localStorage
cy.wait(40000); //todo - wait for the api response instead of hardcoding the wait time
const localStorageData = localStorage.getItem("linksData");
if (JSON.parse(localStorageData)) {
expect(JSON.parse(localStorageData)[0].inputValue).to.eq(
"https://facebook.com"
);
}
// check if the new list item with the corrct value has been addded
cy.get(".shortener-component__list-item")
.contains("https://facebook.com")
.should("be.visible");
//validation mesasge should not be visible
cy.get("#validationMesage")
.contains("Please add a valid link")
.should("not.be.visible");
});
});
I tried with intercept() however I failed. Not sure how to make it working. I also saw some similar SE topics on that but it did not help me.
Any ideas / examples apreciated :)
Thx !
From the order of events you've given
short URL returned
added to localStorage
added to list
just change the order of feature testing
test list - it is last event, but has retriable commands (you can increase the timeout)
now test localStorage, if UI has the short URL so will localStorage
cy.contains('.shortener-component__list-item', 'https://facebook.com', { timeout: 40000 })
.then(() => {
// nested inside .then() so that the above passes first
const localStorageData = localStorage.getItem("linksData");
const linksData = JSON.parse(localStorageData);
expect(linksData).not.to.eq(undefined);
expect(linksData[0].inputValue).to.eq("https://facebook.com");
})
Alternatively, to make use of retry and timeout on the localStorage check,
cy.wrap(localStorage.getItem("linksData"))
.should('not.eq', undefined, { timeout: 40000 }) // will retry the above until true
.then(data => JSON.parse(data))
.should(parsedData => {
expect(parsedData.inputValue).to.eq("https://facebook.com");
});
I guess you should also start the test with
cy.clearLocalStorage("linksData")
There're examples in the documentation, it only takes some reading and experimentation.
In general, you need three commands: cy.intercept(), .as(), and cy.wait():
cy.intercept(your_url).as('getShortenedUrl');
cy.wait('#getShortenedUrl');
you can also use .then() to access the interception object, e.g. a response:
cy.wait('#getShortenedUrl').then(interception => { });
or you can check something in the response using .its():
cy.wait('#getShortenedUrl').its('response.statusCode').should('eq', 200);
The point is that after cy.wait('#getShortenedUrl'), the response has been received.
I need help in async/await implementation. In my code, I want a clear button to clear everything and reload the page. I want to set this up as a promise such that only when it is fulfilled, will it send a get request to the JSON server and get the posts from the JSON server and store them in local storage.
I have set up a console.log message to pop up when the promise is resolved. Now the problem I am experiencing is this message appears and the data is fetched first then the page reload happens after. I thought the page reload would happen first and only then will the message appear and the data would get fetched. this makes the code not to function as I want. I don't know if it is the way I have set up the Promise. I hope my problem is understood. Someone help, I promise to upvote your answer and account :)
const UISelectors =uisetup.getSeletor(); //gets html classes and ids from another module
document.querySelector(UISelectors.clearBtn).addEventListener('click', clearAll); //calls event
const clearAllData = async function(){
//This is the promise supposed to be waited on
const clears = new Promise((resolve)=>{
itemsetup.clearAll();
uisetup.clearEverything()
storagesetup.clearAllfromStorage()
const addAllItems = 0
uisetup.theTotaldatas(addAllItems)
uisetup.clearEditState()
location.reload()
resolve('Finished clearing')
})
const storagedata = await clears
return storagedata
}
//Function for the clear button event
function clearAll(e){
e.preventDefault()
//Calls the function after it has been completed
clearAllData()
.then(clearmessage=> {
http.get('http://localhost:3000/posts')
.then(posts => {
storagesetup.storeinStorage(posts)
uisetup.populateItems(posts)
let datas=null
posts.forEach(post =>{
datas += post.datas
})
uisetup.theTotaldatas(datas)
console.log(posts)
})
.catch(err=>console.log(err))
console.log(clearmessage)
})
}
By looking at the following part I can say that you believe your JS process resumes after page reload.
location.reload()
resolve('Finished clearing')
This is not so. It works the other way round. That is - you create a page load hook which will execute when a page reloads and call your promise in there, take any action after the promise is resolved.
<body onload="clearAll()">
Let this function fetch data and store it in the local storage. You will have to modify your function accordingly.
I'm sending data between a parent window and an embedded <iframe> using window.postMessage(). The parent window listens for a "mounted" message from the <iframe>, and sends a response message with a JavaScript object model as the data.
Here's the parent window event listener for the "mounted" message:
window.addEventListener('message', (event) => {
if (hasOwnProperty.call(event.data, 'mounted')) {
$('iframe')[0].contentWindow.postMessage({ model }, '*');
}
});
The <iframe> may send the "mounted" message several times in one session, and the parent window needs to respond with model each time.
However, on the initial page load, the parent window asynchronously retrieves model from an external API, which could resolve before or after the parent window receives a "mounted" message.
let model = {};
axios.get('/api/model')
.then((response) => {
model = response.data;
});
So, if the parent window received a "mounted" message before the model exists, I want my code to wait until model does exist and then send the response. How can I achieve this? I assume it's going to involve changing my postmessage({ model }, '*') call into a Promise, but I'm not sure how to attach that Promise to make it wait for the axios.get call to complete.
I'm aware of how Promise.all() works, but that wouldn't really work since the "mounted" message can be sent multiple times in a window session.
One solution
While writing this question, I realized that I could write a function that sends model only if model isn't empty, which both my "mounted" event listener and my asynchronous data retrieval could call. So, the message is always posted when the data is loaded from the external API (and it doesn't matter if the <iframe> hasn't mounted), and the event listener only responds once the model has loaded. Here's the complete code for the solution:
function postModel(model) {
if (!_.isEmpty(model)) {
$('iframe')[0].contentWindow.postMessage({ model }, '*');
}
}
let model = {};
axios.get('/api/model')
.then((response) => {
model = response.data;
postModel(model);
});
window.addEventListener('message', (event) => {
if (hasOwnProperty.call(event.data, 'mounted')) {
postModel(model);
}
});
This works for my purposes, but I'd still be interested to see if there is an async/await approach that would be better here.
Maybe you would find want you need with frameTalk.js.
FrameTalk supports Promises between iframes. So, you would do smth like that
frameTalk.sendPromise(window.top, "myFrameID", "methodNameToRun", ["methodAttributesToPass"])
.then(doOnSuccess, doOnFail);
see more at
https://github.com/inedu/frameTalk
I created a small sample application using VueJs and created a C# REST API to store and retrieve data in a SQL Server back end.
For testing, I created a simple web page with a form to create a "note". The note is stored by the following function, 'saveData()':
saveData()
{
let promiseStack = [];
var jsondata = JSON.stringify(this.note);
promiseStack.push(this.$http.post('REST_API/note', jsondata));
Promise.all(promiseStack).then(data =>
{
this.$http.get('REST_API/note');
this.$router.push({ name: 'viewnotes', params: { id: data[0].body.id }})
}, error =>
{
console.log(error);
});
}
I tried to use a promise to wait until the 'store' operation in the backend is complete, and issue a GET request to retrieve all notes once the promise is fulfilled.
However, the get request inside the promise doesn't return any data. If I issue the get request manually later one, I retrieve the data that was stored previously.
So I had look into the C# REST API. There are currently two functions: createNote(...), getAllNotes(...). I used a StreamWriter to log to the filesystem when these functions are called, using milisecond precision. What I see is that 'createNote' is called after 'getAllNotes'. So I suspect that the API is working correctly, but something with the way I'm using promises seems to be awfully wrong.
Maybe somebody has a hint?
UPDATE
I know that the GET request doesn't return any data by using the developer toolbar in Chromium. The response is empty
The developer toolbar in the network tab shows that the requests are submitted in the correct order, so the "POST" request is issued first
It seems I found the problem. I had a 'href' tag in my 'Save' link, which triggered an early routing. The intended 'POST' and 'GET' were fired correctly, but there was another 'GET' inbetween somewhere because of the 'href' tag in the link, even though it was empty.
I removed the tag, now it works as intended.
I'm working on change feeds, I have an API within which a change feed is triggered. The console.logs within the change feed is printed only when the API is called and that change feed happens. The next time the changefeed occurs nothing happens.
exports.trackUser = (id) => {
return r.table(USERS_TABLE).get(id).changes()
.then((feeds) => {
console.log(feeds)
feeds.each((err, feed) => {
if (err) {
logger.error(err)
}
if (feed) {
console.log("change feed")
}
})
})
.error(logger.error);
}
This is the API. In this, once the API is called and then there is an update in the user table, I see the logs. Again if there is an update I don't see the logs. How to make this work each time there is an update in the USER_TABLE? Specific to that ID?
You need to call .run(conn) on the query you've constructed in order to send it to the server. Unless I'm mistaken and the JS driver automatically runs a query if you attach .then.