2

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>
    );
};
Kartikey
  • 4,516
  • 4
  • 15
  • 40

3 Answers3

3

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

Yilmaz
  • 35,338
  • 10
  • 157
  • 202
0

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.

Shoaib Khan
  • 1,020
  • 1
  • 5
  • 18
  • Hi. Thx for answer. I tried it like this now. I added different unique refs for both. I passed props like this: ref={null} onSubmitEditing={() => inputRef2.current?.focus()}. And I got them in the component like : ref={() => props.ref || null} onSubmitEditing={() => props.onSubmitEditing} . But still not working :(( – Oğuzhan Yıldırım Sep 24 '21 at 12:43
  • do it the way I did, you don't need to pass `ref={null}` or any `ref` prop to the component you're focussing on. And simple pass reference like `ref={props.ref || null}` not `ref={() => props.ref || null}` in your `TextInput` component I guess. log your **ref** and **onSubmitEditing** props, make sure you're getting values of both in `GeneralTextInput.tsx` when focussing to next input field. – Shoaib Khan Sep 24 '21 at 14:54
0

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);
        }
      }}
    />
Michael Bahl
  • 2,941
  • 1
  • 13
  • 16