Reusable custom-validated input hook in React Native

Reusable custom-validated input hook in React Native

Introduction

React and React Native is one of the most used frameworks out there, but are you really exploring their core features of it? This article will show you how to create a reusable custom-validated input hook in react native precisely.

In recent times, I have been wondering how to build a custom-validated input form, also thinking of the best way it will be done with lots of reusability purposes in scope. I set up one that is meant to help you in your development, and it works fine as most of your form validation libraries are out there.

Actually, it was fun playing around with it. Enough of the talk, let's get started with the code part.

Setup and Tooling

To get started, we need a couple of things to install, this setup is done in react native, and I will be using the expo cli, this is actually applicable to any of your projects in reactjs and nextjs.

# create a new app my-app with typescript
npx create-expo-app -t expo-template-blank-typescript

# change directory
cd my-app

# run the application
npx expo start

This will successfully create an expo project, and you will also need a validator package, and the project uses a native base for its UI components.

# install validator
yarn add validator @types/validator

#install native-base
yarn add native-base

expo install react-native-svg@12.1.1

expo install react-native-safe-area-context@3.3.2

With the following installation above, you will have a setup ready for coding this custom-validated input out. I have the following folder setup below. You will need to create a hooks folder which handles all your custom hooks.

Hook folder

Inside the hook folder, you will create two files, useValidation.ts and useValidatedInput.tsx. These files are the ones that will handle all your validation and takes care of all the input in your form and can be used across your entire project. The code below is for the useValidation.ts which basically handles the validation for your inputs.

import { useState, useEffect, SetStateAction, Dispatch, useRef } from "react";

export default function useValidation(
  validateFn?: (arg0: string) => boolean,
): [string, Dispatch<SetStateAction<string>>, boolean] {
  const [string, setString] = useState("");
  const [error, setError] = useState(false);
  const isInitialRender = useRef(true);

  useEffect(() => {
    setError(false);
    if (!isInitialRender.current) {
      if (!string) setError(true);
      else if (validateFn) setError(!validateFn(string));
    }

    isInitialRender.current = false;
  }, [isInitialRender, string, validateFn]);

  return [string, setString, error];
}

The useValidation hooks handle the set value of the input and the error state, which is rendered when the validator check is not meant. Let's see how it is implemented in the useValidatedInput.

import { FormControl, Text, Input, Link, Pressable, Icon } from "native-base";
import React, { useState } from "react";
import useValidation from "./useValidation";

interface UseValidatedInputOptions {
  textValue?: string;
  placeholder: string;
  errorMessage?: string;
  validateFn?: (arg0: string) => boolean;
  inputType?: "text" | "password";
  linkLabel?: string;
  linkOnPress?: () => void;
  linkLabelOne?: string;
  linkOnPressOne?: () => void;
  iconName?: string;
}

export default function useValidatedInput(
  options: UseValidatedInputOptions,
): [string, boolean, JSX.Element] {
  const {
    textValue,
    placeholder,
    errorMessage,
    validateFn,
    inputType = "text",
    linkLabel,
    linkOnPress,
    linkLabelOne,
    linkOnPressOne,
    iconName,
  } = options;
  const [string, setString, error] = useValidation(validateFn);
  const [showInput, setShowInput] = useState(inputType === "text");

  const validatedInput = (
    <FormControl isRequired isInvalid={error}>
      <Text mb="2" mt="4" fontSize="16" fontWeight="400">
        {textValue}
      </Text>
      <Input
        placeholder={placeholder}
        type={showInput ? "text" : "password"}
        value={string}
        onChangeText={(text) => setString(text)}
        InputLeftElement={
          iconName ? <Icon name={iconName} size={5} ml="2" mr="2" /> : undefined
        }
        InputRightElement={
          inputType === "password" ? (
            <Pressable onPress={() => setShowInput(!showInput)}>
              <Icon
                name={showInput ? "eye-outline" : "eye-off-outline"}
                size={5}
                mr="2"
                color="muted.400"
              />
            </Pressable>
          ) : undefined
        }
      />
      {errorMessage && (
        <FormControl.ErrorMessage>{errorMessage}</FormControl.ErrorMessage>
      )}
      {linkLabelOne && (
        <Link
          _text={{
            fontSize: "xs",
            fontWeight: "500",
            color: "primary.900",
          }}
          alignSelf="flex-start"
          mt="0.7"
          mb="5"
          textDecoration="none"
          onPress={linkOnPressOne}
        >
          {linkLabelOne}
        </Link>
      )}
      {linkLabel && (
        <Link
          _text={{
            fontSize: "xs",
            fontWeight: "500",
            color: "primary.600",
          }}
          alignSelf="flex-end"
          // mt="0.7"
          mb="5"
          onPress={linkOnPress}
        >
          {linkLabel}
        </Link>
      )}
    </FormControl>
  );

  return [string, error, validatedInput];
}

Component usage

We are using the validated input inside the sign-in component, and we can call the custom-validated input in our react component.

import React from "react";
import { Button, VStack } from "native-base";
import validator from "validator";
import { Routes } from "../../utils/constants";
import { RootStackScreenProps } from "../../navigation/RootNavigation.types";
import useValidatedInput from "../../hooks/useValidatedInput";
import FormLinks from "../common/FormLinks";
import useAuthStore from "../../hooks/useAuthStore";
import { setItem } from "../../utils/storage";
import useAxios from "../../hooks/useAxios";
import Layout from "../common/Layout";

const SignIn = ({ navigation }: RootStackScreenProps<Routes.SIGN_IN>) => {
  const [email, emailError, emailInput] = useValidatedInput({
    textValue: "Phone Number",
    placeholder: "Enter your mobile number",
    errorMessage: "Invalid phone number ",
    validateFn: validator.isMobilePhone,
    iconName: "email-outline",
  });
  const [password, passwordError, passwordInput] = useValidatedInput({
    textValue: "Password",
    placeholder: "Password",
    errorMessage: "Password is required.",
    inputType: "password",
    linkLabel: "Forgot Password?",
    linkLabelOne: "Change Device",
    linkOnPress: () => navigation.push(Routes.FORGOT_PASSWORD),
    linkOnPressOne: () => navigation.push(Routes.FORGOT_PASSWORD),
  });

  const { isLoading, request } = useAxios();

  const { setId } = useAuthStore();

  const signInService = async () => {
    const { data } = await request({
      url: "/auth/signin",
      data: {
        email,
        password,
      },
      failureMessages: {
        400: "Invalid email or password.",
        401: "Invalid email or password.",
      },
    });

    if (data) {
      const { jwtToken, payload } = data.idToken;
      const { sub: id } = payload;

      setItem("jwtToken", jwtToken);
      setId(id);
    }
  };

  return (
    <Layout>
      <VStack space={1}>
        {emailInput}
        {passwordInput}
        <Button
          isLoading={isLoading}
          isDisabled={!email || !password || emailError || passwordError}
          mt="2"
          onPress={() => {
            signInService();
          }}
        >
          Sign In
        </Button>
        <FormLinks
          message="Don't have an account? "
          linkLabel="Create One"
          linkOnPress={() => navigation.push(Routes.SIGN_UP)}
        />
      </VStack>
    </Layout>
  );
};

export default SignIn;

The output of the code works as this below:

View

Conclusion

Creating a custom hook is one way you can prove you are improving your react/ react native skills. Most devs tend to make use of multiple form validation packages without knowing how it works under the hood, this is not good enough, as it does not improve your coding skills. That's it for now.

I will be working more on improving my coding skills, I also want you to do the same. Drop some feedback so I can improve my technical writing skill.

Enjoy more of your holidays. Cheers!!!