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.
Related
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
)
}
}
lifecycleMethodName (){ const presentPage = 'home/page1';if (this.props.location!==presentPage){this.pros.redirectTo(presentPage);}}
FYI. I have tried component will receive props and component did update but no luck. can some one help me ?
Use it in this way :-
import React, { useState } from 'react'
import { Redirect } from 'react-router-dom'
const Home = () => {
constructor(props){
this.state={
isRedirect : false,
presentPage : "home/page1"
}
}
const renderRedirect = () => {
if (this.state.isRedirect && this.props.location!==this.state.presentPage) {
return (
<Redirect to={{
pathname: '/home/page1'
}}
/>
);
}
}
const clicked = () => {
console.log('clicked');
this.setState({
isRedirect :true
})
}
return (
<div>
Home
{renderRedirect()}
<button onClick={() => clicked()}>click</button>
</div>
)
}
export default Home;
Or you want use lifeCycle method then call clicked() function in componentDidMount like below:-
componentDidMount(){
this.clicked()
}
This is a common use case. The React life cycle method you're looking for is componentDidMount.
componentDidMount (){ <<your code to redirect >>}
The above will trigger when the component is mounted. It will also work for server-side rendered components since the method only runs clientside.
However if your app is client-side only and your component is a Class component you can also run your code in the constructor itself, since it will have access to the props.
If you're in a function component, you can run it inside the component right away, there's no need to use any hook, since the function will have access to the props right away.
I wonder, what is the way to use new Mutation components with react lifecycle methods.
Say I've got a page where I use several react-apollo mutations. I want to execute another mutation when loading state changes from true to false to show a notification popup in the page corner.
With higher order component I would do that in componentDidUpdate method, but now with <Mutation /> component I can't do that. Am I missing anything?
You can render your component in render prop function and pass mutation in props:
import gql from "graphql-tag";
import { Mutation } from "react-apollo";
const ADD_TODO = gql`
mutation addTodo($type: String!) {
addTodo(type: $type) {
id
type
}
}
`;
const AddTodo = () => {
return (
<Mutation mutation={ADD_TODO}>
{(addTodo, { data }) =>
<YourComponent someFunction={addTodo} />
}
</Mutation>
);
};
class YourComponent extends Component {
componentDidMount(){
this.props.someFunction({variables: {type: 'abc'}})
}
render(){
return <div></div>
}
}
Below is the sample client side code using Apollo client.
I am providing data from nodemon express server.
My query works fine in graphiQL.
import React from "react"
import { graphql } from 'react-apollo'
import gql from "graphql-tag";
const CounterpartyQuery = gql`
query data{
counterparty {
name
}
}
`;
export class ExchangeRates extends React.Component {
render() {
console.log(this.props.data) // Giving undefined
return (
<div>hiii{this.props.data.counterparty.map(x => x.name)}</div> //Error
)
}
}
const counterpartyData = graphql(CounterpartyQuery, { name: 'data' })(ExchangeRates)
export default counterpartyData
This is happening because your network call isn't over but the component gets rendered using the variable not available.
You need to use networkStatus to see whether the network call is over only then the data would be available to render. Until then you need to show a loader.
So replace
graphql(CounterpartyQuery, { name: 'data' })
by
graphql(CounterpartyQuery, {
name: 'data' ,
props : ({data}) => {
return {
data,
isLoading:data['networkStatus']==1 || data['networkStatus']==2 || data['networkStatus']==4
}
}
}),
and then use
this.props.isLoading
variable in the render method to decide whether to show loading or to show the list.
For eg:
render() {
console.log(this.props.data, this.props.isLoading)
return (
{this.props.isLoading ? <div>Loading Data</div>:<div>hiii{this.props.data.counterparty.map(x => x.name)}</div>
}
)
}
https://www.apollographql.com/docs/react/api/react-apollo.html#graphql-query-data-networkStatus
Apollo client HOC passes a loading prop to each of your queries, you'll need it in almost every case in order to wait for the response before rendering your data (you also need to handle request errors):
export class ExchangeRates extends React.Component {
render() {
if (this.props.data.loading) {
return <div> Loading </div>; // rendering an animated loading indicator here is good practice to show an activity
}
if (this.props.data.error) {
return <div> Error </div>;
}
return (
<div>hiii{this.props.data.counterparty.map(x => x.name)}</div>
)
}
}
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.