DEV Community

Muhammad Saad Bin Nadeem
Muhammad Saad Bin Nadeem

Posted on

Stop re-writing this: How to build a reusable Password Input in React Native

If you are building a React Native app, you are going to need a Login screen, a Registration screen, and probably a "Change Password" screen.

The biggest mistake I see developers make is re-writing the same useState logic to toggle the secureTextEntry (the eye icon) on every single one of these screens. It makes your code messy and violates the DRY (Don't Repeat Yourself) principle.

Instead, we should build a single, beautiful component that handles its own internal state, and just reuse it everywhere.

Here is exactly how to build it using standard React Native components and Expo vector icons.

  1. The Component Code Create a new file called CustomPasswordInput.tsx in your components folder. We will use @expo/vector-icons for the eye graphic (which comes pre-installed in Expo).
import React, { useState } from 'react';
import { View, TextInput, TouchableOpacity, StyleSheet, TextInputProps } from 'react-native';
import { Ionicons } from '@expo/vector-icons';

// We extend standard TextInputProps so we can pass things like 'placeholder' or 'onChangeText'
interface CustomPasswordInputProps extends TextInputProps {}

export const CustomPasswordInput: React.FC<CustomPasswordInputProps> = ({ ...props }) => {
  // Internal state to track if the password is hidden or visible
  const [isSecure, setIsSecure] = useState(true);

  const toggleVisibility = () => {
    setIsSecure(!isSecure);
  };

  return (
    <View style={styles.container}>
      <TextInput
        style={styles.input}
        secureTextEntry={isSecure} // Toggles the masking
        placeholderTextColor="#999"
        {...props} 
      />

      <TouchableOpacity style={styles.iconContainer} onPress={toggleVisibility}>
        <Ionicons 
          name={isSecure ? "eye-off" : "eye"} 
          size={24} 
          color="#666" 
        />
      </TouchableOpacity>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flexDirection: 'row',
    alignItems: 'center',
    backgroundColor: '#F5F5F5',
    borderRadius: 8,
    borderWidth: 1,
    borderColor: '#E0E0E0',
    paddingHorizontal: 15,
    height: 50,
    marginBottom: 15,
  },
  input: {
    flex: 1, // Takes up remaining space
    fontSize: 16,
    color: '#333',
  },
  iconContainer: {
    padding: 5,
  }
});
Enter fullscreen mode Exit fullscreen mode
  1. Why this approach is better Encapsulation: The Login screen doesn't need to know how the eye icon works. It just needs the final typed text.

Flexibility: Because we spread {...props} onto the TextInput, you can still pass standard props like onChangeText, value, or onBlur from the parent screen without having to manually define them all.

  1. How clean it looks in your Login Screen Now, look at how incredibly clean your actual Login Screen becomes. No messy state variables tracking the eye icon!
import React, { useState } from 'react';
import { View, Button } from 'react-native';
import { CustomPasswordInput } from '../components/CustomPasswordInput';

export default function LoginScreen() {
  const [password, setPassword] = useState('');

  const handleLogin = () => {
    console.log("Logging in with: ", password);
  };

  return (
    <View style={{ padding: 20 }}>
      {/* Our clean, reusable component! */}
      <CustomPasswordInput 
        placeholder="Enter your password" 
        value={password}
        onChangeText={setPassword}
      />

      <Button title="Login" onPress={handleLogin} />
    </View>
  );
}
Enter fullscreen mode Exit fullscreen mode

By extracting UI elements into smart components, you can assemble complex screens in seconds instead of hours.

Did you find this helpful? I build full-stack architectures and post weekly about React Native, .NET Core, and writing cleaner code. Make sure to follow my profile so you don't miss the next UI component breakdown!

reactnative, #javascript, #webdev, #ui

Top comments (0)