Home > Back-end >  React Native Text Input focus with useRef using Typescript
React Native Text Input focus with useRef using Typescript

Time:09-24

I have an issue with focusing the next input in React Native. I use just one input called GeneralTextInput.tsx in the whole app.

In this example I have 2 inputs ==> 1.Group Name, 2.Group Description

So I give some props in the parent to this component:

<View style={classes.formContainer}>
  <Text style={classes.label}>{t("group.name-your-group")}</Text>

  <GeneralTextInput
    width={"100%"}
    returnKeyType={"next"}
    isDoneReference={false}
    deleteIcon
    startIcon={"account-multiple"}
    bordered={true}
    placeholder={t("form.placeholders.groupName")}
    value={props.newGroupName}
    onChange={(val: string) => {
      props.setNewGroupName(val);
      if (val.length > 25) {
        props.setNewGroupNameError(t("form.validations.max-25-char"));
      }
      if (val.length <= 25) {
        props.setNewGroupNameError(undefined);
      }
    }}
  />

  <Text style={classes.label}>{t("group.describe-your-group")}</Text>

  <GeneralTextInput
    width={"100%"}
    returnKeyType={"done"}
    isDoneReference={true}
    isDismissed={true}
    startIcon={"text"}
    bordered={true}
    isMultiLine={true}
    numberOfLines={3}
    placeholder={t("form.placeholders.groupDescription")}
    value={props.newGroupDescription}
    onChange={(val: string) => {
      props.setNewGroupDescription(val);
      if (val.length > 30) {
        props.setNewGroupDescriptionError(t("form.validations.max-30-char"));
      }
      if (val.length < 30) {
        props.setNewGroupDescriptionError(undefined);
      }
    }}
  />
</View>

And this is my GeneralTextInput.tsx What should I give to the input as a ref and how should I focus on it?

import * as React from "react";
import {
    NativeSyntheticEvent,
    Platform,
    StyleProp,
    TextInputFocusEventData,
    TextStyle,
    View,
    ViewStyle,
    TextInput,
    ImageStyle,
    Pressable,
} from "react-native";
import { makeStyles, IStyledComponent } from "../../assets/theme/installation";
import { IconButton, Text, useTheme } from "react-native-paper";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import FontAwesome5Icon from "react-native-vector-icons/FontAwesome5";
import { theme } from "../../assets/theme/DefaultTheme";
import { TouchableWithoutFeedback } from "react-native-gesture-handler";

export interface IGeneralTextInputProps
    extends IStyledComponent<GeneralTextInputStyles> {
    readonly value: string | undefined;
    readonly placeholder?: string;
    readonly onChange: (newValue: string) => void;
    readonly onBlur?: (e: NativeSyntheticEvent<TextInputFocusEventData>) => void;
    readonly isPassword?: boolean;
    readonly autoCapitalize?: boolean;
    readonly error?: string;
    readonly startIcon?: string;
    readonly startIconFA5?: string;
    readonly endIcon?: string;
    readonly deleteIcon?: boolean;
    readonly disabled?: boolean;
    readonly disabledInputText?: boolean;
    readonly bordered?: boolean;
    readonly isMultiLine?: boolean;
    readonly width?: number | string;
    readonly numberOfLines?: number;
    readonly keyboardType?: string;
    readonly isGratitude?: boolean;
    readonly autoCorrect?: boolean;
    readonly selectedMeasureUnit?: string;
    readonly returnKeyType?: string;
    readonly isDoneReference?: boolean;
    readonly isDismissed?: boolean;
}

export const GeneralTextInput: React.FC<IGeneralTextInputProps> = (
    props: IGeneralTextInputProps,
) => {
    const classes = useStyles(props);
    const { fonts, colors } = useTheme();
    const [isPressed, setIsPressed] = React.useState(false);
    const [isPasswordVisible, setPasswordVisible] = React.useState(false);

    const groupNameRef = React.useRef<HTMLInputElement>(null);
    const groupDescRef = React.useRef<HTMLInputElement>(null);

    return (
    <View style={classes.container}>
        <TouchableWithoutFeedback>
        <View style={classes.root}>
            <TextInput
            ref={() => (props.isDoneReference ? groupDescRef : groupNameRef)}
            onSubmitEditing={() => {
                groupDescRef.current?.focus();
            }}
            blurOnSubmit={props.isDoneReference ? true : false}
            keyboardType={
                props.keyboardType === "numpad" ? "numeric" : "default"
            }
            autoCorrect={props.autoCorrect}
            multiline={props.isMultiLine}
            numberOfLines={props.numberOfLines}
            maxLength={props.isGratitude ? 300 : 50}
            editable={!props.disabled}
            onBlur={props.onBlur}
            autoCapitalize={
                props.autoCapitalize != undefined ? "words" : "none"
            }
            secureTextEntry={
                props.isPassword == undefined ? false : !isPasswordVisible
            }
            style={
                props.disabledInputText
                ? classes.disabledTextInput
                : classes.textInput
            }
            value={props.value}
            placeholder={props.placeholder}
            placeholderTextColor={fonts.text.small.color}
            onTouchEnd={() => setIsPressed(true)}
            onChangeText={(value) => props.onChange(value)}
            returnKeyType={
                props.returnKeyType === "next"
                ? "next"
                : props.returnKeyType === "done"
                ? "done"
                : "default"
            }
            />
        </View>
        </TouchableWithoutFeedback>
    </View>
    );
};

CodePudding user response:

If I understand correctly forwardRef ist what you are looking for.

import * as React from "react";
import {
  NativeSyntheticEvent,
  Platform,
  StyleProp,
  TextInputFocusEventData,
  TextStyle,
  View,
  ViewStyle,
  TextInput,
  ImageStyle,
  Pressable,
} from "react-native";
import { makeStyles, IStyledComponent } from "../../assets/theme/installation";
import { IconButton, Text, useTheme } from "react-native-paper";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import FontAwesome5Icon from "react-native-vector-icons/FontAwesome5";
import { theme } from "../../assets/theme/DefaultTheme";
import { TouchableWithoutFeedback } from "react-native-gesture-handler";

export interface IGeneralTextInputProps
  extends IStyledComponent<GeneralTextInputStyles> {
  readonly value: string | undefined;
  readonly placeholder?: string;
  readonly onChange: (newValue: string) => void;
  readonly onBlur?: (e: NativeSyntheticEvent<TextInputFocusEventData>) => void;
  readonly isPassword?: boolean;
  readonly autoCapitalize?: boolean;
  readonly error?: string;
  readonly startIcon?: string;
  readonly startIconFA5?: string;
  readonly endIcon?: string;
  readonly deleteIcon?: boolean;
  readonly disabled?: boolean;
  readonly disabledInputText?: boolean;
  readonly bordered?: boolean;
  readonly isMultiLine?: boolean;
  readonly width?: number | string;
  readonly numberOfLines?: number;
  readonly keyboardType?: string;
  readonly isGratitude?: boolean;
  readonly autoCorrect?: boolean;
  readonly selectedMeasureUnit?: string;
  readonly returnKeyType?: string;
  readonly isDoneReference?: boolean;
  readonly isDismissed?: boolean;
}

export const GeneralTextInput: React.forwardRef<IGeneralTextInputProps> = (
  props: IGeneralTextInputProps,
  ref: any
) => {
  const classes = useStyles(props);
  const { fonts, colors } = useTheme();
  const [isPressed, setIsPressed] = React.useState(false);
  const [isPasswordVisible, setPasswordVisible] = React.useState(false);

  const groupNameRef = React.useRef<HTMLInputElement>(null);
  const groupDescRef = React.useRef<HTMLInputElement>(null);

  return (
    <View style={classes.container}>
      <TouchableWithoutFeedback>
        <View style={classes.root}>
          <TextInput
            ref={() => (props.isDoneReference ? groupDescRef : groupNameRef)}
            onSubmitEditing={() => {
              groupDescRef.current?.focus();
            }}
            blurOnSubmit={props.isDoneReference ? true : false}
            keyboardType={
              props.keyboardType === "numpad" ? "numeric" : "default"
            }
            autoCorrect={props.autoCorrect}
            multiline={props.isMultiLine}
            numberOfLines={props.numberOfLines}
            maxLength={props.isGratitude ? 300 : 50}
            editable={!props.disabled}
            onBlur={props.onBlur}
            autoCapitalize={
              props.autoCapitalize != undefined ? "words" : "none"
            }
            secureTextEntry={
              props.isPassword == undefined ? false : !isPasswordVisible
            }
            style={
              props.disabledInputText
                ? classes.disabledTextInput
                : classes.textInput
            }
            value={props.value}
            placeholder={props.placeholder}
            placeholderTextColor={fonts.text.small.color}
            onTouchEnd={() => setIsPressed(true)}
            onChangeText={(value) => props.onChange(value)}
            returnKeyType={
              props.returnKeyType === "next"
                ? "next"
                : props.returnKeyType === "done"
                ? "done"
                : "default"
            }
          />
        </View>
      </TouchableWithoutFeedback>
    </View>
  );
};));

const ref = React.createRef();
    <GeneralTextInput
      ref={ref} 
      width={"100%"}
      returnKeyType={"next"}
      isDoneReference={false}
      deleteIcon
      startIcon={"account-multiple"}
      bordered={true}
      placeholder={t("form.placeholders.groupName")}
      value={props.newGroupName}
      onChange={(val: string) => {
        props.setNewGroupName(val);
        if (val.length > 25) {
          props.setNewGroupNameError(t("form.validations.max-25-char"));
        }
        if (val.length <= 25) {
          props.setNewGroupNameError(undefined);
        }
      }}
    />

CodePudding user response:

Try not to handle your reference in GeneralTextInput.tsx, handle reference for both differently in your main file and pass onSubmitEditing props from the main file too.

To your second GeneralTextInput create a inputRef and focus that on your first GeneralTextInput component

<GeneralTextInput
    onSubmitEditing = {() => inputRef.current.focus()}
    ...
>


<GeneralTextInput
    ref = { inputRef }
    ...
>

Then simply pass them as a prop to your GeneralTextInput.tsx file. Hope this works for you

<TextInput
    ref={props.ref || null}
    onSubmitEditing={props.onSubmitEditing || null}
    ...
>

Hope this works for you.

  • Related