Why 'onSnapshot' google firebase is so fast, instantly fired? - reactjs

I build a simple react app to test google firebase. Anyone have any idea how the onSnapshot method works? I mean, it is so fast (like, instantly), even when I change network to slow 3G in devtools, it still fast. I noticed in XHR tab, when the app made request to firebase, request is still pending, but onSnapshot already fired and change the state in my react app. I thought it will wait until there's response from firebase, but that's not the case.
.firestore()
.collection("expense")
.onSnapshot((snapshot) => {
const newExpense = snapshot.docs.map((doc) => {
return {
id: doc.id,
...doc.data(),
};
});
//set state is fired, but request is still pending
this.setState({expense: newExpense});
});
Did firebase just store the app state somewhere, or just simply read the app state and then update the state (call onSnapshot) when making request? If that's the case then you can not handle network error (or you can but that would be weird), because the app state is already changed.
Did firebase update the app state when this function (function below is to add collection to firebase database) called? and didn't even wait until request is success?
firebase.firestore().collection("expense").add(value);

firebaser here
When you write to Firestore from your application, the Firestore client in that application immediately fires events for the local listeners to that data.
So after your call to firebase.firestore().collection("expense").add(value) the Firestore client does:
Send the new data off to the server.
Calls any local listeners to /expenses straight away.
It's the second step here that makes the local chances show up instantly, as it doesn't wait for the server to complete the write operation.
This leads to a possible problem: what happens if the server rejects the write operation (for example because it conflicts with the security rules of your database)? If that happens, a few more steps are taken:
The server checks the write against the security rules and rejects it.
The client then fires reconciliation events for the local state, so it calls your onSnapshot listener again with the document removed.
Because of these extra steps, it is possible to briefly see the new document appear and then be removed, if it violates the security rules of your app.

Most likely, offline storage is enabled.

I think I found it, thanks to Aprillion for mentioning this explanation
Firebase will cache the data you get, read from that and update the state based on that data while still making network request, that's why it is instantly

Related

Is React reduxtoolkit persist on session storage good location to persist user data?

I am building a fullstack MERN app that allows users to login and perform some actions.
Some components are to display or work with user data like email, name, status etc.
Instead of sending a http request to the backend on every component that needs userData, is it a good practice to persist the userData on #reduxtookit persist not on local storage but session storage because if the user closes the browser without logging out, the data is gone on next session, which sounds good to me as logout would do the same.
So instead of sending a http request with loader function on react router v6.4 on every component. I probably should persist the data on login and use useSelector to access the data from every component that needs to work with it.
My question is: is this a good practice?
I persisted the data on reduxtoolkit persist and the pages were a lot more split second quicker than getting the data straight from the backend.

Handling with external logouts in React app

I just finished my session (cookie) based login system in my React+Redux app.
Then I realized that there is no way how I can check if the user logged out from another location (different chrome tab, removing the cookie, server session invalidation, expiration).
I was looking on Instagram's website what using React too. It seems if you log out in a different browser tab, you can still route to another place in-app until u hit some API fetching... Then website is automatically refreshed...
BUT, there is also some kind of system what realize that user is unlogged even when I do some actions whatnot require API calls.
So how to realize these situations in the best way? How do you handle them when developing.
I'm not exactly sure how Instagrams Authentication works but I'd imagine that it's handled by middleware and when you request an API call, it will check to see if the user has an Auth Token stored in Cookies or whatever before initiating the API Request.
You can easily do this yourself by adding a Redux middleware that checks to see if the cookie is there before dispatching the next action. If it's not there you can return an error message to the user or redirect them or even dispatch a redux action that clears out all loaded data and then finally redirect them back to the login page.
The reason why Instagram is only locking the user when it hits an API call is that you can't really do anything dangerous to the users account if the cookie was deleted as you can't make changes to their account (commenting, posting, changing account settings etc.) without interacting with the API. Therefore, the middleware doesn't have to run every time an action has been dispatched which technically makes their application more performant.
Example Redux middleware
import Cookies from 'js-cookie';
const clientHasToken = store => next => action => {
const authToken = Cookies.get('auth');
if (!authToken) {
// redirects user, but you could do anything here
return window.location.href = '/login';
}
// if user has an auth token, proceed to the next action
next(action);
};
export default clientHasToken;

React-Redux Strategies for Refreshing Cognito IdToken

I'm using the following combination of packages:
react
redux
react-cognito
react-router (v4)
redux-saga
(and I'll disclaim that I'm pretty new with all of these)
Currently I have a PrivateRoute component which will check (and refresh if necessary) an expired IdToken on route changes. This works well.
My problem is if the browser is open past token expiry to a PrivateRoute which is polling my API and sending along the IdToken in its 'Authorization' header. The API will start returning 401.
Options I've Thought of:
Act on API Error
I could catch the 401 error and dispatch an action to refresh the token, but
if there is any issue refreshing the token I wind up in an infinite loop hammering AWS, so need some logic to catch and prevent this. Perhaps redirect to login route if refresh fails?
I then need to add complexity to all my private API calls throughout my app to have this logic, and re-do the requested API call upon successful refresh.
Pre-empt API Error
To me it makes more sense to separate API calls and keeping the auth token valid. Considering react-cognito stores the token expiry time in cognito.user.signInUserSession.idToken.payload.exp, maybe it is possible to pre-empt the API call and expiry.
How to best do this though? At login a refresh action could be 'scheduled' using setTimeout for (currentTime - expiryTime - someBuffer) seconds in the future.
I'm assuming (haven't verified) AWS will let you refresh an IdToken before it expires. I don't want to wait until afterwards else some API calls may have already failed.
are there concerns with using setTimeout with a timeout that may be up to 1 hour long?
Alternatively I could set something up to poll cognito.user.signInUserSession.idToken.payload.exp frequently to detect and refresh an almost-expired token?
Any suggestions?

Why am I receiving a 401 Unauthorized error when requesting API data from React?

To make a long story short, I'm building a server-rendered Express / React application and I'm trying to query the back-end, from the client, for some data. The basic flow looks like this:
User navigates to a client path applications/:id
The Application component is loaded
A GET request is made to the server's API from the Application component lifecycle method componentWillMount
The data from the response is added to Redux
The problem is that the response (step 4) is a 401 (Unauthorized). This is not an issue of being logged in or not. A valid session exists and if I navigate to the API route from the browser, I can see the expected response. As an added note, this application is similar to another application (in nearly ever relevant aspect), which does not have this issue.
Attempted troubleshooting steps:
Verified correct modules and versions
Verified correct API request path and configuration (using Axios and withCredentials config param)
Verified server routing and authentication
Verified PassportJS config
Verified the request session is being lost in the auth process
As it turns out, the problem was that I was making the API request from the componentWillMount React lifecycle method. In other words, since the component in question was being rendered on the server and wasn't yet complete, the API call was being made from the server side as well (the component had not yet mounted), meaning the cookie was not available for the API request. Hence the 401 Unauthorized error.
Reading the docs first would have saved me quite a bit of trouble:
componentWillMount() ...
Avoid introducing any side-effects or subscriptions in this method. For those use cases, use componentDidMount() instead.
This is the only lifecycle hook called on server rendering.

React/Redux server side rendering initial state

I have a React/Redux application that keeps track of a user's authenticated state in local storage. The application is also set up to use server side rendering. I'm running into issues when rendering the initial application state. My server creates a new store and emits the SET_INITIAL_STATE action. This initial action on the client side reads localStorage and passes the authenticated information on to my reducers. The server, however, has no knowledge of this logged in state since I'm using stateless JWT located in local storage for authentication.
Since the server and client are out of sync at this point, I'm getting this error:
React attempted to reuse markup in a container but the checksum was invalid. This generally means that you are using server rendering and the markup generated on the server was not what the client was expecting. React injected new markup to compensate which works but you have lost many of the benefits of server rendering. Instead, figure out why the markup being generated is different on the client or server:
Which makes sense, because the server is trying to render an unauthenticated state.
What is the accepted standard or practice for setting this initial state that relies solely on something the client has access to?
I found that the OP is right, that cookie storage is the best option. If you're using react, redux, and express for a universal app the option that worked for me was https://github.com/eXon/react-cookie.
Essentially:
In you server side code you can use:
import cookie from 'react-cookie';
function handleRender(req, res) {
cookie.setRawCookie(req.headers.cookie);
// Create new Redux store instance
const store = createStore(rootReducer, {
username: cookie.load('username') || '',
accessLevel: cookie.load('accessLevel') || userRoles.public,
});
//other react/redux/express related code
}
In your react applications, inside the components, you can just save the cookies directly:
import cookie from 'react-cookie';
//first parameter is anything you want, second is the data you want to save
cookie.save( 'username', username );
cookie.save('accessLevel', accessLevel);
Further if you want to store an object, as I did, you can pass in JSON.stringify(your object). cookie.load will automatically parse it for you.
In my code I called cookie.save from the actions, but you can call it directly in components or any abstraction you have.
I found a working solution.
The trick, unfortunately, is to store the authenticated state in a cookie so the session state gets sent to the server automatically upon request.

Resources