Search code examples
reactjsmeteormeteor-accounts

Meteor: Calling verifyEmail From React Client?


I've read many SO posts about using Accounts.onEmailVerificationLink and Accounts.verifyEmail, but I haven't yet found one that seems to explain how to call them from a React client.

I have React Router set up to route clicks on the email verification link, to a React component called ConfirmEmail. The router captures the validation token to props.match.params.emailValidationToken.

The ConfirmEmail component looks like this:

import React, {useEffect, useRef, useState} from "react";

function ConfirmEmail(props) {
    const {client, match, history} = props;

    const doSetUp = useRef(true);
    const emailVerificationToken = useRef(props.match.params.emailValidationToken);

    if (doSetUp.current){
        doSetUp.current = false;
        debugger;
        Accounts.onEmailVerificationLink(function(token, done) {
            Accounts.verifyEmail(emailVerificationToken.current, function(error) {
                debugger;
                if (error) {
                    console.log(error)
                } else {
                    console.log(success)
                }
            })
        });
    }


    return (
        <p>Your email address has been verified.</p>
    )
}

export default ConfirmEmail;

I've tried it a few different ways, but I haven't yet found an approach that works.

What is the correct way to handle a click on a Meteor email validation link from a React client?


Solution

  • Ok, I don't use React Hooks but I guess you will find easy to port it up to your flavor.

    It all need to happen in componentDidMount(). This ensures that you avail of the token and that you only run this process once. In the following code, toastr is a UI notification system for the client.

    
    import React, { Component } from 'react'
    import { connect } from 'react-redux' // if you use redux, here it is used to get the user slug from props.
    import { Link } from 'react-router-dom'
    import PropTypes from 'prop-types' // not relevant
    import { Accounts } from 'meteor/accounts-base'
    import { toastr } from 'react-redux-toastr' // not relevant
    import { TiWarningOutline, TiThumbsOk } from '../../components/shared/Icons/icons' // not relevant
    
    class ConfirmEmail extends Component {
      constructor (props) {
        super(props)
        this.state = {
          linkExpired: false,
          logged: true // I need to know if the user is logged in before I test the token
        }
      }
    
      componentDidMount () {
        const { match, history, slug } = this.props
        const token = match.params.token
    
        // If the user is not logged in I cannot test the token against an email.
        if (!slug) {
          this.setState({ logged: false })
          return
        }
    
        Accounts.verifyEmail(token, err => {
          if (err && err.reason === 'Verify email link expired') {
            this.setState({ linkExpired: true })
          }
          // Inform the user what went wrong
          if (err) {
            toastr.light('Could not verify email!', `Error: ${err.reason}`, { timeOut: 2000, icon: (<TiWarningOutline style={{ fontSize: 42, color: 'red' }} />) })
          } else {
            // Confirm to user and move on to where you want to direct the user first or ask the user to close the window...or else
            toastr.light('You\'ve Successfully Confirmed Your Email Address!', { timeOut: 4000, icon: (<TiThumbsOk style={{ fontSize: 42, color: 'rgb(74, 181, 137)' }} />) })
            history.push('/feeds')
          }
        })
      }
    
      render () {
        const { linkExpired, logged } = this.state
        return (
          <div style={{ textAlign: 'center', paddingTop: 80 }}>
            {logged
              ? <>
                {linkExpired
                  ? <p>This link has expired.</p>
                  : <>
                    <img src={`${IMAGE_BANK}/images/6.svg`} style={{ width: 36 }} /> // this is a spinner
                    <p>Awaiting confirmation ...</p>
                  </>}
              </>
              : <>
                <p style={{ maxWidth: 360, margin: '0 auto' }}>
                  In order to verify your email address you need to be authenticated. Please sign in and try the verification link from your email one more time.
                </p>
                <br />
                <Link to='/signin' className='btn btn-primary'>Sign In</Link>
              </>
            }
          </div>
        )
      }
    }
    
    // this block is relevant in Redux context. Just gets the slug (or some other way to check if the user is logged in.)
    const mapStateToProps = state => {
      const currentUser = state.user.currentUser
      return {
        slug: currentUser?.slug
      }
    }
    export default connect(mapStateToProps, { })(ConfirmEmail)
    
    // from here, not so relevant
    ConfirmEmail.propTypes = {
      params: PropTypes.object
    }