Apollo graphql-connected React component isn't rerendering - reactjs

I'm working on building an authentication mechanism over graphql, and I'm trying to figure out why a graphql-connected component isn't rerendering after relogin... for example:
At /users I have a react-apollo graphql-connected Users component that requests a list of users.
The server will return an authentication error if no token is provided; the component renders a <Redirect> to the /login route; after successful login, the Login component stashes the token and redirects back.
The Users component wrapper starts the graphql request and renders "loading..." in the meantime. When the graphql request succeeds, it rerenders and the users appear, yay.
If you Sign Out from that page, the Logout component at /logout discards the token then redirects back; the Users component's request gets the authentication error again and sends you to '/login'.
After another successful login, you're sent back to '/users'; again it starts a graphql request to the server (and renders "loading..."), but this time, when the request succeeds, the component isn't updated again.
I can see that the graphql request succeeds (and have watched in Chrome's debugger as the APOLLO_QUERY_RESULT action is handled by react-apollo's reducer and updates the state in the store).
I've tried to find where React is checking to see if the component's props have changed, but I'm enough of a React debugging noob that I haven't figured out where to find that code in the web inspector: maybe something I don't understand about how React is packaged for distribution.
I clear out the ApolloClient's store (by calling resetStore()) on both login and logout, since I don't want to accidentally reuse data from the other authenticatedness; however, removing these calls doesn't get rid of the problem. Interestingly (?), if I force the connection to bypass the cache by providing { options: { fetchPolicy: 'network-only' } } in the graphql() call, the problem goes away. (Not a viable solution - I'd like to benefit from the cache generally.)
I've built a stripped-down example: https://github.com/bryanstearns/apollo-auth-experiment
and you can see it in a CodeSandbox: https://codesandbox.io/s/m4nlpp86j (you'll probably want to access the running example from an independent browser window at https://m4nlpp86j.codesandbox.io/ because the sandbox editor kinda makes the web debug extensions act weird).

To fix this behavior, modify your graphql HOC to include notifyOnNetworkStatusChange in the options.
export const Users = graphql(usersQuery, { options: { notifyOnNetworkStatusChange: true } })(RawUsers);
I don't think you should have to do that -- I think data.loading is supposed to accurately reflect the status of your query regardless of that option being set to true, unlike data.networkStatus but it looks like it's a known bug.
As far as resetStore -- it doesn't actually wipe your entire store, but rather wipes away your store and refetches all your active queries. If you wanted to blow away your store, since you're already integrating Redux with Apollo, the easiest thing to do would be to create an action to do that.
You may also want to consider a session-based authentication mechanism, rather than relying on the client to persist a token. It's pretty simple to implement server-side, would not only involve less work on the client-side (don't have to remember to pass the token in with every request), and would let you keep your user logged in after they navigate away from the page.

Related

How to stop App re-render when entering links manually

So I have a React app where it has an App component. Inside this app component, I'm reaching out to the server to check the user's auth status. Now I only want to check this one time when the app is opened for the first time.
I'm also using react-router-dom for routing. Now the problem is when I enter a path manually in the address bar it renders my App component again. The same doesn't happen when I navigate with the Link component of react-router-dom. Because of this behaviour, my auth status sets to false (it is default to false in App) even when the user is logged in. Then it again reaches to the server, and then sets the auth status to true again. How can I fix this so that the app component doesn't re-render and I don't see that login screen even for a while when I'm logged in when entering paths manually?
First query the localstorage for Auth Status and if it returns fail, Query with api call and add Auth Status to localStorage and proceed further. asat!
You may suspect of what would happen if user manually update the local storage. Since if you send the auth tokens on every api. No need to worry about that.
Have a great day :)

Apollo caching with React redirect doesn't get new items

I am using Apollo Client.
There's a route /restaurants/${restaurant_name}/places where it lists all the places for concrete restaurant_name.
Let's say I am on the /restaurans/france_res/places route. On that route, I can add new place for it. I add it and when it's done, I redirect users with history.push to the following route - /restaurans/france_res/places/place_name.
Now, if user goes to /restaurans/france_res/places route again (with no refresh, just react router's navigation), Apollo doesn't query the server any more and the places that route shows doesn't include the new one. This is because of the cache policy and it seems like the route /restaurans/france_res/places is already cached...
What could be the workaround for the following situation ?
using no-cache policy seems too harsh... Then what's the point of Apollo's caching policies in the first place ?
I don't use Redux, but I don't know how that could help me. There's a way with Redux, but it's not good.
Maybe, when new place gets added, I make user redirect to the route without history.push which will cause the hard refresh, causing queries to get executed again.
How do you handle situation like this ?

Combination of React HOC with fetch and redirect cause memory leaks

I've created HOC which I use to fetch data and check, if user is logged in.
I use this HOC with Navbar to display different buttons, depending on wether user is logged in or not.
The issue is that if Im logged in (I store this info in localStorage) and load in a page login page (which has navbar), Im instantly redirected to main page (because i coded it that way) and but navbar is already waiting for fetch response and in console I get an error warning about memory leak.
How can I avoid it?
Some answer Ive looked up suggests checking, if component is still mounted, when receiving response from fetch, but im not sure, if its possible in the scenarion, where fetch is called inside a HOC. (Cancel All Subscriptions and Asyncs in the componentWillUnmount Method, how?)

React-router with Saga Server-side render redirect

The issue is that I have an initial fetch request that gets the query string from the URL and makes a request to get a specified product page.
The same logic and routing is used on the client side and server side.
The API request gets executed in redux-saga, but, when it fails (no matching product), the site is already on the /product route, and so redirect needs to happen.
On the client-side this is very easy, as it can be redirected dynamically using browserHistory or just window.location.href, but on the server, i would have to pass down the res object and use res.redirect and, possibly, use two different redirect functions with this approach.
The other solution is to catch this in a topmost fashion; on express index.js during ReactDOMServer.renderToString(component), and also on the client side endpoint when it fails - with different logic. For example, a 404 view with express redirect. But, because saga is not the topmost in hierarchy anyway, throwing this 404 and catching it in the uppermost context (express server) requires passing it up and up in a very "dirty" try/catch way.
Any suggestions how to tackle that in terms of correctness? I really don't want to have routing logic doubled up on express side, so I'd rather have it handled nicely with isomorphic code.
You are probably exposing your main state to be picked up by the front-end code. Any calls that are returned from sagas can update that state. We are using a set404() and set301() actions when redux saga does not return the data we want. These actions are triggered and they set return404: true or return301: true in the main state object. So before rendering our app to string we inspect that object. If any of these are present we return a relevant response from the server.

react-router auto-login in the onEnter hook

In all the react-router examples for the onEnter hook, the hook is synchronous. In the issues it's recommended to keep it that way. What's the idiomatic way to log someone in while inside a hook?
User-story:
Client has a localStore token
Client navigates directly to example.com/mustBeLoggedIn
Before going to that page, I'd like to check if they have a token, if they do, attempt to gain authorization. If authorized, continue onto route
Should I just use a HOC instead of onEnter?
I currently have it working, but I know it's not recommended, and seems very brittle, for example using an immutable store causes the same issues as this issue: https://github.com/rackt/react-router/issues/2365
There are a number of ways to solve this challenge, but I agree with the recommendation that calls in onEnter should be synchronous. Here are two ways to solve this challenge.
Option 1: Don't Validate the Token on Page Load
It is arguably the case that the browser should not validate the existing token at all. That is, the browser should assume that the token is valid and try to load the route as defined.
On an API call failure response from the server (where the actual authentication and authorization is really occurring), your app can handle the need to re-authenticate any way it chooses: redirect to a login page, present a login dialog, etc.
The advantage of this approach is that it will work across all routes that employ this logic without having to specify the routes that need it individually.
Option 2: Use an HOC (recommended)
As you suspected, an HOC is probably the best way to go. Your router would attempt to render something like this:
<EnsureAuthorized checks={myCheckFunction} Component={MustBeLoggedIn} />
This kind of flexible HOC could optionally run custom authorization checks in addition to the required authentication checks (like user roles), and render the component provided only when the authentication and the checks succeed, and then handle failures in a consistent way (e.g. rendering a login component or redirecting the user to the login page or showing a 403-type message, etc).

Resources