Quantcast
Channel: Active questions tagged react-native+ios - Stack Overflow
Viewing all articles
Browse latest Browse all 17175

Typing on TextInput is executes two functions instead just one of this on iOS devices | React Native Expo

$
0
0

I am currently working on a unit conversion calculator in React Native + Expo Go. Handle the value of each TextInput to convert hectares to m/2. When typing in one TextInput the result will be set in the other TextInput and do the calculation vice versa. This works correctly on Android devices but not on iOS devices.


The problem is when I typing on iOS devices the caculation execute 2 times, changing input's values on both TextInputs instead of one of this.

  • package.json
"expo": "~50.0.8","react": "18.2.0","react-native": "0.73.4","react-hook-form": "^7.49.2","typescript": "^5.1.3"
  • Parent component:
const unitConverter = () => {  const [convertionSelected, setConvertionSelected] = useState("area");  const [listOfUnits, setListOfUnits] = useState(converters.area);  const [showUnitsModal, setShowUnitsModal] = useState(false);  const [unitNameFrom, setUnitNameFrom]: useStateProp<null | string> =    useState(null);  const [unitNameTo, setUnitNameTo]: useStateProp<null | string> =    useState(null);  const [selectClicked, setSelectClicked]: useStateProp<null | string> =    useState(null);  const [unitSelectedTop, setUnitSelectedTop]: useStateProp<null | item> =    useState(null);  const [unitSelectedBottom, setUnitSelectedBottom]: useStateProp<null | item> =    useState(null);  const [valueTop, setValueTop] = useState("0")  const [valueBottom, setValueBottom] = useState("0")  const [isScrollEnabled, setIsScrollEnabled] = useState(false);  const scrollViewRef = useRef(null)  const {    control,    setError,    formState: { errors },    clearErrors,    reset,  } = useForm({    defaultValues: {      inputTop: "",      inputBottom: ""    },  });  /**   * Setting type of converter   *   * @param converter unit converter, area, peso, volumen   */  const handleConverterType = (converter: string) => {    setConvertionSelected(converter);    if (converter === "area") setListOfUnits(converters.area);    if (converter === "peso") setListOfUnits(converters.peso);    if (converter === "volumen") setListOfUnits(converters.volumen);    if (unitNameFrom) setUnitNameFrom(null);    if (unitNameTo) setUnitNameTo(null);    if (unitSelectedTop) setUnitSelectedTop(null);    if (unitSelectedBottom) setUnitSelectedBottom(null);    if (valueTop.length || valueBottom.length) {      setValueTop("0");      setValueBottom("0");    }  };  /**   * Handle units select modal visibility   *   * @param show handle select modal visibility   * @param select handle if clicked on top or bottom buttons selects   */  const handleShowUnitsModal = (show: boolean, select: string = "") => {    if (platform === "ios" && Keyboard.isVisible()) Keyboard.dismiss();    setShowUnitsModal(show);    setSelectClicked(select);  };  // handle unit selected  const handleUnitSelected = (unit: item) => {    if (selectClicked === "top") {      if (showUnitsModal) handleShowUnitsModal(false);      setUnitSelectedTop(unit);      setUnitNameFrom(unit.option);    }    if (selectClicked === "bottom") {      if (showUnitsModal) handleShowUnitsModal(false);      setUnitSelectedBottom(unit);      setUnitNameTo(unit.option);    }    if (JSON.stringify(errors) != "{}") {      clearErrors("inputTop");      clearErrors("inputBottom");    }  };  // Handling if input value is a number  const handleIsNotNum = (input: string) => {    if (input === "top") {      setError("inputTop", {        type: "required",        message: "Ingrese un valor numérico válido",      });    } else {      setError("inputBottom", {        type: "required",        message: "Ingrese un valor numérico válido",      });    }  };  // Handling useFrom's clearErrors for inputs  const handleClearErrors = (input: clearErrorsProp) => {    if (errors[input]?.message) clearErrors(input);  }  /**   * Handle input's value format it a thoundsands, and/or centecimals   *    * @param num {string} input value   * @returns formated input value as latam coin   */  const formatNumber = (num: string) => {    if (num === ",") return "0,"    if (num.length === 2) {      if (num[0] === "0" && ONLY_NUMBER_REGEX.test(num[1])) num = num[1]    }    num = num.replaceAll(".", "");    if (num.includes(",")) {      const [integers, decimals] = num.split(",")      const newDecimals = decimals.length >= 4 ? decimals.slice(0, 4) : decimals      if (newDecimals.length === 4 && newDecimals.split("").every(num => num === "0")) return integers      return integers.replace(FORMAT_NUM_REGEX, ".").concat(",").concat(newDecimals)    } else {      return num.replace(FORMAT_NUM_REGEX, ".")    }  }  /**   * Handle convertion result value format it a thoundsands, and/or centecimals   *    * @param num {string} convertion result value   * @returns convertion result value as latam coin   */  const formatConvertionResult = (num: string) => {    if (num.includes(".")) {      const [integers, decimals] = num.split(".")      const formatIntegers = integers.replace(FORMAT_NUM_REGEX, ".")      // greater than or equals to 4 decimals      if (decimals.length >= 4) {        const splitDecimals = decimals.slice(0, 4)        const areZeros = splitDecimals.split("").every(num => num === "0")        if (areZeros) return formatIntegers        return `${formatIntegers},${splitDecimals}`;      }      // less than to 4 decimals      const splitDecimals = decimals.slice(0, 3)      const areZeros = splitDecimals.split("").every(num => num === "0")      if (areZeros) return formatIntegers      return `${formatIntegers},${splitDecimals}`    }    return num.replace(FORMAT_NUM_REGEX, ".")  }   /**   * Calculate values to get convertion by units settled   *    * @param num    */  const settingValueToTop = (num: string = "") => {    num = num.length ? num : valueBottom;    num = num.replaceAll(".", "");    num = num.includes(",") ? num.replace(",", ".") : num;    const unitCurrent = units[convertionSelected][unitSelectedBottom.name][unitSelectedTop.name];    const total = parseFloat(num) * unitCurrent;    const formatTotal = formatConvertionResult(total.toString())    setValueTop(formatTotal);  }  /**   * Calculate values to get convertion by units settled   *    * @param num    */  const settingValueToBottom = (num: string = "") => {    num = num.length ? num : valueTop;    num = num.replaceAll(".", "");    num = num.includes(",") ? num.replace(",", ".") : num;    const unitCurrent = units[convertionSelected][unitSelectedTop.name][unitSelectedBottom.name];    const total = parseFloat(num) * unitCurrent;    const formatTotal = formatConvertionResult(total.toString())    setValueBottom(formatTotal);  }  // Input Top handler  const onChangeInputTop = (num: string) => {    console.log("top")    console.log(num)    if (num == "") {      if (valueBottom !== "0") setValueBottom("0")      handleClearErrors("inputTop")      return setValueTop("0")    }    if (num === ".") return handleIsNotNum("bottom");    if (!NUMBER_REGEX.test(num)) return handleIsNotNum("top");    handleClearErrors("inputTop")    const value = formatNumber(num);    setValueTop(value);    if (unitSelectedBottom != null && unitSelectedTop != null) {      settingValueToBottom(value);    }  }  // Input Bottom handler  const onChangeInputBottom = (num: string) => {    console.log("bottom")    console.log(num)    if (num == "") {      if (valueTop !== "0") setValueTop("0");      handleClearErrors("inputBottom")      return setValueBottom("0");    }    if (num === ".") return handleIsNotNum("bottom");    if (!NUMBER_REGEX.test(num)) return handleIsNotNum("bottom");    handleClearErrors("inputBottom")    const value = formatNumber(num);    setValueBottom(value);    if (unitSelectedBottom != null && unitSelectedTop != null) {      settingValueToTop(value);    }  }  /**   * Handle scrollView's scroll to enable it   */  const onPressInInput = () => {    if (!isScrollEnabled) setIsScrollEnabled(true)  }  /**   * Handle scrollView's scroll to disable it   * when keyboard did hide, view will scroll till top in iOS devices   */  Keyboard.addListener("keyboardDidHide", () => {    setIsScrollEnabled(false)    if (platform === "ios") {      scrollViewRef.current?.scrollTo({        y: 0,        animated: false      })    }  });  /**   * Handle inputs values when unit top or bottom were changes   * and setting value unit convertion to top or bottom   */  useEffect(() => {    if (unitSelectedTop != null && unitSelectedBottom != null) {      if (valueBottom !== "0" && valueTop !== "0") return settingValueToBottom();      if (valueBottom !== "0" && valueTop === "0") return settingValueToTop();      if (valueBottom === "0" && valueTop !== "0") return settingValueToBottom();    }  }, [unitSelectedTop]);  useEffect(() => {    if (unitSelectedTop != null && unitSelectedBottom != null) {      if (valueBottom !== "0" && valueTop !== "0") return settingValueToTop();      if (valueBottom !== "0" && valueTop === "0") return settingValueToTop();      if (valueBottom === "0" && valueTop !== "0") return settingValueToBottom();    }  }, [unitSelectedBottom]);  return (<SafeAreaView      style={styles.container}><ScrollView        ref={scrollViewRef}        keyboardShouldPersistTaps="handled"        overScrollMode="never"        scrollEnabled={isScrollEnabled}        bounces={false}        automaticallyAdjustKeyboardInsets={true}        // onTouchEnd={onPressOutInput}        style={styles.scrollview}><View style={styles.containerBody}><View style={styles.bodyTop}><HeaderTools /><ToolsNavbar              convertionSelected={convertionSelected}              handleConverter={handleConverterType}            /><InputTopConverterUnit              control={control}              unitNameFrom={unitNameFrom}              valueTop={valueTop}              handleShowUnitsModal={handleShowUnitsModal}              onChangeInputTop={onChangeInputTop}              onPressInInput={onPressInInput}            /><IconConverterUnit /></View><View style={[styles.bodyBottom, { marginTop: platform === "ios" ? "5%" : "10%" }]}><InputBottomConverterUnit              control={control}              unitNameTo={unitNameTo}              valueBottom={valueBottom}              handleShowUnitsModal={handleShowUnitsModal}              onChangeInputBottom={onChangeInputBottom}              onPressInInput={onPressInInput}            /></View></View></ScrollView><ModalUnits        visible={showUnitsModal}        listOfUnits={listOfUnits}        unitSelectedTop={unitSelectedTop}        unitSelectedBottom={unitSelectedBottom}        handleShowUnitsModal={handleShowUnitsModal}        handleUnitSelected={handleUnitSelected}      /></SafeAreaView>  );};
  • Inputs
type propsTop = {  control: control;  unitNameFrom: null | string;  valueTop: string;  handleShowUnitsModal: (show: boolean, select: string) => void;  onChangeInputTop: (e: any) => void;  onPressInInput: () => void;};export const InputTopConverterUnit: FC<propsTop> = ({  control,  valueTop,  unitNameFrom,  handleShowUnitsModal,  onChangeInputTop,  onPressInInput,}) => {  return (<Controller      name={"inputTop"}      control={control}      rules={{        required: "Ingrese un valor",      }}      render={({ fieldState: { error } }) => {        return (<View style={[styles.containerInput, { marginTop: "3%" }]}><UnitsSelect              clickOn="top"              onPress={handleShowUnitsModal}              unitName={unitNameFrom}            /><TextInput              style={[                styles.inputValue,                {                  paddingHorizontal: valueTop.length ? "10%" : "40%",                },              ]}              keyboardType={"numeric"}              cursorColor={"#123021"}              onChangeText={onChangeInputTop}              value={valueTop}              multiline={true}              onTouchStart={onPressInInput}            />            {error?.message && <Text>{error?.message}</Text>}</View>        );      }}    />  );};type propsBottom = {  control: control;  unitNameTo: null | string;  valueBottom: string;  handleShowUnitsModal: (show: boolean, select: string) => void;  onChangeInputBottom: (num: string) => void;  onPressInInput: () => void;};export const InputBottomConverterUnit: FC<propsBottom> = ({  control,  valueBottom,  unitNameTo,  handleShowUnitsModal,  onChangeInputBottom,  onPressInInput,}) => {  return (<Controller      name={"inputBottom"}      control={control}      rules={{        required: "Ingrese un valor",      }}      defaultValue={0}      render={({ fieldState: { error } }) => {        return (<View style={[styles.containerInput, { height: "70%" }]}><UnitsSelect              clickOn="bottom"              onPress={handleShowUnitsModal}              unitName={unitNameTo}            /><TextInput              style={[                styles.inputValue,                {                  paddingHorizontal: valueBottom.length ? "10%" : "40%",                },              ]}              keyboardType={"numeric"}              cursorColor={"#123021"}              onChangeText={onChangeInputBottom}              onTouchStart={onPressInInput}              value={valueBottom}              multiline={true}            />            {error?.message && <Text>{error?.message}</Text>}</View>        );      }}    />  );};
  • Modal units for select
type item = {  id: number;  name: string;  option: string;};type props = {  visible: boolean;  listOfUnits: item[];  unitSelectedTop: null | item;  unitSelectedBottom: null | item;  handleShowUnitsModal: (prop: boolean) => void;  handleUnitSelected: (prop: item) => void};export const ModalUnits: FC<props> = ({  visible,  listOfUnits,  unitSelectedTop,  unitSelectedBottom,  handleShowUnitsModal,  handleUnitSelected,}) => {  const [newListOfUnits, setNewListOfUnits]: useStateProp<item[]> = useState(listOfUnits);  const handleUnitsFromNewList = () => {    if (unitSelectedBottom != null && unitSelectedTop != null) {      const newList = listOfUnits.filter(unit =>         unit.name != unitSelectedBottom.name &&        unit.name != unitSelectedTop.name      );      return setNewListOfUnits(newList);    }  }  // Filter list of units by unit selected  useEffect(() => {    if (unitSelectedTop != null && unitSelectedBottom == null) {      const newList = listOfUnits.filter(unit => unit.name != unitSelectedTop.name);      return setNewListOfUnits(newList);    } else {      handleUnitsFromNewList();    }  }, [unitSelectedTop]);  // Filter list of units by unit selected  useEffect(() => {    if (unitSelectedBottom != null && unitSelectedTop == null) {      const newList = listOfUnits.filter(unit => unit.name != unitSelectedBottom.name);      return setNewListOfUnits(newList);    } else {      handleUnitsFromNewList();    }  }, [unitSelectedBottom])  // Setting new list of units convertion  useEffect(() => {    setNewListOfUnits(listOfUnits)  }, [listOfUnits])  return (<Modal animationType="fade" transparent={true} visible={visible}><TouchableWithoutFeedback onPress={() => handleShowUnitsModal(false)}><View style={styles.container}><View style={styles.containerModal}><ScrollView overScrollMode="never">              {newListOfUnits.map((item, index) => {                return (<TouchableOpacity                    key={item.name}                    activeOpacity={0.5}                    style={[                      styles.btnUnit,                      {                        borderBottomWidth:                          newListOfUnits.length !== index + 1 ? 1.5 : 0,                        borderBottomColor:                          newListOfUnits.length !== index + 1                            ? "#123021"                            : "none",                      },                    ]}                    onPress={() => {                      handleUnitSelected(item)                    }}><Text style={styles.txtUnit}>{item.option}</Text></TouchableOpacity>                )              }              )}</ScrollView></View></View></TouchableWithoutFeedback></Modal>  );};
  • constants
type unitProps = {  area: {}  peso: {};  volumen: {};};export const units: unitProps = {  area: {    ha: {      m2: 10_000,      mz: 1.419,      tarea: 15.9,    },    m2: {      ha: 0.000_1,      mz: 0.000_141_964,      tarea: 0.001_590_330,    },    mz: {      ha: 0.7,      m2: 7_000,      tarea: 11.202_290,    },    tarea: {      m2: 628.8,      ha: 0.062_88,      mz: 0.089_267,    },  },  peso: {    kg: {      ton: 0.001,      onzas: 35.274,      libras: 2.204_62,      tonLargas: 0.000_984_2,      tonCortas: 0.001_102,    },    ton: {      kg: 1_000,      onzas: 35274.94,      libras: 2204.62,      tonLargas: 0.984_206,      tonCortas: 1.102_3,    },    onzas: {      kg: 0.028_35,      ton: 0.000_028_349_5,      libras: 0.062_5,      tonLargas: 0.000_027_901_8,      tonCortas: 0.000_031_25,    },    libras: {      kg: 0.453_592,      ton: 0.000_453_59,      onzas: 16,      tonLargas: 0.000_446_43,      tonCortas: 0.000_5,    },    tonLargas: {      kg: 1_016.05,      onzas: 35_839.98,      libras: 2_240,      ton: 1.016,      tonCortas: 1.12,    },    tonCortas: {      kg: 907.18,      onzas: 31_999.99,      libras: 2_000,      ton: 0.907_185,      tonLargas: 0.892_857,    },    quintales: {      kg: 1000,      onzas: 3_527.396_2,      libras: 220.462,      ton: 0.1,      tonLargas: 0.098_420_7,      tonCortas: 0.110_231,    },  },  volumen: {    gal: { // EEUU - líquido      lts: 3.785,      ml: 3785.411_81,    },    lts: {      gal: 0.264_172,      ml: 1_000,    },    ml: {      gal: 0.000_264_17,      lts: 0.001,    },  },};type option = {  id: number;  name: string;  option: string;};type convertersProps = {  area: option[];  peso: option[];  volumen: option[];};export const converters: convertersProps = {  area: [    {      id: 0,      name: "ha",      option: "Hectareas (ha)",    },    {      id: 1,      name: "m2",      option: "Metros cuadrados (m2)",    },    {      id: 2,      name: "mz",      option: "Manzanas",    },    {      id: 3,      name: "tarea",      option: "Tareas",    },  ],  peso: [    {      id: 0,      name: "kg",      option: "Kilogramos (kg)",    },    {      id: 1,      name: "ton",      option: "Toneladas (t)",    },    {      id: 2,      name: "onzas",      option: "Onzas (oz)",    },    {      id: 3,      name: "tonLargas",      option: "Toneladas Largas (lo tn)",    },    {      id: 4,      name: "tonCortas",      option: "Toneladas Cortas (sh tn)",    },    {      id: 5,      name: "quintales",      option: "Quintales (qm)",    },  ],  volumen: [    {      id: 0,      name: "gal",      option: "Galon (gal)",    },    {      id: 1,      name: "lts",      option: "Litros (lts)",    },    {      id: 2,      name: "ml",      option: "Mililitros (ml)",    },  ],};
  • Button for select unit to convert
type props = {  clickOn: string;  onPress: (open: boolean, clickOn: string) => void;  unitName: null | string;};export const UnitsSelect: FC<props> = ({ clickOn, onPress, unitName }) => {  return (<TouchableOpacity      activeOpacity={0.7}      style={styles.select}      onPress={() => onPress(true, clickOn)}><Text style={styles.txtUnit}>{unitName ?? "Seleccione unidad"}</Text><CaretDown        size={30}        color="#123021"        style={styles.iconCartetCircleDown}      /></TouchableOpacity>  );};

The function is simple, type a number in a TextInput to see the result in the other TextInput when the units to convert were selected.


Viewing all articles
Browse latest Browse all 17175

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>