Skip to content

Commit 950d3a6

Browse files
authored
Merge pull request #1 from johelder/adjust/handle-with-input-focus
adjust/handle-with-input-focus
2 parents e52155e + 808ad19 commit 950d3a6

14 files changed

+219
-175
lines changed

README.md

+118-8
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ npm install react-native-input-code-otp
1212

1313
## Usage
1414

15-
```ts
15+
```tsx
1616
import {
1717
TextInputOTP,
1818
TextInputOTPSlot,
@@ -35,7 +35,7 @@ export function MyComponent() {
3535
<TextInputOTPSlot index={5} />
3636
</TextInputOTPGroup>
3737
</TextInputOTP>
38-
)
38+
);
3939
}
4040
```
4141

@@ -46,10 +46,14 @@ export function MyComponent() {
4646
| `maxLength` | number - Required | The max number of digits to OTP code. |
4747
| `onFilled` | (code: string) => void - Optional | The callback function is executed when the OTP input has been entirely completed, and it receives the OTP code as a parameter. |
4848

49+
---
50+
4951
| TextInputOTPGroup | Type | Description |
5052
| ----------------- | -------------------- | ----------------------------- |
5153
| `groupStyles` | ViewStyle - Optional | Custom styles for the `View`. |
5254

55+
---
56+
5357
| TextInputOTPSlot | Type | Description |
5458
| ----------------------- | -------------------- | ----------------------------------------------------------------------------------------------------------- |
5559
| `index` | number - Required | The position of the slot within the OTP input sequence. Each slot must have a unique index starting from 0. |
@@ -58,6 +62,8 @@ export function MyComponent() {
5862
| `slotTextStyles` | TextStyle - Optional | Custom styles for the `Text`. |
5963
| `focusedSlotTextStyles` | TextStyle - Optional | Custom styles applied to the slot `Text` when it is focused. |
6064

65+
---
66+
6167
| TextInputOTPSeparator | Type | Description |
6268
| --------------------- | -------------------- | ----------------------------- |
6369
| `separatorStyles` | ViewStyle - Optional | Custom styles for the `View`. |
@@ -66,12 +72,116 @@ export function MyComponent() {
6672

6773
The `TextInputOTP` component exposes these functions with `ref`:
6874

69-
| Prop | Type | Description |
70-
| ---------- | ------------------------ | -------------------------------------------------------------------------- |
71-
| `clear` | () => void; | Resets the OTP input by clearing all entered values. |
72-
| `focus` | () => void; | Activates the OTP input field, allowing the user to type. |
73-
| `blue` | () => void; | Deactivates the OTP input field, removing focus. |
74-
| `setValue` | (value: string) => void; | Sets a custom value to the OTP input fields, overriding any current input. |
75+
| Prop | Type | Description |
76+
| ---------- | ----------------------- | -------------------------------------------------------------------------- |
77+
| `clear` | () => void | Resets the OTP input by clearing all entered values. |
78+
| `focus` | () => void | Activates the OTP input field, allowing the user to type. |
79+
| `blue` | () => void | Deactivates the OTP input field, removing focus. |
80+
| `setValue` | (value: string) => void | Sets a custom value to the OTP input fields, overriding any current input. |
81+
82+
## Examples
83+
84+
<details>
85+
<summary>Usage with react-hook-form</summary>
86+
87+
```tsx
88+
import { Button, View } from 'react-native';
89+
import {
90+
TextInputOTP,
91+
TextInputOTPSlot,
92+
TextInputOTPGroup,
93+
TextInputOTPSeparator,
94+
} from 'react-native-input-code-otp';
95+
import { Controller, useForm } from 'react-hook-form';
96+
97+
type FormValues = {
98+
code: string;
99+
};
100+
101+
export function MyComponent() {
102+
const { control, handleSubmit } = useForm<FormValues>({
103+
defaultValues: {
104+
code: '',
105+
},
106+
});
107+
108+
function onSubmit({ code }: FormValues) {
109+
console.log({ code });
110+
}
111+
112+
return (
113+
<View>
114+
<Controller
115+
name="code"
116+
control={control}
117+
render={({ field: { onChange, value } }) => (
118+
<TextInputOTP value={value} onChangeText={onChange} maxLength={6}>
119+
<TextInputOTPGroup>
120+
<TextInputOTPSlot index={0} />
121+
<TextInputOTPSlot index={1} />
122+
<TextInputOTPSlot index={2} />
123+
</TextInputOTPGroup>
124+
<TextInputOTPSeparator />
125+
<TextInputOTPGroup>
126+
<TextInputOTPSlot index={3} />
127+
<TextInputOTPSlot index={4} />
128+
<TextInputOTPSlot index={5} />
129+
</TextInputOTPGroup>
130+
</TextInputOTP>
131+
)}
132+
/>
133+
134+
<Button title="Submit" onPress={handleSubmit(onSubmit)} />
135+
</View>
136+
);
137+
}
138+
```
139+
140+
</details>
141+
142+
<details>
143+
<summary>Usage of ref to programmatically set OTP value</summary>
144+
145+
```tsx
146+
import { useRef } from 'react';
147+
import { Button, View } from 'react-native';
148+
import {
149+
TextInputOTP,
150+
TextInputOTPSlot,
151+
TextInputOTPGroup,
152+
TextInputOTPSeparator,
153+
} from 'react-native-input-code-otp';
154+
155+
export function MyComponent() {
156+
const inputRef = useRef<TextInputOTPRef>(null);
157+
158+
function onSomeAction() {
159+
inputRef.current?.setValue('123456');
160+
}
161+
162+
return (
163+
<View>
164+
<TextInputOTP ref={inputRef} maxLength={6}>
165+
<TextInputOTPGroup>
166+
<TextInputOTPSlot index={0} />
167+
<TextInputOTPSlot index={1} />
168+
<TextInputOTPSlot index={2} />
169+
</TextInputOTPGroup>
170+
<TextInputOTPSeparator />
171+
<TextInputOTPGroup>
172+
<TextInputOTPSlot index={3} />
173+
<TextInputOTPSlot index={4} />
174+
<TextInputOTPSlot index={5} />
175+
</TextInputOTPGroup>
176+
</TextInputOTP>
177+
178+
<Button title="Submit" onPress={onSomeAction} />
179+
</View>
180+
);
181+
}
182+
```
183+
184+
</details>
75185

76186
## Contributing
77187

example/src/App.tsx

+3-13
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,15 @@
1-
import { useRef } from 'react';
2-
import { StyleSheet, View, useColorScheme } from 'react-native';
1+
import { StyleSheet, View } from 'react-native';
32
import {
43
TextInputOTP,
54
TextInputOTPSlot,
65
TextInputOTPGroup,
76
TextInputOTPSeparator,
8-
type TextInputOTPRef,
97
} from 'react-native-input-code-otp';
108

119
export default function App() {
12-
const colorSchema = useColorScheme();
13-
const inputRef = useRef<TextInputOTPRef>(null);
14-
const backgroundColor = colorSchema === 'light' ? 'white' : 'black';
15-
16-
function handleSubmit(code: string) {
17-
console.log({ code });
18-
}
19-
2010
return (
21-
<View style={[styles.container, { backgroundColor }]}>
22-
<TextInputOTP ref={inputRef} maxLength={6} onFilled={handleSubmit}>
11+
<View style={styles.container}>
12+
<TextInputOTP maxLength={6} onFilled={(code) => console.log(code)}>
2313
<TextInputOTPGroup>
2414
<TextInputOTPSlot index={0} />
2515
<TextInputOTPSlot index={1} />

src/components/text-input-otp-slot.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,12 @@ function TextInputOTPSlotComponent({
2525

2626
return (
2727
<Pressable
28+
onPress={handlePress}
2829
style={StyleSheet.flatten([
2930
styles.slot,
3031
borderStyles,
3132
isFocused ? focusedSlotStyles : slotStyles,
3233
])}
33-
onPress={() => handlePress(index)}
3434
{...rest}
3535
>
3636
{code[index] && (

src/components/text-input.tsx

+4-13
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,8 @@ export const TextInput = forwardRef<
77
TextInputOTPRef,
88
Omit<TextInputOTPProps, 'children'>
99
>(({ autoFocus = true, ...rest }, ref) => {
10-
const {
11-
inputRef,
12-
handleKeyPress,
13-
handleChangeText,
14-
setValue,
15-
focus,
16-
blur,
17-
clear,
18-
} = useTextInputOTP();
10+
const { inputRef, handleChangeText, setValue, focus, blur, clear } =
11+
useTextInputOTP();
1912

2013
useImperativeHandle(ref, () => ({
2114
setValue,
@@ -26,14 +19,12 @@ export const TextInput = forwardRef<
2619

2720
return (
2821
<RNTextInput
29-
value=""
3022
ref={inputRef}
31-
onKeyPress={handleKeyPress}
32-
onChangeText={handleChangeText}
33-
style={styles.input}
3423
keyboardType="number-pad"
3524
autoFocus={autoFocus}
25+
style={styles.input}
3626
{...rest}
27+
onChangeText={handleChangeText}
3728
/>
3829
);
3930
});

src/hooks/use-slot-border-styles.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { UseSlotBorderStylesProps } from '../types/text-input-otp-slot';
1+
import type { UseSlotBorderStylesProps } from '../types';
22

33
export function useSlotBorderStyles({
44
isFocused,

src/hooks/use-text-input-otp.tsx

+28-61
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,13 @@ import {
77
useState,
88
type PropsWithChildren,
99
} from 'react';
10-
import type {
11-
NativeSyntheticEvent,
12-
TextInput,
13-
TextInputKeyPressEventData,
14-
} from 'react-native';
15-
import { BACKSPACE_KEY } from '../constants';
10+
import type { TextInput } from 'react-native';
1611
import type { TextInputOTPContextProps, TextInputOTPProps } from '../types';
1712

1813
const TextInputOTPContext = createContext<TextInputOTPContextProps>({
19-
code: [],
14+
code: '',
2015
currentIndex: 0,
2116
inputRef: { current: null },
22-
handleKeyPress: () => {},
2317
handleChangeText: () => {},
2418
handlePress: () => {},
2519
setValue: () => {},
@@ -31,76 +25,50 @@ const TextInputOTPContext = createContext<TextInputOTPContextProps>({
3125
export function TextInputOTPProvider({
3226
autoFocus = true,
3327
maxLength,
28+
value = '',
3429
onFilled,
30+
onChangeText,
3531
children,
3632
}: PropsWithChildren<
37-
Pick<TextInputOTPProps, 'autoFocus' | 'maxLength' | 'onFilled'>
33+
Pick<
34+
TextInputOTPProps,
35+
'autoFocus' | 'maxLength' | 'onFilled' | 'onChangeText' | 'value'
36+
>
3837
>) {
39-
const [code, setCode] = useState(Array(maxLength).fill(''));
40-
const [currentIndex, setCurrentIndex] = useState(autoFocus ? 0 : -1);
38+
const [code, setCode] = useState(value);
39+
const [currentIndex, setCurrentIndex] = useState(() => (autoFocus ? 0 : -1));
4140
const inputRef = useRef<TextInput>(null);
4241

43-
const updateCodeAtIndex = useCallback(
44-
(index: number, value: string) => {
45-
const newCode = [...code];
46-
newCode[index] = value;
47-
setCode(newCode);
48-
49-
return newCode;
50-
},
51-
[code]
52-
);
53-
54-
const handleKeyPress = useCallback(
55-
(event: NativeSyntheticEvent<TextInputKeyPressEventData>) => {
56-
const { key } = event.nativeEvent;
57-
58-
if (key !== BACKSPACE_KEY) {
59-
return;
60-
}
61-
62-
if (!code[currentIndex] && currentIndex > 0) {
63-
updateCodeAtIndex(currentIndex - 1, '');
64-
setCurrentIndex((prev) => prev - 1);
65-
return;
66-
}
67-
68-
updateCodeAtIndex(currentIndex, '');
69-
},
70-
[code, currentIndex, updateCodeAtIndex]
71-
);
72-
7342
const handleChangeText = useCallback(
7443
(text: string) => {
75-
if (text.length > 1) {
44+
if (text.length > maxLength) {
7645
return;
7746
}
7847

79-
const updatedCode = updateCodeAtIndex(currentIndex, text);
48+
setCode(text);
49+
onChangeText?.(text);
8050

81-
if (currentIndex < maxLength - 1) {
82-
setCurrentIndex((prev) => prev + 1);
83-
return;
51+
if (text.length === maxLength) {
52+
onFilled?.(text);
8453
}
8554

86-
const finalCode = [...updatedCode].join('');
87-
88-
if (finalCode.length === maxLength) {
89-
onFilled?.(finalCode);
55+
if (text.length < maxLength) {
56+
setCurrentIndex(text.length);
9057
}
9158
},
92-
[currentIndex, maxLength, onFilled, updateCodeAtIndex]
59+
[maxLength, onChangeText, onFilled]
9360
);
9461

95-
function handlePress(index: number) {
96-
setCurrentIndex(index);
62+
const handlePress = useCallback(() => {
63+
setCurrentIndex(code.length);
9764
inputRef.current?.focus();
98-
}
65+
}, [code.length]);
9966

10067
const setValue = useCallback(
10168
(text: string) => {
102-
const value = text.length > maxLength ? text.slice(0, maxLength) : text;
103-
setCode(Array.from(value));
69+
const filledCode =
70+
text.length > maxLength ? text.slice(0, maxLength) : text;
71+
setCode(filledCode);
10472
setCurrentIndex(maxLength - 1);
10573
},
10674
[maxLength]
@@ -115,24 +83,23 @@ export function TextInputOTPProvider({
11583
}
11684

11785
const clear = useCallback(() => {
118-
setCode(Array(maxLength).fill(''));
86+
setCode('');
11987
setCurrentIndex(0);
120-
}, [maxLength]);
88+
}, []);
12189

12290
const contextValue = useMemo(
12391
() => ({
124-
code,
92+
code: value || code,
12593
currentIndex,
12694
inputRef,
127-
handleKeyPress,
12895
handleChangeText,
12996
handlePress,
13097
setValue,
13198
focus,
13299
blur,
133100
clear,
134101
}),
135-
[clear, code, currentIndex, handleChangeText, handleKeyPress, setValue]
102+
[clear, code, currentIndex, handleChangeText, handlePress, setValue, value]
136103
);
137104

138105
return (

0 commit comments

Comments
 (0)