Simple One Time Password OTP Component for React Native
No library needed, just a ~120-line component.
Open Source Library vs. Do it Yourself
Often we like to just reach for an open source library to accomplish a ‘simple enough functionality’. This is an enticing pattern in software engineering at first glance, but beneath the hood of something like the rapidly changing and evolving React Native ecosystem, there are often many problems with compatibility and versioning that render open-source components or libraries broken after just a few short months of each release. One example case is that of an input for a one-time password (OTP) component. There are a handful of libraries that seem to be floating around, but in our experience almost all of them are not heavily maintained and they seem to be riddled with bugs and issues. We ultimately came to the conclusion to create our own version of this component in-house. Ultimately it was not too hard to accomplish through built-in components that ship with React Native itself.
Show Me the Code!
I’m going to keep this post short and sweet. Here’s the code:
Caveats
Note this code makes use of a variety of imports from third parties andother custom code. The first is useIsFocused
by @react-navigation/native
— this simply switches to true
when the screen is focused, false
otherwise. This hook is used to focus on the first input whenever the screen using theOTPInput
component is focused. Next is a small utility functionisIOS()
that is just a wrapper around a call on the Platform
object imported from react-native
(it’s more idiomatic and a bit smaller for our team):
export const isIOS = () => {
return Platform.OS === 'ios';
};
Then finally we have a useTimeout
hook that triggers the keyboard opening in a react-safe way:
// Taken from https://github.com/antonioru/beautiful-react-hooks/blob/master/src/useTimeout.ts
import {useCallback, useEffect, useRef, useState} from 'react';export type UseTimeoutOptions = {
cancelOnUnmount?: boolean;
};const defaultOptions = {
cancelOnUnmount: true,
};/**
* An async-utility hook that accepts a callback function and a delay time (in milliseconds), then delays the
* execution of the given function by the defined time.
*/
export const useTimeout = <T extends (...args: any[]) => any>(
fn: T,
milliseconds: number | null,
options: UseTimeoutOptions = defaultOptions,
): [boolean, () => void] => {
const opts = {...defaultOptions, ...(options || {})};
const timeout = useRef<NodeJS.Timeout>();
const callback = useRef<T>(fn);
const [isCleared, setIsCleared] = useState<boolean>(false);// the clear method
const clear = useCallback(() => {
if (timeout.current) {
clearTimeout(timeout.current);
setIsCleared(true);
}
}, []);// if the provided function changes, change its reference
useEffect(() => {
if (typeof fn === 'function') {
callback.current = fn;
}
}, [fn]);// when the milliseconds change, reset the timeout (if not null)
// if milliseconds are null, clear the timeout
useEffect(() => {
if (milliseconds !== null) {
timeout.current = setTimeout(() => {
callback.current();
}, milliseconds);
} else {
clear();
}
return clear;
}, [milliseconds]);// when component unmount clear the timeout
useEffect(
() => () => {
if (opts.cancelOnUnmount) {
clear();
}
},
[],
);return [isCleared, clear];
};
I found that using such a timeout was a bit smoother when opening the keyboard after navigating to our OTP screen.
Thanks!
If this post helped you or your team out, give it a clap or two, and follow if you want more software posts!
Also note this post was very rushed so I may add it or modify it later. :)
Cheers🍺
-Chris