Oğuzhan Yıldırım
Oğuzhan Yıldırım

Reputation: 166

React Native Text Input focus with useRef using Typescript

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>
    );
};

Upvotes: 2

Views: 8563

Answers (3)

Yilmaz
Yilmaz

Reputation: 49180

You wrap GeneralTextInput with forwardRef:

import { TextInput, TextInputProps } from "react-native";


export const GeneralTextInput: React.forwardRef<TextInput,IGeneralTextInputProps> = (
  // type of props and ref will be inferred by ts
  props
  ref
) => {
     .... 
     return (
     ....
     <TextInput
        ref={ref}
        {...props}
     ...
     ...

    />
    )}

Now in the parent component Define one useRef:

const secondInputRef = useRef<TextInput | null>(null);

you have 2 generalInput. on first input

<GeneralTextInput
    ....
    ....
    // add this. this will focus on secondInput
    onSubmitEditing={() => {
                             secondInputRef.current?.focus();
                           }}
  />

second GeneralInput will be as it is

Upvotes: 3

Shoaib Khan
Shoaib Khan

Reputation: 1210

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.

Upvotes: 0

Michael Bahl
Michael Bahl

Reputation: 3649

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);
        }
      }}
    />

Upvotes: 0

Related Questions