Skip to content

Commit 7da2ad1

Browse files
authored
Merge pull request #18 from anday013/feat/prop-on-filled-callback
feat: add `onFilled` callback to props
2 parents 5c6aff9 + 843c464 commit 7da2ad1

14 files changed

Lines changed: 264 additions & 75 deletions

README.MD

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@
1414
- Supports OTP input with a fixed number of digits.
1515
- Highly customizable appearance and styling.
1616
- Supports autofill
17+
- Fully covered with unit tests.
1718
- Seamless integration with React Native and Expo applications.
19+
- Fully typed with TypeScript.
1820

1921
## Installation
2022

@@ -50,6 +52,7 @@ yarn add react-native-otp-entry
5052
focusColor="green"
5153
focusStickBlinkingDuration={500}
5254
onTextChange={(text) => console.log(text)}
55+
onFilled={(text) => console.log(`OTP is ${text}`)}
5356
theme={{
5457
containerStyle={styles.container},
5558
inputsContainerStyle={styles.inputsContainer},
@@ -64,14 +67,15 @@ yarn add react-native-otp-entry
6467

6568
The `react-native-otp-entry` component accepts the following props:
6669

67-
| Prop | Type | Description |
68-
| ---------------------------- | ---------------------- | ----------------------------------------------------------------------------------------------------------- |
69-
| `numberOfDigits` | number | The number of digits to be displayed in the OTP entry. |
70-
| `focusColor` | ColorValue | The color of the input field border and stick when it is focused. |
71-
| `onTextChange` | (text: string) => void | A callback function that is invoked when the OTP text changes. It receives the updated text as an argument. |
72-
| `hideStick` | boolean | Hide cursor of the focused input. |
73-
| `theme` | Theme | Custom styles for each element. |
74-
| `focusStickBlinkingDuration` | number | The duration (in milliseconds) for the focus stick to blink. |
70+
| Prop | Type | Description |
71+
| ---------------------------- | ---------------------- | -------------------------------------------------------------------------------------------------------------- |
72+
| `numberOfDigits` | number | The number of digits to be displayed in the OTP entry. |
73+
| `focusColor` | ColorValue | The color of the input field border and stick when it is focused. |
74+
| `onTextChange` | (text: string) => void | A callback function is invoked when the OTP text changes. It receives the updated text as an argument. |
75+
| `onFilled` | (text: string) => void | A callback function is invoked when the OTP input is fully filled. It receives a full otp code as an argument. |
76+
| `hideStick` | boolean | Hide cursor of the focused input. |
77+
| `theme` | Theme | Custom styles for each element. |
78+
| `focusStickBlinkingDuration` | number | The duration (in milliseconds) for the focus stick to blink. |
7579

7680
| Theme | Type | Description |
7781
| ----------------------- | --------- | ---------------------------------------------------------------------------------- |

dist/src/OtpInput/OtpInput.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ const OtpInput_styles_1 = require("./OtpInput.styles");
77
const VerticalStick_1 = require("./VerticalStick");
88
const useOtpInput_1 = require("./useOtpInput");
99
exports.OtpInput = (0, react_1.forwardRef)((props, ref) => {
10-
const { models: { text, inputRef, focusedInputIndex }, actions: { clear, handlePress, handleTextChange }, forms: { setText }, } = (0, useOtpInput_1.useOtpInput)(props);
10+
const { models: { text, inputRef, focusedInputIndex }, actions: { clear, handlePress, handleTextChange }, forms: { setTextWithRef } } = (0, useOtpInput_1.useOtpInput)(props);
1111
const { numberOfDigits, hideStick, focusColor = "#A4D0A4", focusStickBlinkingDuration, theme = {}, } = props;
1212
const { containerStyle, inputsContainerStyle, pinCodeContainerStyle, pinCodeTextStyle, focusStickStyle, } = theme;
13-
(0, react_1.useImperativeHandle)(ref, () => ({ clear, setValue: setText }));
13+
(0, react_1.useImperativeHandle)(ref, () => ({ clear, setValue: setTextWithRef }));
1414
return (<react_native_1.View style={[OtpInput_styles_1.styles.container, containerStyle]}>
1515
<react_native_1.View style={[OtpInput_styles_1.styles.inputsContainer, inputsContainerStyle]}>
1616
{Array(numberOfDigits)

dist/src/OtpInput/OtpInput.test.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,14 @@ describe("OtpInput", () => {
1515
const stick = react_native_1.screen.getByTestId("otp-input-stick");
1616
expect(stick).toBeTruthy();
1717
});
18+
test("focusColor should not be overridden by theme", () => {
19+
renderOtpInput({
20+
focusColor: "#000",
21+
theme: { pinCodeContainerStyle: { borderColor: "#fff" } },
22+
});
23+
const inputs = react_native_1.screen.getAllByTestId("otp-input");
24+
expect(inputs[0]).toHaveStyle({ borderColor: "#000" });
25+
});
1826
// Test if the number of rendered inputs is equal to the number of digits
1927
test.each([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])("should render the correct number of inputs: %i", (numberOfDigits) => {
2028
renderOtpInput({ numberOfDigits: numberOfDigits });
@@ -58,5 +66,19 @@ describe("OtpInput", () => {
5866
});
5967
expect(react_native_1.screen.getByText("1")).toBeTruthy();
6068
});
69+
test('ref setValue() should set only the first "numberOfDigits" characters', () => {
70+
const ref = React.createRef();
71+
(0, react_native_1.render)(<OtpInput_1.OtpInput ref={ref} numberOfDigits={4}/>);
72+
const otp = "123456";
73+
(0, react_native_1.act)(() => {
74+
ref.current?.setValue(otp);
75+
});
76+
expect(react_native_1.screen.getByText("1")).toBeTruthy();
77+
expect(react_native_1.screen.getByText("2")).toBeTruthy();
78+
expect(react_native_1.screen.getByText("3")).toBeTruthy();
79+
expect(react_native_1.screen.getByText("4")).toBeTruthy();
80+
expect(react_native_1.screen.queryByText("5")).toBeFalsy();
81+
expect(react_native_1.screen.queryByText("6")).toBeFalsy();
82+
});
6183
});
6284
});

dist/src/OtpInput/OtpInput.types.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ export interface OtpInputProps {
33
numberOfDigits: number;
44
focusColor?: ColorValue;
55
onTextChange?: (text: string) => void;
6+
onFilled?: (text: string) => void;
67
hideStick?: boolean;
78
focusStickBlinkingDuration?: number;
89
theme?: Theme;

dist/src/OtpInput/useOtpInput.d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/// <reference types="react" />
22
import { TextInput } from "react-native";
33
import { OtpInputProps } from "./OtpInput.types";
4-
export declare const useOtpInput: ({ onTextChange }: OtpInputProps) => {
4+
export declare const useOtpInput: ({ onTextChange, onFilled, numberOfDigits }: OtpInputProps) => {
55
models: {
66
text: string;
77
inputRef: import("react").RefObject<TextInput>;
@@ -14,5 +14,6 @@ export declare const useOtpInput: ({ onTextChange }: OtpInputProps) => {
1414
};
1515
forms: {
1616
setText: import("react").Dispatch<import("react").SetStateAction<string>>;
17+
setTextWithRef: (value: string) => void;
1718
};
1819
};

dist/src/OtpInput/useOtpInput.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
33
exports.useOtpInput = void 0;
44
const react_1 = require("react");
55
const react_native_1 = require("react-native");
6-
const useOtpInput = ({ onTextChange }) => {
6+
const useOtpInput = ({ onTextChange, onFilled, numberOfDigits }) => {
77
const [text, setText] = (0, react_1.useState)("");
88
const inputRef = (0, react_1.useRef)(null);
99
const focusedInputIndex = text.length;
@@ -17,14 +17,21 @@ const useOtpInput = ({ onTextChange }) => {
1717
const handleTextChange = (value) => {
1818
setText(value);
1919
onTextChange?.(value);
20+
if (value.length === numberOfDigits) {
21+
onFilled?.(value);
22+
}
23+
};
24+
const setTextWithRef = (value) => {
25+
const normalizedValue = value.length > numberOfDigits ? value.slice(0, numberOfDigits) : value;
26+
handleTextChange(normalizedValue);
2027
};
2128
const clear = () => {
2229
setText("");
2330
};
2431
return {
2532
models: { text, inputRef, focusedInputIndex },
2633
actions: { handlePress, handleTextChange, clear },
27-
forms: { setText },
34+
forms: { setText, setTextWithRef },
2835
};
2936
};
3037
exports.useOtpInput = useOtpInput;

dist/src/OtpInput/useOtpInput.test.js

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,17 @@ describe("useOtpInput", () => {
2020
const { result } = renderUseOtInput();
2121
result.current.actions.clear();
2222
(0, react_native_1.act)(() => {
23-
expect(result.current.models.text).toBe("");
2423
expect(result.current.forms.setText).toHaveBeenCalledWith("");
2524
});
2625
});
26+
test("setTextWithRef() should only call setText the first 'numberOfDigits' characters", () => {
27+
jest.spyOn(React, "useState").mockImplementation(() => ["", jest.fn()]);
28+
const { result } = renderUseOtInput();
29+
result.current.forms.setTextWithRef("123456789");
30+
(0, react_native_1.act)(() => {
31+
expect(result.current.forms.setText).toHaveBeenCalledWith("123456");
32+
});
33+
});
2734
test("handlePress() should dismiss Keyboard if it's visible", () => {
2835
jest.spyOn(react_native_2.Keyboard, "dismiss");
2936
jest.spyOn(react_native_2.Keyboard, "isVisible").mockReturnValue(false);
@@ -61,4 +68,22 @@ describe("useOtpInput", () => {
6168
expect(mockOnTextChange).toHaveBeenCalledWith(value);
6269
});
6370
});
71+
test("onFilled() should be called when the input filled", () => {
72+
const value = "123456";
73+
const mockOnFilled = jest.fn();
74+
const { result } = renderUseOtInput({ onFilled: mockOnFilled });
75+
result.current.actions.handleTextChange(value);
76+
(0, react_native_1.act)(() => {
77+
expect(mockOnFilled).toHaveBeenCalledWith(value);
78+
});
79+
});
80+
test("onFilled() should NOT be called when the input is NOT filled", () => {
81+
const value = "12345";
82+
const mockOnFilled = jest.fn();
83+
const { result } = renderUseOtInput({ onFilled: mockOnFilled });
84+
result.current.actions.handleTextChange(value);
85+
(0, react_native_1.act)(() => {
86+
expect(mockOnFilled).not.toHaveBeenCalled();
87+
});
88+
});
6489
});

0 commit comments

Comments
 (0)