Rethinkdb point changefeed is not consistent - javascript

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.

Related

firebase severtimestamp delay (w/ vanilla JS)

I am trying to create a live chat with vanilla JS and firebase.
I'm able to add and get the "newest" message render to the DOM with the following codes:
add message
async addChat(message) {
const chat = {
message,
createdAt: serverTimestamp(),
};
return await addDoc(this.chat, chat);
}
get message
getChat(syncChat) {
onSnapshot(this.chat, snapshot => {
snapshot.docChanges().forEach(shot => {
if (shot.type === "added") {
syncChat(shot.doc.data());
}
});
});
}
render message to the DOM
render(data) {
const html = `
<li class="chat-item">
<span class="chat-message">${data.message}</span>
<p class="chat-time">${formatDistanceToNow(data.createdAt.toDate(), {
addSuffix: true,
})}</p>
</li>
`;
this.chatList.innerHTML += html;
}
Like I said, I can render the chat message no problem, but I realize the there's a time delay when message is added to firebase and timestamp is created. So when the first new message is displayed on the DOM, the time actually return null, and I have to refresh the page in order to display the actually time.
I am just wondering if there's anyway to fix that? I tried looking through the firebase doc and got nothing. I also did a little bit of digging on the interest but most of them are using a framework and not vanilla js.
Thanks for the help.
SOLUTION
getChat(showChat) {
const q = query(
this.collection,
where("room", "==", this.room),
orderBy("createdAt", "asc")
);
this.unsub = onSnapshot(q, snapshot => {
snapshot.docChanges().forEach(snap => {
if (snap.type === "added") {
showChat(snap.doc.data({ serverTimestamps: "estimate" }));
}
});
});
}
Thanks for the help! I have decide to use the snapshotoptions, the estimate servertimestamps, and everything works great.
When you perform a write operation on a client, the Firestore SDK immediately invoked all local listeners with the new data - something typically referred to as latency compensation within Firebase.
So when you write a new document with a server-side timestamp, your onSnapshot listener gets called before the request is sent to the server, and at that point the value of the timestamp field will be null, since it hasn't been calculated yet.
Then when the server confirms that the write operation was completed, and what the timestamp value was, your onSnapshot listener gets called again, now with the correct value for the timestamp field. Since this is a change to the document you already got before, it is an event with type changed.
To display the update, your code needs to also handle events where the type is changed, and use that to update the DOM element that it added for the first event.
Alternatively you can ask the Firestore SDK to give you an estimated timestamp on the first event by passing an option to the data() call as shown here: Firebase Cloud Firestore how to set snapShotOptions

Data from firestore it's being fetched multiple times when login and logout (vanilla JS)

Well I made this Library app, where an user can login and add books. So, when a user login the app fetch data from a firestore collection, cool. The problem exists when the user login once, logout and then login again without refreshing the app. If the user do this twice, the fetch twice, if thrice, the fetch thrice. The code that executes multiple times its the fetchBooks(), the signInWithGoogle() only executes once. Here's the code involved:
function signInWithGoogle(){
const provider = new firebase.auth.GoogleAuthProvider()
auth.signInWithPopup(provider)
.then(result => {
// Create the new user document in firestore
createNewUserDocument(result.user)
// fetch feed data
auth.onAuthStateChanged(user =>{
user? fetchBooks() : null
})
}).catch(err => {
console.log(err)
})
signUpForm.reset()
signUpModal.hide()
signInForm.reset()
signInModal.hide()
}
function fetchBooks() {
const docRef = db.collection('users').doc(auth.currentUser.uid).collection('books')
docRef.get().then(querySnapshot =>{
console.log(querySnapshot)
querySnapshot.forEach(doc => {
const data = doc.data()
console.log(doc.data());
addCardToHTML(data.title, data.author, data.pages, data.description, data.read)
})
})
}
onAuthStateChanged is a subscription that triggers itself when there's a change in the user's authentication state.
So it will trigger when you log in, when you log out, etc.
So ideally you'd want to wait until the user logs in, and then call the fetchBooks() function, but if you keep doing it inside of the subscriber the function will trigger any time the subscriber emits a new value.
I would recommend starting with a restructure of your code to have functions that do individual things. Right now, you have a function signInWithGoogle. That function should only sign the user in with Google and return a promise with the result of that sign in. Instead, you have it signing in the user, fetching books (which itself is also fetching books AND modifying the DOM), and calling methods on your signUp elements.
Restructuring this to have some other top-level function would likely help you handle your problem easier. Specifically, try something like this:
function handleSignIn() {
signInWithGoogle()
.then(fetchBooks)
.then(books => {
books.forEach(book => addCardToHTML(...))
})
}
This is a good start because now it's clear what each individual function is doing. So now to handle your specific issue, I'll assume that the problem you're facing is that you're seeing the books be added multiple times. In that case, I would think what you'd want to happen is that:
When a user is signed in, you want to load their books and display them on the page.
When they log out, you want the books to be unloaded from the screen
When they log back in, the books are re-loaded and displayed.
If all of those assumptions are correct, then your problem wouldn't be with the code you have, but rather the signout functionality. When the user signs out, you need to add a function that will remove the books from the HTML. That way, when they sign back in after signing out, the handleSignIn function will kick off again and the addCardToHTML function will be running on a blank HTML page rather than a page that already has the cards.
Example:
function handleSignOut() {
signOut()
.then(clearBookCards)
}
function clearBookCards() {
// Manipulate DOM to remove all of the card HTML nodes
}

Using promise on button click in stenciljs

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.

Cypress - wait for the API response and verify UI changes

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.

Firestore: What is the efficient way to list messages in chat?

I am trying to create a chat like app using Firestore and trying to list all messages in chat and updates it every time when a message is added.
I tried this way first to implement it.
mounted() {
docRef.collection('messages').orderBy('timestamp', 'desc').limit(1).onSnapshot((querySnapShot) => {
querySnapShot.forEach((doc) => {
if (!doc.metadata.hasPendingWrites) {
this.messages.push(doc.data())
}
})
})
}
This way seems efficient because this way only gets the latest message in collection. However, I found out this way has a problem. When a user refreshs a page, the user can't get the past messages.
So I changed it like this.
mounted() {
docRef.collection('messages').orderBy('timestamp', 'asc').onSnapshot((querySnapShot) => {
querySnapShot.docChanges().forEach((change) => {
if (change.type === 'added') {
this.messages.push(change.doc.data())
}
})
})
}
This way works as I expected. But this way needs a lot of requests because every time the collection is changed I need to read all the documents in the collection.
What is the efficient way to list messages in chat?
I thought it works if I get all the current messages first and set the listener for new messages but the listener is triggered immediately after I enter the page even though there is no change in the collection and read the latest message twice.
I ended up just using a flag to check whether the initial trigger is done. I don't know if this is smart way but this works.
// Get all current messages
docRef.collection('messages').orderBy('timestamp', 'asc').get().then((querySnapShot) => {
querySnapShot.forEach((doc) => {
this.messages.push(doc.data())
})
})
// Update for new messages (Skip the initial loading)
docRef.collection('messages').orderBy('timestamp', 'desc').limit(1).onSnapshot((querySnapShot) => {
querySnapShot.forEach((doc) => {
if (!doc.metadata.hasPendingWrites && this.isInitialDone) {
this.messages.push(doc.data())
}
this.isInitialDone = true
})
})
You are going to need to either set an appropriate limit, or add a filter to determine which ones you want.
If you want to go with a size limit, just change limit(1) in your first query to the number you actually want.
If you want to go with a filter, you should probably use the timestamp you already have in place in order to determine how far back in the past you would like to go.
Those are your two options for limiting the size of the results. You can use either one or both together, but there is no other way of limiting the size of the result set.

Categories

Resources