Home > Mobile >  Supertokens: Adding custom form fields with modifier function capabilities
Supertokens: Adding custom form fields with modifier function capabilities

Time:05-10

I'm adding custom fields to the Sign Up form in Supertokens. I see there is a builtin validate function and you can basically have a true or false return on some conditional. I was wondering if there was a way to apply a "mask" to a phone number field which forces a format ie: (123)456-7890 something like an "onBlur()" or "onKeydown()" type of event. Here's my frontendConfig() code. (Snipped for brevity):

function inputMask(value) {
  console.log("value: ", value);
  const _num = value.replace(/\D/g, "").match(/(\d{3})(\d{3})(\d{4})/);
  const phoneNumber = `(${_num[1]})${_num[2]}-${_num[3]}`;
  return phoneNumber;
}

EmailPasswordReact.init({
        signInAndUpFeature: {
          signUpForm: {
            formFields: [
              {
                id: "firstname",
                label: "First Name",
                placeholder: "Jane",
              },
              {
                id: "lastname",
                label: "Last Name",
                placeholder: "Doe",
              },
              {
                id: "phone",
                label: "Phone",
                placeholder: "(123)456-7890",
                // as an example
                validate: (value) => {
                  return inputMask(value);
                },
              },
            ],
          },
        },```

CodePudding user response:

The validate function is only meant for checking if the input is correct or not. It's not meant for normalisation.

The SuperTokens react library render react components for the login / sign up UI, so you can use plain JS to change the form behaviour:

  • You want to add an event listener on the custom input field which fires on the blur event. In order to add this, you need to find that input field using JS and call addEventListener on it - this should be done in the useEffect block of the page that shows the sign up form.
  • When the event is fired, you want to call the inputMask function and change the value of the form field to be equal to that function's output.
  • You should also implement logic in the validate function to check that the user's input (normalised or not) is correct.
  • Finally, in case the input field blur event doesn't happen, then you want to normalise the user's input when the sign up button is clicked as well. This can be achieved via the override feature.

First, in order to add the event listener, you want override the react component in the sign up form and use a useEffect hook in your custom component. The component to override is called EmailPasswordSignUpForm_Override (supertokens-auth-react SDK version >= 0.20.0)

EmailPasswordReact.init({
  override: {
    components: {
      EmailPasswordSignUpForm_Override: ({ DefaultComponent, ...props }) => {
        React.useEffect(() => {
          // This will get fired each time the sign up form is shown to the user
          // TODO: adding the event listener logic come in here...
        }, [])
        return <DefaultComponent {...props} />;
      }
    }
  },
  signInAndUpFeature: {...}
})

Then you want to find the input field representing the input for the phone number. You can find it like:

React.useEffect(() => {
  // Note that the phone number input is the 5th element in the sign up form. That's why we do nth-child(5)
  let input = document.querySelector("#supertokens-root").shadowRoot.querySelector("form > div:nth-child(5) > div:nth-child(2) > div > input");

  function onBlur() {
    let phoneNumber = input.value;
    let normalisedPhoneNumber = inputMask(phoneNumber);
    input.value = normalisedPhoneNumber;
  }

  input.addEventListener('blur', onBlur);
  return () => {
    input.removeEventListener('blur', onBlur);
  }
}, [])

The above will noramlise the phone number when the phone number input is blurred. Whilst the above works from a UI point of view, when the sign up button is clicked, or when the validate function is called, the user's input may still be unnormalised (cause the react state in the library has not been updated). So you should also call the inputMask function during the validate function call and when signUp is clicked:

Normalise when signUp is clicked

EmailPasswordReact.init({
  override: {
    functions: (oI) => {
      return {
        ...oI,
        signUp: async function (input) {
          // we normalise the phone input
          input.formFields = input.formFields.map(f => {
            if (f.id === "phone") {
              return {
                ...f,
                value: inputMask(f.value)
              }
            }
            return f;
          })

          // we call the original implementation
          return oI.signUp(input);
        }
      }
    }
  }
})

Normalise when validate function is called:

validate: (value) => {
  value = inputMask(value);
  // TODO: logic for checking input syntax. If there is an error, return a string else undefined
}

So the overall code looks like:

import React from 'react'
import EmailPasswordReact from 'supertokens-auth-react/recipe/emailpassword'
import SessionReact from 'supertokens-auth-react/recipe/session'
import { appInfo } from './appInfo'

export let frontendConfig = () => {
  return {
    appInfo,
    recipeList: [
      EmailPasswordReact.init({
        override: {
          functions: (oI) => {
            return {
              ...oI,
              signUp: async function (input) {
                // we normalise the phone input
                input.formFields = input.formFields.map(f => {
                  if (f.id === "phone") {
                    return {
                      ...f,
                      value: inputMask(f.value)
                    }
                  }
                  return f;
                })

                // we call the original implementation
                return oI.signUp(input);
              }
            }
          },
          components: {
            EmailPasswordSignUpForm_Override: ({ DefaultComponent, ...props }) => {
              React.useEffect(() => {
                let input = document.querySelector("#supertokens-root").shadowRoot.querySelector("form > div:nth-child(5) > div:nth-child(2) > div > input");

                function onBlur() {
                  let phoneNumber = input.value;
                  let normalisedPhoneNumber = inputMask(phoneNumber);
                  input.value = normalisedPhoneNumber;
                }

                input.addEventListener('blur', onBlur);
                return () => {
                  input.removeEventListener('blur', onBlur);
                }
              }, [])
              return <DefaultComponent {...props} />;
            }
          }
        },
        signInAndUpFeature: {
          signUpForm: {
            formFields: [
              {
                id: "firstname",
                label: "First Name",
                placeholder: "Jane",
              },
              {
                id: "lastname",
                label: "Last Name",
                placeholder: "Doe",
              },
              {
                id: "phone",
                label: "Phone",
                placeholder: "(123)456-7890",
                validate: (value) => {
                  value = inputMask(value);
                  // TODO: logic for checking input syntax
                  return undefined;
                }
              },
            ],
          },
        }
      }),
      SessionReact.init(),
    ],
  }
}

function inputMask(value) {
  const _num = value.replace(/\D/g, "").match(/(\d{3})(\d{3})(\d{4})/);
  const phoneNumber = `(${_num[1]})${_num[2]}-${_num[3]}`;
  return phoneNumber;
}

Hope this helps!

  • Related