From 885521d6fb4eb52f4fb62a33eb23c89962e0a128 Mon Sep 17 00:00:00 2001 From: Michael Vargas Date: Thu, 16 Jan 2020 23:00:55 -0500 Subject: [PATCH 1/9] Fix: warning UNSAFE_componentWillReceiveProps --- index.js | 99 ++++++++++++++++++++++++++++++-------------------------- 1 file changed, 53 insertions(+), 46 deletions(-) diff --git a/index.js b/index.js index 408edad..57856c6 100644 --- a/index.js +++ b/index.js @@ -1,71 +1,78 @@ -import React, { Component } from 'react' +import React, { useEffect, useState, useRef } from "react"; import { TextInput, findNodeHandle, NativeModules, Platform -} from 'react-native' +} from "react-native"; -const mask = NativeModules.RNTextInputMask.mask -const unmask = NativeModules.RNTextInputMask.unmask -const setMask = NativeModules.RNTextInputMask.setMask -export { mask, unmask, setMask } +const mask = NativeModules.RNTextInputMask.mask; +const unmask = NativeModules.RNTextInputMask.unmask; +const setMask = NativeModules.RNTextInputMask.setMask; +export { mask, unmask, setMask }; -export default class TextInputMask extends Component { - static defaultProps = { - maskDefaultValue: true, - } - - masked = false +function TextInputMask(props) { + const inputRef = useRef(); + const masked = useRef(false); + const [prevValue, setPrevValue] = useState(props.value); + const [prevMask, setPrevMask] = useState(props.mask); - componentDidMount() { - if (this.props.maskDefaultValue && - this.props.mask && - this.props.value) { - mask(this.props.mask, '' + this.props.value, text => - this.input && this.input.setNativeProps({ text }), - ) + useEffect(() => { + if (props.maskDefaultValue && props.mask && props.value) { + mask( + props.mask, + "" + props.value, + text => inputRef.current && inputRef.current.setNativeProps({ text }) + ); } - if (this.props.mask && !this.masked) { - this.masked = true - setMask(findNodeHandle(this.input), this.props.mask) + if (props.mask && !masked.current) { + masked.current = true; + setMask(findNodeHandle(inputRef.current), props.mask); } - } + }, []); - componentWillReceiveProps(nextProps) { - if (nextProps.mask && (this.props.value !== nextProps.value)) { - mask(this.props.mask, '' + nextProps.value, text => - this.input && this.input.setNativeProps({ text }) - ); - } + if (props.mask && prevValue !== props.value) { + mask( + prevValue, + "" + value, + text => inputRef.current && inputRef.current.setNativeProps({ text }) + ); + setPrevValue(props.value); + } - if (this.props.mask !== nextProps.mask) { - setMask(findNodeHandle(this.input), nextProps.mask) - } + if (prevMask !== props.mask) { + setMask(findNodeHandle(inputRef.current), props.mask); + setPrevMask(props.mask); } - render() { - return ( { - this.input = ref - if (typeof this.props.refInput === 'function') { - this.props.refInput(ref) + inputRef.current = ref; + if (typeof props.refInput === "function") { + props.refInput(ref); } }} - multiline={this.props.mask && Platform.OS === 'ios' ? false : this.props.multiline} + multiline={props.mask && Platform.OS === "ios" ? false : props.multiline} onChangeText={masked => { - if (this.props.mask) { - const _unmasked = unmask(this.props.mask, masked, unmasked => { - this.props.onChangeText && this.props.onChangeText(masked, unmasked) - }) + if (props.mask) { + const _unmasked = unmask(props.mask, masked, unmasked => { + props.onChangeText && props.onChangeText(masked, unmasked); + }); } else { - this.props.onChangeText && this.props.onChangeText(masked) + props.onChangeText && props.onChangeText(masked); } }} - />); - } + /> + ); } + +TextInputMask.defaultProps = { + maskDefaultValue: true +}; + +export default TextInputMask; From 007d6b9b0d516980c493038afe3c1a8e3383f9dd Mon Sep 17 00:00:00 2001 From: Michael Vargas Date: Thu, 16 Jan 2020 23:01:14 -0500 Subject: [PATCH 2/9] Add typescript support --- index.d.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 index.d.ts diff --git a/index.d.ts b/index.d.ts new file mode 100644 index 0000000..2ed3b73 --- /dev/null +++ b/index.d.ts @@ -0,0 +1,19 @@ +import * as React from "react"; +import { TextInputProps, TextInput } from "react-native"; + +export type onChangeTextCallback = ( + formatted: string, + extracted?: string +) => void; + +export interface TextInputMaskProps + extends Omit { + maskDefaultValue?: boolean; + mask?: string; + onChangeText?: onChangeTextCallback; + refInput?: React.LegacyRef; +} + +export default >( + function TextInputMask(): JSX.Element {} +); From dc2fb1668e6ddc7e9384f430f039f09ef60f9e3c Mon Sep 17 00:00:00 2001 From: Michael Vargas Date: Thu, 16 Jan 2020 23:03:38 -0500 Subject: [PATCH 3/9] Update README install to avoid use_frameworks --- README.md | 23 +---------------------- package.json | 2 +- 2 files changed, 2 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 43a151d..5b9a336 100644 --- a/README.md +++ b/README.md @@ -24,28 +24,7 @@ yarn add react-native-text-input-mask # Installation
- For RN >= 0.61 - -#### iOS - -1. Add following lines to your target in `Podfile` -``` -use_frameworks! -pod 'RNInputMask', :path => '../node_modules/react-native-text-input-mask/ios/InputMask' -``` -2. Run following command -```bash -cd ios && pod install -``` - -#### Android - -No need to do anything. - -
- -
- For RN = 0.60.* + For RN >= 0.60 #### iOS diff --git a/package.json b/package.json index ca59f9d..eb03c92 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-text-input-mask", - "version": "2.0.0", + "version": "2.0.1", "description": "Text input mask for React Native.", "main": "index.js", "repository": { From 56ea9bd6d5a4b27834fbc70d57f2a7500a2e2017 Mon Sep 17 00:00:00 2001 From: Michael Vargas Date: Thu, 16 Jan 2020 23:59:11 -0500 Subject: [PATCH 4/9] Add forwardRef to TextInput --- index.d.ts | 8 ++++---- index.js | 31 +++++++++++++++---------------- 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/index.d.ts b/index.d.ts index 2ed3b73..9f872f3 100644 --- a/index.d.ts +++ b/index.d.ts @@ -11,9 +11,9 @@ export interface TextInputMaskProps maskDefaultValue?: boolean; mask?: string; onChangeText?: onChangeTextCallback; - refInput?: React.LegacyRef; } -export default >( - function TextInputMask(): JSX.Element {} -); +export default React.forwardRef(function( + props, + ref +): JSX.Element {}); diff --git a/index.js b/index.js index 57856c6..a99261f 100644 --- a/index.js +++ b/index.js @@ -12,11 +12,11 @@ const unmask = NativeModules.RNTextInputMask.unmask; const setMask = NativeModules.RNTextInputMask.setMask; export { mask, unmask, setMask }; -function TextInputMask(props) { +const TextInputMask = React.forwardRef((props, ref) => { const inputRef = useRef(); const masked = useRef(false); - const [prevValue, setPrevValue] = useState(props.value); const [prevMask, setPrevMask] = useState(props.mask); + const [prevValue, setPrevValue] = useState(props.mask); useEffect(() => { if (props.maskDefaultValue && props.mask && props.value) { @@ -29,21 +29,22 @@ function TextInputMask(props) { if (props.mask && !masked.current) { masked.current = true; - setMask(findNodeHandle(inputRef.current), props.mask); + inputRef.current && setMask(findNodeHandle(inputRef.current), props.mask); } }, []); - if (props.mask && prevValue !== props.value) { - mask( - prevValue, - "" + value, - text => inputRef.current && inputRef.current.setNativeProps({ text }) - ); + if (prevValue !== props.value) { + props.mask && + mask( + props.mask, + "" + props.value, + text => !!inputRef.current && inputRef.current.setNativeProps({ text }) + ); setPrevValue(props.value); } if (prevMask !== props.mask) { - setMask(findNodeHandle(inputRef.current), props.mask); + inputRef.current && setMask(findNodeHandle(inputRef.current), props.mask); setPrevMask(props.mask); } @@ -51,11 +52,9 @@ function TextInputMask(props) { { - inputRef.current = ref; - if (typeof props.refInput === "function") { - props.refInput(ref); - } + ref={textInputRef => { + inputRef.current = textInputRef; + ref && ref(textInputRef); }} multiline={props.mask && Platform.OS === "ios" ? false : props.multiline} onChangeText={masked => { @@ -69,7 +68,7 @@ function TextInputMask(props) { }} /> ); -} +}); TextInputMask.defaultProps = { maskDefaultValue: true From 1f0df694ae79b0344ee9b1e51693a86f8f0d75f9 Mon Sep 17 00:00:00 2001 From: Michael Vargas Date: Fri, 17 Jan 2020 07:46:30 -0500 Subject: [PATCH 5/9] Refactoring hooks and update typescript --- index.d.ts | 9 ++++----- index.js | 37 ++++++++++++++++--------------------- 2 files changed, 20 insertions(+), 26 deletions(-) diff --git a/index.d.ts b/index.d.ts index 9f872f3..e299de6 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,4 +1,4 @@ -import * as React from "react"; +import React, { ForwardRefExoticComponent, PropsWithoutRef, RefAttributes} from "react"; import { TextInputProps, TextInput } from "react-native"; export type onChangeTextCallback = ( @@ -13,7 +13,6 @@ export interface TextInputMaskProps onChangeText?: onChangeTextCallback; } -export default React.forwardRef(function( - props, - ref -): JSX.Element {}); +declare const TextInputMask : ForwardRefExoticComponent & RefAttributes>; + +export default TextInputMask; \ No newline at end of file diff --git a/index.js b/index.js index a99261f..cfe3b13 100644 --- a/index.js +++ b/index.js @@ -1,4 +1,4 @@ -import React, { useEffect, useState, useRef } from "react"; +import React, { useEffect, useRef } from "react"; import { TextInput, @@ -14,39 +14,34 @@ export { mask, unmask, setMask }; const TextInputMask = React.forwardRef((props, ref) => { const inputRef = useRef(); - const masked = useRef(false); - const [prevMask, setPrevMask] = useState(props.mask); - const [prevValue, setPrevValue] = useState(props.mask); + const masked = useRef(); useEffect(() => { - if (props.maskDefaultValue && props.mask && props.value) { + if (props.maskDefaultValue && props.mask && props.value) mask( props.mask, "" + props.value, text => inputRef.current && inputRef.current.setNativeProps({ text }) ); - } - if (props.mask && !masked.current) { - masked.current = true; inputRef.current && setMask(findNodeHandle(inputRef.current), props.mask); + masked.current = true; } }, []); - if (prevValue !== props.value) { - props.mask && - mask( - props.mask, - "" + props.value, - text => !!inputRef.current && inputRef.current.setNativeProps({ text }) - ); - setPrevValue(props.value); - } - - if (prevMask !== props.mask) { + // Check if value change + useEffect(() => { + mask( + props.mask, + "" + props.value, + text => inputRef.current && inputRef.current.setNativeProps({ text }) + ); + }, [props.value]); + + //Check if mask change + useEffect(() => { inputRef.current && setMask(findNodeHandle(inputRef.current), props.mask); - setPrevMask(props.mask); - } + }, [props.mask]); return ( Date: Fri, 17 Jan 2020 13:35:52 -0500 Subject: [PATCH 6/9] add hook useCombinedRefs --- index.d.ts | 4 ++-- index.js | 43 ++++++++++++++++++++++++++++++++----------- 2 files changed, 34 insertions(+), 13 deletions(-) diff --git a/index.d.ts b/index.d.ts index e299de6..bf74471 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,7 +1,7 @@ import React, { ForwardRefExoticComponent, PropsWithoutRef, RefAttributes} from "react"; import { TextInputProps, TextInput } from "react-native"; -export type onChangeTextCallback = ( +export type onChangeTextMask = ( formatted: string, extracted?: string ) => void; @@ -10,7 +10,7 @@ export interface TextInputMaskProps extends Omit { maskDefaultValue?: boolean; mask?: string; - onChangeText?: onChangeTextCallback; + onChangeText?: onChangeTextMask; } declare const TextInputMask : ForwardRefExoticComponent & RefAttributes>; diff --git a/index.js b/index.js index cfe3b13..9defc65 100644 --- a/index.js +++ b/index.js @@ -1,4 +1,4 @@ -import React, { useEffect, useRef } from "react"; +import React, { useEffect, useLayoutEffect, useRef } from "react"; import { TextInput, @@ -12,11 +12,31 @@ const unmask = NativeModules.RNTextInputMask.unmask; const setMask = NativeModules.RNTextInputMask.setMask; export { mask, unmask, setMask }; -const TextInputMask = React.forwardRef((props, ref) => { - const inputRef = useRef(); - const masked = useRef(); +function useCombinedRefs(...refs) { + const targetRef = useRef() useEffect(() => { + refs.forEach(ref => { + if (!ref) return + + if (typeof ref === 'function') { + ref(targetRef.current) + } else { + ref.current = targetRef.current + } + }) + }, [refs]) + + return targetRef +} + +const TextInputMask = React.forwardRef((props, ref) => { + const inputRef = useRef(null); + const combinedRef = useCombinedRefs(ref, inputRef) + const masked = useRef(false); + const isMounted = useRef(false); + + useLayoutEffect(() => { if (props.maskDefaultValue && props.mask && props.value) mask( props.mask, @@ -27,11 +47,15 @@ const TextInputMask = React.forwardRef((props, ref) => { inputRef.current && setMask(findNodeHandle(inputRef.current), props.mask); masked.current = true; } + isMounted.current = true; + return ()=>{ + isMounted.current = false + } }, []); // Check if value change useEffect(() => { - mask( + isMounted.current && props.mask && mask( props.mask, "" + props.value, text => inputRef.current && inputRef.current.setNativeProps({ text }) @@ -40,17 +64,14 @@ const TextInputMask = React.forwardRef((props, ref) => { //Check if mask change useEffect(() => { - inputRef.current && setMask(findNodeHandle(inputRef.current), props.mask); + isMounted.current && inputRef.current && setMask(findNodeHandle(inputRef.current), props.mask); }, [props.mask]); return ( { - inputRef.current = textInputRef; - ref && ref(textInputRef); - }} + ref={combinedRef} multiline={props.mask && Platform.OS === "ios" ? false : props.multiline} onChangeText={masked => { if (props.mask) { @@ -69,4 +90,4 @@ TextInputMask.defaultProps = { maskDefaultValue: true }; -export default TextInputMask; +export default React.memo(TextInputMask); From e918823b6a450df9edd9833d8f1a47a4eb80fbb6 Mon Sep 17 00:00:00 2001 From: Michael Vargas Date: Sat, 18 Jan 2020 08:43:09 -0500 Subject: [PATCH 7/9] Fix trigger onChangeText and add imperativeHandle --- index.d.ts | 2 +- index.js | 119 ++++++++++++++++++++++++++++++----------------------- 2 files changed, 68 insertions(+), 53 deletions(-) diff --git a/index.d.ts b/index.d.ts index bf74471..bbed0c9 100644 --- a/index.d.ts +++ b/index.d.ts @@ -2,7 +2,7 @@ import React, { ForwardRefExoticComponent, PropsWithoutRef, RefAttributes} from import { TextInputProps, TextInput } from "react-native"; export type onChangeTextMask = ( - formatted: string, + formatted?: string, extracted?: string ) => void; diff --git a/index.js b/index.js index 9defc65..dfbef9a 100644 --- a/index.js +++ b/index.js @@ -1,4 +1,4 @@ -import React, { useEffect, useLayoutEffect, useRef } from "react"; +import React, { useEffect, useRef, useImperativeHandle, useCallback } from "react"; import { TextInput, @@ -12,39 +12,64 @@ const unmask = NativeModules.RNTextInputMask.unmask; const setMask = NativeModules.RNTextInputMask.setMask; export { mask, unmask, setMask }; -function useCombinedRefs(...refs) { - const targetRef = useRef() +function TextInputMask(props, ref){ + const inputRef = useRef(); + const prevMask = useRef(props.mask) + const prevValue = useRef(props.value) + const masked = useRef(false); + const isMounted = useRef(false); - useEffect(() => { - refs.forEach(ref => { - if (!ref) return + const setNativeMask = useCallback( + (newInputMask)=>{ + inputRef.current && setMask(findNodeHandle(inputRef.current), newInputMask); + },[inputRef.current]); - if (typeof ref === 'function') { - ref(targetRef.current) - } else { - ref.current = targetRef.current + const setNativeTextMask = useCallback((value, inputMask)=>{ + inputMask && mask( + inputMask, + "" + value, + text =>{ + inputRef.current && inputRef.current.setNativeProps({ text }); + prevValue.current = ""+ value; } - }) - }, [refs]) - - return targetRef -} - -const TextInputMask = React.forwardRef((props, ref) => { - const inputRef = useRef(null); - const combinedRef = useCombinedRefs(ref, inputRef) - const masked = useRef(false); - const isMounted = useRef(false); + ); + },[inputRef.current, prevValue.current]); - useLayoutEffect(() => { - if (props.maskDefaultValue && props.mask && props.value) - mask( - props.mask, - "" + props.value, - text => inputRef.current && inputRef.current.setNativeProps({ text }) - ); + const onChangeText = useCallback(masked => { + if(masked === prevValue.current) return; + if (props.mask) { + const _unmasked = unmask(props.mask, masked, unmasked => { + props.onChangeText && props.onChangeText(masked, unmasked); + }); + } else { + props.onChangeText && props.onChangeText(masked); + } + },[props.onChangeText,prevValue.current]) + + useImperativeHandle(ref,()=>({ + isFocused : ()=>{ + return inputRef.current && inputRef.current.isFocused() + }, + focus: ()=>{ + return inputRef.current && inputRef.current.focus(); + }, + blur : ()=>{ + return inputRef.current && inputRef.current.blur(); + }, + clear: ()=>{ + return inputRef.current && inputRef.current.clear(); + }, + setNativeProps : ({ mask: inputMask, text, ...nativeProps})=>{ + if( (inputMask || props.mask) && (text || props.value)) setNativeTextMask(text || props.value, inputMask || props.mask); + if(inputMask !== props.mask) setNativeMask(inputMask); + return Object.keys(nativeProps).length && inputRef.current && inputRef.current.setNativeProps(nativeProps) + } + })); + + useEffect(() => { + if (props.maskDefaultValue && props.mask && props.value) setNativeTextMask(props.value, props.mask) if (props.mask && !masked.current) { - inputRef.current && setMask(findNodeHandle(inputRef.current), props.mask); + setNativeMask(props.mask); masked.current = true; } isMounted.current = true; @@ -54,40 +79,30 @@ const TextInputMask = React.forwardRef((props, ref) => { }, []); // Check if value change - useEffect(() => { - isMounted.current && props.mask && mask( - props.mask, - "" + props.value, - text => inputRef.current && inputRef.current.setNativeProps({ text }) - ); - }, [props.value]); - + if(props.value !== prevValue.current){ + isMounted.current && setNativeTextMask(props.value,props.mask); + } //Check if mask change - useEffect(() => { - isMounted.current && inputRef.current && setMask(findNodeHandle(inputRef.current), props.mask); - }, [props.mask]); + if(props.mask !== prevMask.current){ + isMounted.current && setNativeMask(props.mask); + prevMask.current = props.mask; + } return ( { - if (props.mask) { - const _unmasked = unmask(props.mask, masked, unmasked => { - props.onChangeText && props.onChangeText(masked, unmasked); - }); - } else { - props.onChangeText && props.onChangeText(masked); - } - }} + onChangeText={onChangeText} /> ); -}); +} + +TextInputMask = React.forwardRef(TextInputMask); TextInputMask.defaultProps = { maskDefaultValue: true }; -export default React.memo(TextInputMask); +export default React.memo(TextInputMask,(prevProps,nextProps)=>prevProps.mask === nextProps.mask && prevProps.value === nextProps.value); From 2c9438945995d202e33484030d917943677dd613 Mon Sep 17 00:00:00 2001 From: Michael Vargas Date: Sat, 18 Jan 2020 16:01:29 -0500 Subject: [PATCH 8/9] Remove add pod InputMask in podspec in RN0.60+ --- react-native-text-input-mask.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/react-native-text-input-mask.podspec b/react-native-text-input-mask.podspec index f684cf0..d470b1f 100644 --- a/react-native-text-input-mask.podspec +++ b/react-native-text-input-mask.podspec @@ -24,5 +24,5 @@ Pod::Spec.new do |s| s.source_files = "ios/**/*.{h,m}" s.dependency 'React' - s.dependency 'RNInputMask' + s.dependency 'InputMask', '~> 5.0' end From 467788bad7a24cf20f8969430eadf3f1a01a9ac1 Mon Sep 17 00:00:00 2001 From: msvargas Date: Sat, 18 Jan 2020 17:05:31 -0500 Subject: [PATCH 9/9] Update react-native-text-input-mask.podspec --- react-native-text-input-mask.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/react-native-text-input-mask.podspec b/react-native-text-input-mask.podspec index d470b1f..f684cf0 100644 --- a/react-native-text-input-mask.podspec +++ b/react-native-text-input-mask.podspec @@ -24,5 +24,5 @@ Pod::Spec.new do |s| s.source_files = "ios/**/*.{h,m}" s.dependency 'React' - s.dependency 'InputMask', '~> 5.0' + s.dependency 'RNInputMask' end