skip to Main Content

I’m working on an OTP input screen in React Native where I need to show a placeholder () inside the input fields under the following conditions:

  1. When a box is focused, the placeholder should not be visible.
  2. After clearing the value in any box, the placeholder () should appear only if the box is empty, even if the box is not focused.

Current Code:

Here’s the code I’m using:

<View style={styles.otpContainer}>
  {[...new Array(4)].map((_, index) => (
    <TextInput
      ref={ref => {
        inputs.current[index] = ref;
      }}
      key={index}
      style={[
        styles.otpInput,
        focusedIndex === index && { borderColor: tertiary },
        invalidOtp && { borderColor: error },
      ]}
      keyboardType="number-pad"
      maxLength={1}
      selectTextOnFocus
      contextMenuHidden
      testID={`OTPInput-${index}`}
      onChangeText={text => handleChangeText(text, index)}
      onKeyPress={e => handleKeyPress(e, index)}
      value={otpValues[index]}
      placeholder={otpValues[index] === '' && focusedIndex !== index ? '•' : ''}
      placeholderTextColor={secondary}
      onFocus={() => setFocusedIndex(index)} 
      onBlur={() => setFocusedIndex(null)} 
    />
  ))}
</View>

Problem:

  • Condition 1: When the input box is focused, the placeholder should not be visible, which is working fine.
  • Condition 2: After clearing the value in any box, the placeholder () should appear in that box only if the box is empty, even if the box is not focused.
    Currently, this is not working. When I clear a box and move focus to another box, the placeholder doesn’t reappear for the empty box.

Expected Behavior:

  • When the input box is focused, the placeholder should not be visible.
  • After clearing a digit, if the input box is empty (even if not focused), the placeholder () should appear.

2

Answers


  1. I think the problem was with the check on placeholder you should be checking for focused index only, because once the input has some value it automatically disables the placeholder.

    I simplified your code a little for me to solve the problem, now it is working, just tweak the placeholder check as below and the code should work.

    const [focusedIndex, setFocusedIndex] = React.useState(-1);
    
    <View>
      {[...new Array(4)].map((_, index) => (
        <TextInput
          key={index}
          keyboardType="number-pad"
          maxLength={1}
          selectTextOnFocus
          contextMenuHidden
          testID={`OTPInput-${index}`}
          placeholder={focusedIndex !== index ? '•' : ''}
          onFocus={() => setFocusedIndex(index)} 
          onBlur={() => setFocusedIndex(-1)} 
        />
      ))}
    </View>
    
    Login or Signup to reply.
  2. Placeholder is now simply based on whether the otpValues[index] is empty (otpValues[index] === ''), and no longer depends on the focus state.
    If the input is empty, the placeholder • is displayed, even when the field is not focused.
    Replaced the condition to show the placeholder as otpValues[index] === '' ? '•' : '', ensuring the placeholder appears when the field is empty.

    import React, {useState, useRef} from 'react';
    import {View, TextInput, StyleSheet} from 'react-native';
    
    const OTPInput = () => {
      const [otpValues, setOtpValues] = useState(['', '', '', '']);
      const [focusedIndex, setFocusedIndex] = useState(null);
      const [invalidOtp, setInvalidOtp] = useState(false);
      const inputs = useRef([]);
    
      const tertiary = '#007BFF';
      const error = '#FF0000';
      const secondary = '#A9A9A9';
    
      const handleChangeText = (text, index) => {
        const updatedOtpValues = [...otpValues];
        updatedOtpValues[index] = text;
        setOtpValues(updatedOtpValues);
    
        if (text && index < inputs.current.length - 1) {
          inputs.current[index + 1]?.focus();
        }
      };
    
      const handleKeyPress = (e, index) => {
        if (e.nativeEvent.key === 'Backspace' && !otpValues[index] && index > 0) {
          inputs.current[index - 1]?.focus();
        }
      };
    
      return (
        <View style={styles.otpContainer}>
          {[...new Array(4)].map((_, index) => (
            <TextInput
              ref={ref => {
                inputs.current[index] = ref;
              }}
              key={index}
              style={[
                styles.otpInput,
                focusedIndex === index && {borderColor: tertiary},
                invalidOtp && {borderColor: error},
              ]}
              keyboardType="number-pad"
              maxLength={1}
              selectTextOnFocus
              contextMenuHidden
              testID={`OTPInput-${index}`}
              onChangeText={text => handleChangeText(text, index)}
              onKeyPress={e => handleKeyPress(e, index)}
              value={otpValues[index]}
              placeholder={otpValues[index] === '' ? '•' : ''}
              placeholderTextColor={secondary}
              onFocus={() => setFocusedIndex(index)}
              onBlur={() => setFocusedIndex(null)}
            />
          ))}
        </View>
      );
    };
    
    const styles = StyleSheet.create({
      otpContainer: {
        flexDirection: 'row',
        justifyContent: 'space-between',
        paddingHorizontal: 20,
        marginTop: 50,
      },
      otpInput: {
        width: 50,
        height: 50,
        borderWidth: 1,
        borderColor: '#CCCCCC',
        borderRadius: 5,
        textAlign: 'center',
        fontSize: 18,
        color: '#000',
      },
    });
    
    export default OTPInput;
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search