Skip to content

Commit f8505df

Browse files
Damian SznajderDamian Sznajder
authored andcommitted
Rewrite whole to functions
1 parent 21760ea commit f8505df

File tree

3 files changed

+191
-139
lines changed

3 files changed

+191
-139
lines changed

src/helpers.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export const fillOtpCode = (numberOfInputs: number, value?: string) => {
2+
const otpCode: { [key: string]: string } = {};
3+
for (let i = 0; i < numberOfInputs; i++) {
4+
otpCode[`${i}`] = (value && value[i]) || '';
5+
}
6+
return otpCode;
7+
};

src/index.tsx

Lines changed: 169 additions & 139 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,26 @@
1-
import React, { ReactNode, RefObject, Component } from 'react';
1+
import React, {
2+
RefObject,
3+
forwardRef,
4+
useCallback,
5+
useEffect,
6+
useImperativeHandle,
7+
useReducer,
8+
useRef,
9+
} from 'react';
210
import {
311
Clipboard,
412
Keyboard,
513
StyleProp,
614
TextInput,
15+
TextInputProps,
716
TextStyle,
817
View,
918
ViewStyle,
10-
TextInputProps,
1119
} from 'react-native';
1220

1321
import OtpInput from './OtpInput';
22+
import { ActionTypes, OtpInputsRef, Actions } from './types';
23+
import { fillOtpCode } from './helpers';
1424

1525
type Props = TextInputProps & {
1626
styles?: StyleProp<ViewStyle>;
@@ -23,164 +33,184 @@ type Props = TextInputProps & {
2333
testIDPrefix: string;
2434
};
2535

26-
type State = {
27-
otpCode: Array<string>;
28-
previousCopiedText?: string;
36+
const ACTION_TYPES: ActionTypes = {
37+
setOtpTextForIndex: 'setOtpTextForIndex',
38+
setOtpCode: 'setOtpCode',
39+
clearOtp: 'clearOtp',
2940
};
3041

31-
const MINIMAL_INDEX = 0;
32-
33-
class OtpInputs extends Component<Props, State> {
34-
constructor(props: Props) {
35-
super(props);
36-
37-
const inputs = [];
38-
39-
for (let index = 0; index < this.props.numberOfInputs; index++) {
40-
inputs[index] = React.createRef<TextInput>();
42+
const reducer = (state: { [key: string]: string }, action: Actions) => {
43+
switch (action.type) {
44+
case ACTION_TYPES.setOtpTextForIndex: {
45+
return {
46+
[`${action.payload.index}`]: action.payload.text,
47+
};
4148
}
4249

43-
this._interval = undefined;
44-
this.inputs = inputs as Array<RefObject<TextInput>>;
45-
this.state = {
46-
previousCopiedText: '',
47-
otpCode: [],
48-
};
49-
}
50-
51-
componentDidMount(): void {
52-
this._listenOnCopiedText();
53-
54-
this._interval = setInterval(() => {
55-
this._listenOnCopiedText();
56-
}, 1000);
57-
}
58-
59-
componentWillUnmount(): void {
60-
clearInterval(this._interval);
61-
}
62-
63-
inputs: Array<RefObject<TextInput>>;
64-
_interval: any;
65-
66-
reset = (): void => {
67-
this.setState({ otpCode: [] });
68-
this.props.handleChange('');
69-
this.inputs.forEach(i => i.current!.clear());
70-
};
71-
72-
_listenOnCopiedText = async (): Promise<void> => {
73-
const { numberOfInputs } = this.props;
74-
const { otpCode, previousCopiedText } = this.state;
75-
const copiedText = await Clipboard.getString();
76-
77-
if (
78-
copiedText &&
79-
copiedText.length === numberOfInputs &&
80-
copiedText !== otpCode.join('') &&
81-
copiedText !== previousCopiedText
82-
) {
83-
clearInterval(this._interval);
84-
this._handleAfterOtpAction(copiedText.split(''), numberOfInputs, true);
85-
}
86-
};
87-
88-
_handleAfterOtpAction = (
89-
otpCode: Array<string>,
90-
indexToFocus: number,
91-
fromClipboard?: boolean,
92-
): void => {
93-
const { handleChange, numberOfInputs } = this.props;
94-
handleChange(otpCode.join(''));
95-
96-
this.setState({
97-
otpCode,
98-
...(fromClipboard && { previousCopiedText: otpCode.join('') }),
99-
});
100-
101-
if (indexToFocus === numberOfInputs) {
102-
return Keyboard.dismiss();
103-
}
104-
105-
if (indexToFocus >= MINIMAL_INDEX && indexToFocus < numberOfInputs) {
106-
this._focusInput(indexToFocus);
107-
}
108-
};
109-
110-
_handleTextChange = (text: string, index: number): void => {
111-
const { numberOfInputs } = this.props;
112-
113-
if (!text.length) {
114-
this.inputs[index].current!.clear();
115-
return this._focusInput(index - 1);
50+
case ACTION_TYPES.setOtpCode: {
51+
return fillOtpCode(action.payload.numberOfInputs, action.payload.code);
11652
}
11753

118-
if (text.length === numberOfInputs) {
119-
this._handleAfterOtpAction(text.split(''), numberOfInputs, true);
120-
} else if (text) {
121-
let otpArray = this.state.otpCode;
122-
otpArray[index] = text[text.length - 1];
123-
this._handleAfterOtpAction(otpArray, index + 1);
54+
case ACTION_TYPES.clearOtp: {
55+
return fillOtpCode(action.payload);
12456
}
125-
};
12657

127-
_focusInput = (index: number): void => {
128-
if (index >= 0 && index < this.props.numberOfInputs) {
129-
this.inputs[index].current!.focus();
130-
}
131-
};
58+
default:
59+
return state;
60+
}
61+
};
13262

133-
_renderInputs = (): Array<JSX.Element> => {
134-
const {
63+
const OtpInputs = forwardRef<OtpInputsRef, Props>(
64+
(
65+
{
13566
autoCapitalize,
13667
clearTextOnFocus,
13768
focusStyles,
13869
inputContainerStyles,
13970
inputStyles,
71+
isRTL,
14072
keyboardType,
14173
numberOfInputs,
74+
placeholder,
14275
secureTextEntry,
14376
selectTextOnFocus,
77+
styles,
14478
testIDPrefix,
145-
isRTL,
146-
placeholder,
147-
} = this.props;
148-
const { otpCode } = this.state;
149-
const iterationArray = Array<number>(numberOfInputs).fill(0);
150-
151-
return iterationArray.map((_, index) => {
152-
let inputIndex = index;
153-
if (isRTL) {
154-
inputIndex = numberOfInputs - 1 - index;
79+
},
80+
ref,
81+
) => {
82+
const [otpCode, dispatch] = useReducer(reducer, numberOfInputs, fillOtpCode);
83+
const previousCopiedText: { current: string } = useRef('');
84+
const inputs: { current: Array<RefObject<TextInput>> } = useRef([]);
85+
86+
const handleInputTextChange = ({ text, index }: { text: string; index: number }) => {
87+
dispatch({
88+
type: ACTION_TYPES.setOtpTextForIndex,
89+
payload: {
90+
text,
91+
index,
92+
},
93+
});
94+
focusInput(index + 1);
95+
};
96+
97+
const handleTextChange = (text: string, index: number): void => {
98+
if (!text.length) {
99+
handleClearInput(index);
155100
}
156101

157-
return (
158-
<OtpInput
159-
autoCapitalize={autoCapitalize}
160-
clearTextOnFocus={clearTextOnFocus}
161-
firstInput={index === 0}
162-
focusStyles={focusStyles}
163-
handleTextChange={(text: string) => this._handleTextChange(text, inputIndex)}
164-
inputContainerStyles={inputContainerStyles}
165-
inputStyles={inputStyles}
166-
key={inputIndex}
167-
keyboardType={keyboardType}
168-
numberOfInputs={numberOfInputs}
169-
placeholder={placeholder}
170-
ref={this.inputs[inputIndex]}
171-
secureTextEntry={secureTextEntry}
172-
selectTextOnFocus={selectTextOnFocus}
173-
testID={`${testIDPrefix}-${inputIndex}`}
174-
value={otpCode[inputIndex]}
175-
/>
176-
);
177-
});
178-
};
179-
180-
render(): ReactNode {
181-
return <View style={this.props.styles}>{this._renderInputs()}</View>;
182-
}
183-
}
102+
if (text) {
103+
handleInputTextChange({ text, index });
104+
}
105+
106+
if (index === numberOfInputs - 1 && text) {
107+
Keyboard.dismiss();
108+
}
109+
};
110+
111+
const focusInput = useCallback(
112+
(index: number): void => {
113+
if (index >= 0 && index < numberOfInputs) {
114+
const input = inputs.current[index];
115+
input && input.current && input.current.focus();
116+
}
117+
},
118+
[numberOfInputs],
119+
);
120+
121+
const handleClearInput = useCallback(
122+
(inputIndex: number) => {
123+
const input = inputs.current[inputIndex];
124+
input && input.current && input.current.clear();
125+
focusInput(inputIndex - 1);
126+
},
127+
[focusInput],
128+
);
129+
130+
const fillInputs = useCallback(
131+
(code: string) => {
132+
dispatch({ type: ACTION_TYPES.setOtpCode, payload: { numberOfInputs, code } });
133+
},
134+
[numberOfInputs],
135+
);
136+
137+
useImperativeHandle(
138+
ref,
139+
() => ({
140+
reset: (): void => {
141+
dispatch({ type: ACTION_TYPES.clearOtp, payload: numberOfInputs });
142+
inputs.current.forEach(input => input && input.current && input.current.clear());
143+
previousCopiedText.current = '';
144+
},
145+
}),
146+
[numberOfInputs],
147+
);
148+
149+
const listenOnCopiedText = useCallback(async (): Promise<void> => {
150+
const copiedText = await Clipboard.getString();
151+
const otpCodeValue = Object.values(otpCode).join('');
152+
153+
if (
154+
copiedText &&
155+
copiedText.length === numberOfInputs &&
156+
copiedText !== otpCodeValue &&
157+
copiedText !== previousCopiedText.current
158+
) {
159+
previousCopiedText.current = otpCodeValue;
160+
fillInputs(copiedText);
161+
}
162+
}, [fillInputs, numberOfInputs, otpCode]);
163+
164+
useEffect(() => {
165+
const interval = setInterval(() => {
166+
listenOnCopiedText();
167+
}, 1000);
168+
169+
return () => {
170+
clearInterval(interval);
171+
};
172+
}, [listenOnCopiedText, numberOfInputs]);
173+
174+
const renderInputs = (): Array<JSX.Element> => {
175+
const iterationArray = Array<number>(numberOfInputs).fill(0);
176+
177+
return iterationArray.map((_, index) => {
178+
let inputIndex = index;
179+
if (isRTL) {
180+
inputIndex = numberOfInputs - 1 - index;
181+
}
182+
const inputValue = otpCode[`${inputIndex}`];
183+
184+
if (!inputs.current[inputIndex]) {
185+
inputs.current[inputIndex] = React.createRef<TextInput>();
186+
}
187+
188+
return (
189+
<OtpInput
190+
autoCapitalize={autoCapitalize}
191+
clearTextOnFocus={clearTextOnFocus}
192+
firstInput={index === 0}
193+
focusStyles={focusStyles}
194+
handleTextChange={(text: string) => handleTextChange(text, inputIndex)}
195+
inputContainerStyles={inputContainerStyles}
196+
inputStyles={inputStyles}
197+
key={inputIndex}
198+
keyboardType={keyboardType}
199+
numberOfInputs={numberOfInputs}
200+
placeholder={placeholder}
201+
ref={inputs.current[inputIndex]}
202+
secureTextEntry={secureTextEntry}
203+
selectTextOnFocus={selectTextOnFocus}
204+
testID={`${testIDPrefix}-${inputIndex}`}
205+
inputValue={inputValue}
206+
/>
207+
);
208+
});
209+
};
210+
211+
return <View style={styles}>{renderInputs()}</View>;
212+
},
213+
);
184214

185215
export default OtpInputs;
186216

src/types.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
type SetOtpTextForIndex = { type: 'setOtpTextForIndex'; payload: { index: number; text: string } };
2+
type SetOtpCode = { type: 'setOtpCode'; payload: { numberOfInputs: number; code: string } };
3+
type ClearOtp = { type: 'clearOtp'; payload: number };
4+
5+
export type ActionTypes = {
6+
setOtpTextForIndex: 'setOtpTextForIndex';
7+
setOtpCode: 'setOtpCode';
8+
clearOtp: 'clearOtp';
9+
};
10+
11+
export type Actions = SetOtpTextForIndex | SetOtpCode | ClearOtp;
12+
13+
export type OtpInputsRef = {
14+
reset: () => void;
15+
};

0 commit comments

Comments
 (0)