callstack / react-native-paper

Material Design for React Native (Android & iOS)
https://reactnativepaper.com
MIT License
12.8k stars 2.08k forks source link

TextInput initial wrong padding/position for text content #4512

Open rossparachute opened 1 day ago

rossparachute commented 1 day ago

Hey all,

react-native-paper is great, appreciate all the hard work.

This may not strictly be a bug as I can understand certain layout calculations maybe don't make sense inside a container with display: none.

Regardless, I've found a workaround that might save others some time.

Current behaviour

On initial render the TextInput contents lack the expected padding and positioning.

It affects both Android and iOS.

Expected behaviour

That there is a slight padding on the left/right and the text contents are vertically centre-aligned inside the TextInput.

How to reproduce?

The most reliable way to reproduce the "bug" is when the TextInput is placed inside a that initially has a display style prop set to "none" and then to "flex". The value prop of the TextInput is pre-populated with the value from a useState variable.

However, we have intermittently seen it occur when the inputs are just in a View or ScrollView with no special display prop styles. EDIT: Generally when this has occurred it's when the TextInput components aren't immediately visible on render, i.e - out of visible content of a scroll view but not always.

Changing the contents of the TextInput seems to get the expected padding/text positioning to kick in.

Educated guess is when in a container with display: none the UI layout code can't calculate the required padding etc.

The following snack reproduces the bug: https://snack.expo.dev/@rossparachute/surprised-red-macaroni-and-cheese

Alternatively, the following code should also do the trick in the event the Snack goes down.

import { useState } from "react"
import { View, Text, TouchableOpacity, SafeAreaView, StyleSheet } from 'react-native'
import { TextInput } from "react-native-paper"

export default function App() {
  const [name, setName] = useState(`Foo`)
  const [isActive, setIsActive] = useState(false)

  const handlePressButton = (e) => {
    setIsActive(!isActive)
  }

  const handleChangeName = (newValue: string) => {
    setName(newValue)
  }

  return (
    <SafeAreaView style={styles.container}>
      <TouchableOpacity style={styles.buttonStyles} onPress={handlePressButton}>
        <Text style={styles.buttonText}>Show Input</Text>
      </TouchableOpacity>

      <View style={{
        display: isActive ? "flex" : "none", // (comment in to see bug)
        // height: isActive ? undefined : 0, // using this instead of display: "none" seems to sort it (comment out to see bug)
      }}>
        <TextInput 
            label="Name"
            value={name}
            onChangeText={handleChangeName}
            style={styles.inputStyles}
            contentStyle={styles.inputContentStyles}
            underlineStyle={styles.underlineStyle}
        />
      </View>
    </SafeAreaView>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    backgroundColor: '#ecf0f1',
    padding: 8,
  },
  buttonStyles: {
    width: 120,
    height: 44,
    marginBottom: 44,
    alignItems: "center",
    justifyContent: "center",
    backgroundColor: "green",
    borderRadius: 12,
    overflow: "hidden"
  },
  buttonText: {
    fontWeight: 600,
    color: "#ffffff"
  },
  inputStyles: {
    fontSize: 14,
    backgroundColor: "#f4f4f3",
    borderRadius: 12,
    borderTopLeftRadius: 12,
    borderTopRightRadius: 12,
    overflow: "hidden",
  },
  inputContentStyles: {
    backgroundColor: "#ffffff",
  },
  inputUnderlineStyles: {
    height: 0,
    display: "none",
  }
});

package.json

{
  "dependencies": {
    "expo": "^49.0.15",
    "@expo/vector-icons": "^13.0.0",
    "react-native-paper": "^5.7.0",
    "react-native-safe-area-context": "4.6.3"
  }
}

Preview

In Production (Actual Behaviour) image

In Production (Expected Behaviour, toggled after changing text) image

Snack (Actual Behaviour) image

Snack (Expected Behaviour) image

What have you tried so far?

In production we upgraded to the latest react-native-paper release v5.12.5 to no avail.

Fix/Workaround

Slightly changing the implementation of the "accordion" to have a height of 0 rather than display: none renders the TextInput as expected.

Your Environment

software version
ios 15-18
android 12
react-native 0.72.10
react-native-paper 5.7.0
node 20.11.0
npm 10.2.4
expo sdk 49.0.15
definitelytobey commented 7 hours ago

I believe the main issue here isn't the component losing its default properties or failing to calculate values on state change. From what I understand, the TextInput components contentStyle and underlineStyle params have default props assigned to them. So, we can either set the style or update selected styles from the default props. It seems like you're setting the props instead of updating them.

If you keep the default props and only modify them, it seems to load correctly with the styling and labels placed as expected.

Here’s what worked for me:

import { useState } from "react";
import { View, Text, TouchableOpacity, SafeAreaView, StyleSheet } from 'react-native';
import { TextInput } from "react-native-paper";

export default function App() {
  const [name, setName] = useState('Foo');
  const [isActive, setIsActive] = useState(false);

  const handlePressButton = () => {
    setIsActive(!isActive);
  };

  const handleChangeName = (newValue: string) => {
    setName(newValue);
  };

  return (
    <SafeAreaView style={styles.container}>
      <TouchableOpacity style={styles.buttonStyles} onPress={handlePressButton}>
        <Text style={styles.buttonText}>Show Input</Text>
      </TouchableOpacity>

      <View style={{
        height: isActive ? undefined : 0,  // Using this instead of display: "none" seems to fix it
      }}>
        <TextInput 
            label="Name"
            value={name}
            onChangeText={handleChangeName}
            style={(props) => ({ ...props, ...styles.inputStyles })}
            contentStyle={(props) => ({ ...props, ...styles.inputContentStyles })}
            underlineStyle={styles.inputUnderlineStyles}
        />
      </View>
    </SafeAreaView>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    backgroundColor: '#ecf0f1',
    padding: 8,
  },
  buttonStyles: {
    width: 120,
    height: 44,
    marginBottom: 44,
    alignItems: "center",
    justifyContent: "center",
    backgroundColor: "green",
    borderRadius: 12,
    overflow: "hidden",
  },
  buttonText: {
    fontWeight: '600',
    color: "#ffffff",
  },
  inputStyles: {
    fontSize: 14,
    backgroundColor: "#f4f4f3",
    borderRadius: 12,
    overflow: "hidden",
  },
  inputContentStyles: {
    backgroundColor: "#ffffff",
  },
  inputUnderlineStyles: {
    height: 0,
    display: "none",
  },
});

This is my first comment on GitHub...