Headless WordPress: User Registration with ReactJS & WPGraphQL

Our account system is taking shape now that we have login, logout, and password reset implemented. Next we need to handle user registrations!

Lucky for us, WPGraphQL provides us with a user registration mutation we can use out of the box.

mutation registerUser {
  registerUser(input: {
    username:"new_user"
  }) {
    clientMutationId
    user {
      name
      slug
    }
  }
}Code language: JavaScript (javascript)

All we need to is build some client code to consume this. In this post I’ll be demonstrating this using ReactJS and the Apollo Client package (which I covered in a previous post) to make our requests to the server where WordPress and WPGraphQL are running.

Custom hooks

When we create a registration form it will need a way to send the form data to the server. For this we can create some custom React hooks.

useRegisterMutation

This hook provides us with a function named registerMutation that accepts a username, email, and password and then uses Apollo Client useMutation hook to make the GraphQL request. This function returns the result as a JavaScript promise.

/**
 * External dependencies
 */
import { gql, useMutation } from '@apollo/client';

const REGISTER = gql`
	mutation RegisterUser(
		$username: String!
		$email: String!
		$password: String!
	) {
		registerUser(
			input: { username: $username, email: $email, password: $password }
		) {
			clientMutationId
		}
	}
`;

export const useRegisterMutation = () => {
	const [ mutation, mutationResults ] = useMutation( REGISTER );

	const registerMutation = ( username, email, password ) => {
		return mutation( {
			variables: {
				username,
				email,
				password,
			},
		} );
	};

	return { registerMutation, results: mutationResults };
};Code language: JavaScript (javascript)

This hook can be used like this:

const { registerMutation, results } = useRegisterMutation();Code language: JavaScript (javascript)

useRegistration

I created another custom hook that consumes useRegisterMutation and does a little more handling for things such as error states and status that our registration form will later use.

One thing we do need to handle is error codes coming back from the WPGraphQL API—the codes are descriptive but need to be converted into a more human readable format if they are being displayed to the user:

const errorCodes = {
	invalid_username:
		'Invalid username or email address. Please check it and try again.',
	invalid_email: 'Invalid email address. Please check it and try again.',
	incorrect_password:
		'Incorrect password. Please try again, or reset your password.',
	empty_username: 'Please provide your username.',
	empty_password: 'Please provide your password.',
};Code language: JavaScript (javascript)

The hook itself uses the mutation hook we made earlier, tracks the status of requests (idle, resolving, resolved), and stores any error messages we get back from the server.

export const useRegistration = () => {
	const [error, setError] = useState(null);
	const [status, setStatus] = useState('idle');
	const { registerMutation } = useRegisterMutation();

	const register = (username, email, password) => {
		setError(null);
		setStatus('resolving');
		return registerMutation(username, email, password)
			.then(() => {
				setStatus('resolved');
			})
			.catch((errors) => {
				setError(errorCodes[errors.message] || errors.message);
				setStatus('resolved');
			});
	};

	return {
		register,
		error,
		status,
	};
};Code language: JavaScript (javascript)

Creating the user registration form

The registration form needs fields for username, email address, and password. It also needs to use the status and error state from the useRegistration hook we created earlier.

A basic version of the form in React would be as follows (I am using useState here to store field values).

import { useState } from 'react';
import { useRegistration } from 'hooks';

export const RegisterForm = () => {
	const [ username, setUsername ] = useState( '' );
	const [ email, setEmail ] = useState( '' );
	const [ password, setPassword ] = useState( '' );
	const { register, error, status } = useRegistration();

	const onRegister = ( e ) => {
		e.preventDefault();
		register( username, email, password );
	};

	return (
		<form
			onSubmit={ onRegister }
			className="register-form"
			autoComplete="on"
		>
			{ error && <div className="error-notice">{ error }</div> }
			<label htmlFor={ `field-username` }>Username</label>
			<input
				type="text"
				id="field-username"
				value={ username }
				autoComplete="username"
				onChange={ ( value ) => setUsername( value ) }
				disabled={ status === 'resolving' }
			/>
			<label htmlFor={ `field-email` }>Email</label>
			<input
				type="text"
				id="field-email"
				value={ email }
				autoComplete="email"
				onChange={ ( value ) => setEmail( value ) }
				disabled={ status === 'resolving' }
			/>
			<label htmlFor={ `field-password` }>Password</label>
			<input
				type="password"
				autoComplete="new-password"
				value={ password }
				onChange={ ( value ) => setPassword( value ) }
				disabled={ status === 'resolving' }
			/>
			<button
				className="button"
				onClick={ onRegister }
				disabled={ status === 'resolving' }
			>
				Create Account
			</button>
		</form>
	);
};

export default RegisterForm;Code language: JavaScript (javascript)

With this in place, our form looks like this:

Registration form

Adding some basic form validation

To avoid making erroneous requests we can add some client side validation to our fields. We can add these validation rules in our form submit handler.

const [ passwordError, setPasswordError ] = useState( '' );

const onRegister = (e) => {
	e.preventDefault();
	setPasswordError( '' );
	if ( username.length === 0 ) {
		setPasswordError( 'Please enter a username.' );
		return;
	}
	if ( email.length === 0 ) {
		setPasswordError( 'Please enter your email address.' );
		return;
	}
	if ( password.length === 0 ) {
		setPasswordError( 'Please enter a password.' );
		return;
	}
	register(username, email, password);
};Code language: JavaScript (javascript)

With this change in place, registration will only be attempted if the username, email, and password fields are populated. This could be improved further by adding email format validation and so on.

Improving the password field

Our password field could be improved by giving some indication of password strength. For this I chose to use the React Password Strength Bar package. You can customise the scoreWords and shortScoreWord however you please to reflect password strength. For fun, I’m using a D&D theme 🙂

<PasswordStrengthBar
	password={ password }
	scoreWords={ [
		'critical fail',
		'okay',
		'good',
		'strong',
		'critical roll!',
	] }
	shortScoreWord={ 'critical fail' }
/>Code language: JavaScript (javascript)

In the above snippet we provide the current password, and it will access the strength and display it to the user:

The password field and PasswordStrengthBar component

You can see the final form on GitHub here.

Wrapping up

Our registration system is complete. The only other change I made (on the server side) was to disable new user emails:

remove_action( 'register_new_user', 'wp_send_new_user_notifications' );Code language: PHP (php)

You can see a working example of the registration form in my D&D app here, and the source code is also public on GitHub.

As I’ve said before, it’s very useful that WPGraphQL includes some of the account mutations out of the box. Whilst this is not unique to GraphQL (we could use the WP REST API if we wanted), I have found it very easy to work with from the client side.

  1. Headless WordPress: Cookie Based Login using GraphQL
  2. Headless WordPress: Log-out using GraphQL & ReactJS
  3. Headless WordPress: Password Reset with ReactJS & WPGraphQL
  4. Headless WordPress: User Registration with ReactJS & WPGraphQL

Posted

in

by

%d bloggers like this: