Reset store after logout with Apollo client - reactjs

I'm trying to reset the store after logout in my react-apollo application.
So I've created a method called "logout" which is called when I click on a button (and passed by the 'onDisconnect' props).
To do that I've tried to follow this example :
https://www.apollographql.com/docs/react/recipes/authentication.html
But in my case I want LayoutComponent as HOC (and it's without graphQL Query).
Here is my component :
import React, {Component} from 'react';
import { withApollo, graphql } from 'react-apollo';
import { ApolloClient } from 'apollo-client';
import AppBar from 'material-ui/AppBar';
import Sidebar from 'Sidebar/Sidebar';
import RightMenu from 'RightMenu/RightMenu';
class Layout extends Component {
constructor(props) {
super(props);
}
logout = () => {
client.resetStore();
alert("YOUHOU");
}
render() {
return (
<div>
<AppBar title="myApp" iconElementRight={<RightMenu onDisconnect={ this.logout() } />} />
</div>
);
}
}
export default withApollo(Layout);
The issue here is that 'client' is not defined and I can't logout properly.
Do you have any idea to help me to handle this situation or an example/best practices to logout from apollo client ?
Thanks by advance

If you need to clear your cache and don't want to fetch all active queries you can use:
client.cache.reset()
client being your Apollo client.
Keep in mind that this will NOT trigger the onResetStore event.

client.resetStore() doesn't actually reset the store. It refetches all
active queries
Above statement is very correct.
I was also having the logout related problem. After using client.resetStore() It refetched all pending queries, so if you logout the user and remove session token after logout you will get authentication errors.
I used below approach to solve this problem -
<Mutation
mutation={LOGOUT_MUTATION}
onCompleted={()=> {
sessionStorage.clear()
client.clearStore().then(() => {
client.resetStore();
history.push('/login')
});
}}
>
{logout => (
<button
onClick={() => {
logout();
}}
>
Logout <span>{user.userName}</span>
</button>
)}
</Mutation>
Important part is this function -
onCompleted={()=> {
sessionStorage.clear(); // or localStorage
client.clearStore().then(() => {
client.resetStore();
history.push('/login') . // redirect user to login page
});
}}

you can use useApolloClient to access apollo client.
import { useApolloClient } from "#apollo/client";
const client = useApolloClient();
client.clearStore();

You were very close!
Instead of client.resetStore() it should have been this.props.client.resetStore()
withApollo() will create a new component which passes in an instance
of ApolloClient as a client prop.
import { withApollo } from 'react-apollo';
class Layout extends Component {
...
logout = () => {
this.props.client.resetStore();
alert("YOUHOU");
}
...
}
export default withApollo(Layout);
For those unsure about the differences between resetStore and clearStore:
resetStore()
Resets your entire store by clearing out your cache and then
re-executing all of your active queries. This makes it so that you may
guarantee that there is no data left in your store from a time before
you called this method.
clearStore()
Remove all data from the store. Unlike resetStore, clearStore will not
refetch any active queries.

Related

Why is #auth0/nextjs-auth0 invoking /me api everytime I change route?

I just started using auth0 sdk for nextjs projects. Everything seems to be ok, but I have one little problem. Everytime I change route (while I am logged in) there is invoked the /me api. This means that the user that I got through useUser ctx becomes undefined and there is a little flash of the pages rendered only if logged in.
This is the general structure of my project
_app.tsx
import "../styles/globals.css";
import type { AppProps } from "next/app";
import { appWithTranslation } from "next-i18next";
import { UserProvider } from "#auth0/nextjs-auth0/client";
import AppMenu from "../components/AppMenu";
function MyApp({ Component, pageProps }: AppProps) {
return (
<UserProvider>
<AppMenu>
<Component {...pageProps} />
</AppMenu>
</UserProvider>
);
}
export default appWithTranslation(MyApp);
AppMenu is a component that I render only if I have the user. It's something like:
import { NextPage } from "next";
import { useUser } from "#auth0/nextjs-auth0/client";
interface Props {
children: JSX.Element;
}
const AppMenu: NextPage<Props> = ({ children }) => {
const { user } = useUser();
return (
<div>
{user && (<nav>{/* more stuff...*/}</nav>) }
{children}
</div>
);
};
export default AppMenu;
In this component I have the app's menu.
As I said before, when I switch from a route to another I can see in the network tab that there is called the /me endpoint and "user" is undefined. This implies that the app's menu (and all the protected components) is not rendered and there is a nasty flash of the page waiting for the response.
Is there a way to fix this? I was looking for a isLoggedIn prop but I haven't found anything.
What do you suggest?
p.s. Possibly, I would avoid a "loading" component
useUser() is a hooks call. Therefore, you need to use useEffect() with a Promise and wait until load the user. However, by the end of the day loading component is a must.
In the meantime, A feature called isLoading comes up with the useUser() state. You can improve your code like the below.
const { user, error, isLoading } = useUser();
if (isLoading) return <div>Loading...</div>;
if (error) return <div>{error.message}</div>;
Source

Call a GraphQL Mutation once after React component mounts

After a user creates a profile, they receive a link in their email that sends them back to the site with a verifyToken in the url. If the token matches the token that is stores in the database, their isVerified status is stored in the database with the value true.
new-profile.js
import VerifyEMail from '../components/VerifyEmail';
const NewProfilePage = props => (
<div>
<VerifyEMail verifyToken={props.query.verifyToken} />
</div>
);
export default NewProfilePage;
Currently, I have this implemented and working using a form with a "Verify" button that the user must click to call the graphQL mutation, verifyEmail. Since this sets the isVerified value to true in the database, I know that everything is working as it should.
../components/VerifyEmail.js
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Mutation } from 'react-apollo';
import gql from 'graphql-tag';
const VERIFY_EMAIL_MUTATION = gql`
mutation VERIFY_EMAIL_MUTATION($verifyToken: String!) {
verifyEmail(verifyToken: $verifyToken) {
isVerified
}
}
`;
class VerifyEmail extends Component {
render() {
const { verifyToken } = this.props;
return (
<Mutation mutation={VERIFY_EMAIL_MUTATION} variables={{ verifyToken }}>
{verifyEmail => (
<form
onSubmit={async () => {
await verifyEmail(verifyToken);
}}
>
<button type="submit">Verify</button>
</form>
)}
</Mutation>
);
}
}
VerifyEmail.propTypes = {
verifyToken: PropTypes.string.isRequired,
};
export default VerifyEmail;
However, I really don't want to force my users to have to click a button to fire the mutation. I would like it to be called once the component loads. I have been racking my brain for a day and a half on this, tried so many things, and just can't seem to find anything that works.
I've seen some solutions using React hooks, Apollo hooks, componentDidMount, etc. My mind is just having a difficult time seeing it any more. This link had some of the best solutions that I found so far, but I couldn't figure out how to implement them...
[Feature idea] Execute a mutation on mount #1939
Any help to point me in the right direction would be really appreciated. Thank you.
This is far simpler application when using React hooks:
import React, { useEffect } from "react";
function VerifyEmail({ verifyToken }) {
const [ verifyEmail, { loading, data, error }] = useMutation(VERIFY_EMAIL_MUTATION);
useEffect(() => {
verifyEmail({
variables: { verifyToken },
});
}, []);
return (
<>
{loading && <p>Loading...</p>}
{data && <p>Verified successfully!</p>}
{error && <p>Error!</p>}
</>
)
}
If you somehow want to keep using classes, the only solution is to create a component and utilise componentDidMount of the component for this purpose.
// Current component:
<Mutation mutation={VERIFY_EMAIL_MUTATION} variables={{ verifyToken }}>
{verifyEmail => (
<SendEmail token={verifyToken} verify={verifyEmail} />
)}
</Mutation>
// Send Email component
class SendEmail extends Component {
componentDidMount() {
const { token, verify } = this.props;
verify(token);
}
render() {
return (
//handle loading, data and error states
)
}
}

Firing React Apollo mutation on page load

I am trying to implement email verification system in react-apollo application and running into an issue. The problem is that I want to fire a GraphQL mutation on page load when a user visits a link with a verification token. The mutation currently is fired on a button click, but I want it to happen on page load.
I tried to return the mutation from render but it sent the application into an infinite loop.
return (
<Mutation
mutation={VERIFY_EMAIL_MUTATION}
variables={{ id }}
onCompleted={() => this.setState({ userVerified: true })}
>
{(verifyEmail, { loading, error }) => {
verifyEmail();
}
</Mutation>
How can I implement firing this mutation on page load?
Use compose and pass it down as a function to your component. The following method allows you to pass multiple mutations/queries, you can use them where-ever you want without and with triggers.
import React from "react";
import { compose, graphql } from "react-apollo";
import {
VERIFY_EMAIL_MUTATION
} from "../GraphqlQueries/ServerQueries";
class YourComponent extends React.Component {
componentWillMount() {
this.props.verifyEmail({variables: {email: "your_email", variable2: "variable2" }});
}
render() {
return (
<React.Fragment>
Your Render
</React.Fragment>
);
}
}
export default compose(
graphql(VERIFY_EMAIL_MUTATION, {
name: "verifyEmail"
})
)(YourComponent);
Rather than compose you can do it like this
useEffect(() => {
MutationName({
variables: {
variable1: variable1Value
}
});
}, []);
useEffect behave as on pageload.

How to pass state to React JS High Order Component

I am using OIDC redux connector for user state. I have a few components that require authentication. I would like to use something like export default connect(mapStateToProps, mapDispatchToProps)(withAuth(Component)); and request data from state inside my authentication service.
import React, { Component } from 'react';
import { connect } from 'react-redux'
import { push } from 'connected-react-router'
export const withAuth = (Component) => {
return props => {
return <Component {...props} />
}
}
Is it possible to get state in the render function? So I can check the user beinig logged in and redirect to the sign-in page if there is no user signed in?
BTW: How would I redirect? I have tried using redirect-router-dom and <Redirect /> But then it complains about set state being changed too often ... But that might be my mistake. I get this error when I render a Redirect: Error: Maximum update depth exceeded.
If I understand correctly you want to "decorate" your component with additional logic to handle an authentication redirect?
I suggest using a "decorator" pattern here e.g.:
export const withAuth = (Component) => {
const mapStateToProps = (state, ownProps) => ({
authenticated: state.authenticated // Or what you need to do to determine this
});
return connect(mapStateToProps)(class extends React.Component {
render() {
const { authenticated, ...componentProps } = props;
if (authenticated) {
return <Component {...componentProps }>;
}
return <Redirect to="/login" />;
}
});
}
Then when you need this you can do things like:
export default withAuth(connect(yourOwnMapStateToProps)(YourComponent))
Just figured it out, I changed the store so instead of returning a function, it returns the object. So I can load in all js files. It might not be the best solution. If there is a better way to get the store in code, I would love to hear about how to do that. The configurestore function is what I found in quite a lot of examples.
import { store } from '../configureStore';
Using store.getState() I can get the current state.
The redirect issue I am having is similar to: How to use react-transition-group with react-router-dom

Login in React.js with localStorage token

Im developing a Login system of a React WebSite. We're using api_tokens for access to the API and retrieve the info as validate user credentials. I need to restringe everything when the auth fails and redirect to the user login page, currently Im using redux as app global state. The issue is I'm saving the api_token in browser localStorage, and I need to dispatch the UNAUTH_USER when the api_token is modified by Browser Clear History or other things........
I was wondering if I can attach some eventListener to that... and if is the right solution..
The code looks below:
import React from 'react'
import Header from './Header'
import Navigation from '../navigation/components/Navigation'
import Ribbon from '../ribbon/Ribbon'
import Footer from './Footer'
import Shortcut from '../navigation/components/Shortcut'
import LayoutSwitcher from '../layout/components/LayoutSwitcher'
import {connect} from 'react-redux'
// require('../../smartadmin/components/less/components.less');
class Layout extends React.Component {
constructor(props) {
super(props);
}
componentWillMount() {
if(!this.props.authenticated) {
this.props.router.push('/login');
}
}
componentWillUpdate(nextProps) {
if(!nextProps.authenticated) {
this.props.router.push('/login');
}
}
render() {
if (!this.props.authenticated) {
this.props.router.push('/login');
return null;
}
else {
return (
<div>
<Header />
<Navigation />
<div id="main" role="main">
<LayoutSwitcher />
<Ribbon />
{this.props.children}
</div>
<Footer />
<Shortcut />
</div>
)
}
}
}
const mapStateToProps = (state) => {
return {
authenticated: state.auth.authenticated
};
}
export default connect(mapStateToProps)(Layout);
I do the same thing in my app. For every call my Sagas make, if an api error occurs and a 403 expired/unvalid token is raised, I dispatch there a redux action that clears localstorage and disconnect the user (I have a refresh token mechanism retry before that).
I'm not sure that you can attach an eventListener to a localStorage expiration, but placing a check at your API calls mechanism would be IMO a good practice.
Best

Resources